Secret Santa for the busy office

The Visual Studio logo, next to an image of Bit the Raccoon.

It’s fast approaching Christmas and, as office tradition dictates, everyone puts their names into a hat to find out who they’ll be buying a gift for as part of this year’s Secret Santa. But it’s also a busy year, everyone is working from home or in meetings all day, so how do you get everyone to pick their name out of the hat?

I decided to remove that obstacle by writing a small DotNetCore application to take everyone’s names, randomly select a recipient for each Santa and then email that Santa to let them know who they’d picked. I had a couple of requirements for this.

  1. The picking must be random
  2. The person running the application (me) can’t know who’s been assigned to whom.
  3. The participants should be fed in from a file

Starting with DotNetCore

The programming language I tend to use for most solutions is C# so I knew I wanted this to be a .NET application. But why wouldn’t I want my application to be cross-platform and use all the nice new tooling. So, as I seem to be doing more often lately, I started the solution in DotNetCore. This is only a small and simple project, it’s using new libraries, I don’t need long term stability and I wanted it to be cross-platform.

Starting a new console application is as simple as typing in the following inside of an empty folder, this creates a new project with a single code file and restores dependencies.

$ mkdir SecretSanta
$ cd SecretSanta
$ dotnet new console

Then off to what should be everyone’s favourite editor, Visual Studio Code, if it’s in your PATH then you can just type in “code .” at the command prompt after creating the app, the single dot is important as it means “This directory”.

Visual Studio Code

Visual Studio Code is Microsoft’s cross-platform code editor, you can customise a lot of how it works and add new capabilities through the extensions marketplace. Extensions such as GitLens and RESTClient make it a very powerful tool, allowing you to edit and debug code in a variety of languages all from the same editor.

If you open the Program.cs file you’ll see a simple Hello World program which you can either run directly in Visual Studio Code, or from the command line (or integrated terminal) with “dotnet run”. This will build the program and execute the program. From here you can start to build out your application, adding new class files, installing packages from Nuget and debugging as you go.

If you need to change the way your application is launched by the debugger so that you can pass in arguments (like I need to) then you can modify your launch.json file and tweak these settings. Unlike Visual Studio you can also specify environment variable values when debugging which makes debugging a lot easier and you don’t end up messing up your development environment with stray settings.

Inside Santa’s Sack

So, with the project started and a text editor open and ready to go I was able to put together the application. First up, how to make sure that for each Santa a random recipient is picked.

What needed to happen was to create a pool of people who could be picked (the hat), that pool needed to exclude the person currently being picked for and anyone else who had been picked previously.

A diagram illustrating how the program will pick a Secret Santa without using names already picked, or allowing your own name to be drawn.

Creating the pool is pretty straight forward using linq.

var hat = participants
    .Where(p => p.Name != person.Name)
    .Where(p => !presentReceipients.Contains(p))
    .ToList();

The interesting part is how to make sure that the people in the hat are randomized so that multiple runs don’t pair the same people together each time. A simple solution to this is to implement a Shuffle method on the list, this sorts the list by working through the list an item at a time and then swapping it with another item after it in the list.

public static void Shuffle<T>(this IList<T> list, Random random)
{
    for (var i = 0; i < list.Count; i++)
    {
        list.Swap(i, random.Next(i, list.Count));
    }
}

This was implemented as an extension method so that I could just call “Shuffle” on the list. It also needed a swap method implementing so that the values are swapped as the list is sorted.

After testing the picking algorithm a few times the program crashed, running again it worked for a few times and then crashed again. The output showed that the problem was in the algorithm, so I set a break point and stepped through using the debugger built into Visual Studio Code. It didn’t directly reveal a problem on that run through but looking at where it crashed it suddenly dawned on me that there was the possibility of the final person only being able to pick themselves. This can happen in the office picking as well, when that happens people put the names back in and try again. In the application this becomes less likely because of removing the person from the hat on each iteration, but it can still happen. A simple change to the algorithm to say that, if there are no people left in the hat to pick from then try again.

This ticked off the first requirement in my list, the recipient selection was now random so that subsequent runs were unlikely to result in the same pairing.

Sending emails

I wanted to have the solution send an email to each Santa, telling them who they were to buy a gift for. One reason for this was because not everyone was in the office together, so making sure that everyone was informed was important. Another reason though was because I didn’t want to know who had been paired, it somewhat spoils the fun.

Rather than relying on a 3rd party library for this simple task I fell back onto System.Net.Mail. For small applications like this it’s fine, but if you’re working with parallel processes you need to be careful as the SMTP client must fully send an email before taking a request to send another. This has the possibility of creating a bottle neck in your application if you’re not careful.

Creating a client is pretty simple and I moved the SMTP details out into a configuration file so that they could be changed easily later (more on why shortly). After this it’s a case of setting the subject, sender, recipient and message body and calling Send (or SendMailAsync if you’re doing it right).

I wanted to test that the application was sending emails though, and without sending the emails to the recipients. This is one of the reasons I moved the SMTP server configuration out into a config file as I decided to use 2 services for email.

Mailtrap – This is a great service which has a free tier option. It’s a fake SMTP server which can receive emails, it just doesn’t send them. What it does do though is trap the email so you can see what has been sent, where it would have gone, what the content is and even get a spam rating on your email. This works a lot better than swapping out email addresses and flooding your own mailbox. The free tier has some limitations, such as the number of emails in the inbox and how many you can send per second, but for this project the free tier was fine. If you want more other tiers are available for not a lot of money.

A screenshot of an email sent by the program that shows the name of the secret santa, as well as the rules of the gift exchange.

Sendgrid – Used for marketing campaigns and enterprise delivery Sendgrid is a great platform for distributing emails. If you happen to have an Azure account as well (I do) then you can create a sendgrid instance for yourself through the portal with a free option available. You’re “limited” to 25,000 emails per month, which for the office secret santa is more than sufficient.

So, the plan is simple, build the application and test sending out the messages using Mailtrap, then when it’s ready to run switch to Sendgrid and run. That way for the final run I can’t see who’s been paired with whom except for whoever was picked for me. That’s my second requirement done.

The CSV input

Last up is the CSV input file with the participants. This needed to be a simple file which contained the person’s name and their email address. To make this work I needed to provide the CSV file path as an input to the program, then have it read the data out and into Plain Old C# Object (POCO) to use inside the program.

PowerArgs is a great library for handling command line inputs. It lets you create a class and use attributes to define the applications arguments. The library then parses the “args” variable and populates a new instance of your class with the values. If you specify that an argument is required, then parsing fails and you can handle that gracefully. In my case the useful feature I wanted was the “ArgExistingFile” attribute, this tells the parser that I’m expecting a file path and that the path should exist, otherwise it’s not a valid argument value. The library also has methods for writing out a command help information to help the user use your program correctly.

Once the file path has been picked up and validated then I can read in the CSV data. Again at this stage I wanted to use a library to read in the file. CSV files often seem like an easy thing to parse, but there are so many quirks (separator value, quote delimiter, escape characters, multiline values etc…) that using a library is an incredibly sensible thing to do. CSVHelper is one of the best libraries out there for working with CSV files, with a range of options and configuration to give you a lot of control. For this application the CsvReader.GetRecords<T> method made the world of difference. Once I had a CsvReader instance then calling this method meant I got an IEnumerable back of objects which represented my data. All that reading and collating in a single line of code, and so the final requirement was met.

Final steps

I ran the code through a few times with Mailtrap as the SMTP server to check that the picker was working, the emails were going and that I hadn’t done anything which could give away who each person’s Santa is. Then, with some people being out of the office, I switched the SMTP settings to Sendgrid and ran it. It worked perfectly. Using the activity view in Sendgrid I could confirm that everyone had been sent an email (although I couldn’t see the content), and I got my own selection emailed to me. Now I just need to make sure that no-one accidentally slips and reveals who they’re buying a present for.

Next up for the tool is to spend some time turning it into a .NET Core Global Tool. This allows others to install your command line application using a simple “dotnet tool install” command. Once that’s done hopefully other people with busy office lives can sort out their secret Santa selections with ease as well.

For now though, if you want to have a look at the code and try it out yourself then you can find it all over at Github.