DEV Community

Cover image for How I used Twilio and NodeJS to support vote-by-mail in Pennsylvania.
Ryan Roat
Ryan Roat

Posted on

How I used Twilio and NodeJS to support vote-by-mail in Pennsylvania.

In October 2020, with the general election in the United States rapidly approaching, it became clear that an unprecedented number of voters were opting to receive and submit their ballots via the recently expanded vote-by-mail system in Pennsylvania. I was asked to contact over one hundred voters in my neighborhood who had requested ballots but hadn’t returned them. I needed to remind them of instructions and deadlines regarding how and when to return ballots.

In previous election cycles, I’d sent out texts from my phone one-by-one to neighbors to remind them of election day, but I knew that I should be able to leverage some code to facilitate outreach this time. I’d heard of Twilio, initially as a sponsor of both the CodeNewbies podcast and CodeLand Conference, and also in a few business news reports. The podcast sponsorship advertisement said it was easy to get started, and even offered some credit for a trial. A quick google search yielded some suggestions directly from the Twilio blog and I started considering the needs for my program.

It’s a simple concept: send a text message to a list of phone numbers. Yet I had to create something that could work with my specific circumstances. The biggest issue was that I did not know exactly how the contact information would be provided to me. I would need to keep the data input flexible.

The Twilio blog post I used as the starting point - https://www.twilio.com/blog/2017/12/send-bulk-sms-twilio-node-js.html - is very helpful. While it doesn’t go step by step regarding setting up a new Twilio account, it does link to support for those steps. It’s simple to create an account, and Twilio provides trial credit, enough for me to test my concept by renting a phone number for sending and receiving texts and to potentially send hundreds of test text messages. (Note: during this trial phase, all phone numbers must be verified and all texts are prepended with a ‘Twilio trial’ message.) Use my Twilio referral link and if you upgrade beyond a trial, we’ll each get bonus credit.

I began, as the blog post suggests, by setting up the basic API call to send one message to one phone--mine. Since I would eventually send one specific message to every recipient, I hard-coded the outgoing message as a CONST variable in the code. An obvious future feature addition would be to make the outgoing message variable in some fashion, but needing just the minimum viable product, I ignored this feature throughout my project.

Speaking of ignoring things, the Twilio API authentication credentials need to be kept secret, so they are stored in a .env file. Remember to add that file to your .gitignore file to keep them secret, if you are committing the project to github or a similar service. While I only run the app locally on my personal machine, I commit all my projects to github, so keeping some files in this project confidential is a necessity.

My app, at its most basic, requires only two package installations. Twilio’s node package, and the dotenv package to access .env data within the app. I did not utilize the node-env-run package the Twilio blog post suggests, but feel free to explore that option as well.

Once the app dependencies are installed, and a Twilio trial account is set up, it’s remarkably simple to send a text message to a single phone number: only 13 lines of JavaScript!

JS code sample of Twilio messaging API call

I sent myself a test message and it worked! My phone buzzed with an incoming message; that was the moment I knew that I’d be able to automate my texting obligations, and while it would not be free, it would be significantly cheaper to write the code myself than to use some third-party text service.

My next step was to duplicate the Twilio blog post’s code to send texts to multiple numbers stored in an array. At this point, I still did not know in what format the target phone numbers would be provided to me, but I had to start somewhere. I figured I would somehow convert the provided data into an array once I got a look at it.

Due to the speed with which a Twilio number can send a text, and restrictions from mobile carriers on how many texts they will allow from a particular number before they consider it spam and block further messages, it is recommended to use what Twilio’s calls ‘messaging service.’ (See the Twilio blog post for more details.) This allows the app to send all the outgoing requests to the messaging service efficiently, and then the service completes the work independent of the app.

I was able to test the update using messaging service with an array of one phone number, which worked as expected. I added an additional target phone number to the array, and was able to text to separate numbers running the app once. Success! Now where was my data???

Once I finally had a list of contacts provided to me, I had to jump through a few hoops. I received a PDF with the contact information, and had to copy all the information and import it into a spreadsheet. Via the spreadsheet I was able to isolate the phone numbers, and save them to a text file, one number per line. Next, I had to wrap my head around reading a file in Node.

Using the NodeJS fs (file system) module I loaded the numbers into an array, splitting the numbers on the escape characters \r\n for each carriage return/newline in the file. I also eliminated any duplicate numbers by converting the array to a Set, which is a collection of unique values; duplicates are eliminated upon conversion to a Set. Then using the spread operator to create an array, the unique values in the Set were copied back to the original array. It’s easier to see than to explain this step:

JS code sample

Finally, the format of numbers needed to be in a specific format for Twilio, the E.164 format. Mapping over the array of numbers, I passed each through a regex to strip out anything not a digit, then prepended the E.164 format required ‘+1’ to each. All of my data consisted of US phone numbers of 10 digits without a leading ‘1,’ but if my data were not as consistent, I would have had to check for those leading 1s prior to the prepend step. (Wes Bos’ free JavaScript30 course was very helpful with the regex, specifically Day 6 ‘AJAX Type Ahead.')

JS code sample of regex

With that final conversion, I had an array of phone numbers ready for the Twilio API call. It was the moment of truth. Sure, my code had worked with an array of two phone numbers, but what about 50 or 100? Only one way to find out. I checked my code again; double checked the text file conversion of every number console.log’d out to my terminal, and at last, uncommented the API call I had tested earlier with just two phone numbers. I ran my bulk.js app, and received my ‘Messages sent.’ output--no errors! I hopped over to my Twilio console and saw my messages sent tally (and billing) ticking up.

It is possible to build an app that can receive and interpret both Twilio service status information as well as incoming text messages such as replies. But in this case, trying to get an MVP working, I decided not to write any code to receive replies, and instead relied on the Twilio console to check any feedback. It started trickling in, which meant that my app worked! We had asked if there were any questions, and we got a few. We also received a few status updates regarding ballot submissions, and of course, a few ‘STOP’ and ‘QUIT’ messages. Twilio actually sees those and essentially blacklists those numbers for my account, meaning even if I try to text them again, Twilio won’t send them through. Since I have no interest in annoying my neighbors with multiple texts, I find that a helpful feature.

According to published reports, about 10,000 ballots were received after the election day deadline in November. That is still a high number, but it’s half of the 20,000 ballots received after the primary election day in June, while more than one million more voters utilized the vote-by-mail system in November, an increase of almost 79%! I like to think that my project automating texts to over one hundred of my neighbors helped.

Check out the code on GitHub.

cover image: (Danya Henninger/Billy Penn)

Top comments (0)