/secret-santa

Randomize your friends and send secret santa emails

Primary LanguagePython

Secret Santa Dispatcher

Given a CSV of names, emails, and addresses, dispatch emails with info on each person’s Secret Santa. Randomly assign gift givers and receivers.

Usage

python secret_santa.py --participants participants.csv

Optionally, add the --dry-run=true flag if you want to do a test run without actually sending emails.

Setup

  1. Create and activate a venv: python -m venv .venv && .venv/bin/activate
  2. Install dependencies: pip install -r requirements.txt

You also need to create a .env file with the following keys:

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_ADDRESS=person.person@gmail.com
SMTP_PASSWORD=

The defaults above work for gmail accounts, for which you would get a password using App Passwords.

CSV

Given how mailing addresses have commas in them, it’s easier to separate values with |. As such, format the CSV like so:

name|email|address
Bert|bert@sesamestreet.edu|123 Sesame St, City, State 12345
Sherlock|holmes@investigators.org|21 B Baker St, London

Script

import argparse
import csv
from email.message import EmailMessage
import os
import random
import smtplib
from string import Template

from dotenv import load_dotenv

load_dotenv()

# change this template as necessary
email_template = Template("""Hi $your_name,
Thanks for being a Secret Santa!

You are giving a gift to:

$their_name
$their_address

Remember, keep it under $$25 total!

- Nerding Santa's fascinating email robot
""")

def read_csv(path_to_csv):
    """
    Returns a list of dicts representing people
    """
    people = []
    with open(path_to_csv, newline='') as csvfile:
        reader = csv.DictReader(csvfile, delimiter="|", quotechar="|")
        for row in reader:
            people.append(row)
    return people

def randomize(people):
    """
    Return a dict of pairings, where each key and value is a position in the list of people
    """
    assignments = {}
    recipients = [i for i in range(len(people))]

    for i in range(len(people)):
        found = False
        while not found:
            r = random.randint(0, len(recipients) - 1)
            recipient = recipients[r]
            is_same_person = recipient == i
            reciprocal_assignment = assignments.get(recipient, -1) == i
            if not is_same_person and not reciprocal_assignment:
                found = True
                assignments[i] = recipient
                print(f"Giving a gift to {recipient}!")
                recipients.remove(recipient)

    return assignments

def make_templates(people, assignments):
    """
    Build email templates. Returns a list of {to: "email address", body: "email body"}
    """
    emails_to_send = []

    for s, r in assignments.items():
        sender = people[s]
        recipient = people[r]
        email = {
            "to": sender["email"],
            "body": email_template.substitute(
                your_name=sender["name"],
                their_name=recipient["name"],
                their_address=recipient["address"]
            )
        }
        emails_to_send.append(email)

    return emails_to_send

def send_emails(emails_to_send, dry_run):
    """
    Actually send emails
    """

    if dry_run:
        for email in emails_to_send:
            print(email)
        return

    smtp_host = os.getenv("SMTP_HOST")
    smtp_port = os.getenv("SMTP_PORT")
    smtp_address = os.getenv("SMTP_ADDRESS")
    smtp_password = os.getenv("SMTP_PASSWORD")

    s = smtplib.SMTP(smtp_host, smtp_port)
    s.starttls()
    s.login(smtp_address, smtp_password)

    for email in emails_to_send:
        msg = EmailMessage()
        msg.set_content(email["body"])
        msg["Subject"] = "Nerding Secret Santa!"
        msg["From"] = smtp_address
        msg["To"] = email["to"]

        s.send_message(msg)

    s.quit()

def main(path_to_csv, dry_run):
    people = read_csv(path_to_csv)
    assignments = randomize(people)
    emails_to_send = make_templates(people, assignments)
    send_emails(emails_to_send, dry_run)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Send out Secret Santa pairings')
    parser.add_argument('--participants',
                        default=None,
                        help='Path to a CSV file of participants')
    parser.add_argument('--dry-run',
                        default=False,
                        help='Do not actually send emails')
    args = parser.parse_args()
    main(args.participants, args.dry_run)