Sending 10,000 calls to Congress with Node.js and Twilio

May 26, 2017
Written by
Ian Webster
Contributor
Opinions expressed by Twilio contributors are their own

Capitol Hill in Washington, D.C.

Over the past year, major changes in American politics have left a lot of people feeling like they should be doing more to get involved.

Political advocacy can take a lot of forms.  It can be passive or active, partisan or nonpartisan, personal or large-scale.  As engineers, we are uniquely empowered to apply our skills to important causes.  This post describes how I built a simple, open-source app using Twilio’s Voice API that’s handled over 10,000 calls to senators and representatives in Congress (or just jump to the finished code) You can also watch me code this application in this video of my SIGNAL 2017 talk.

Getting started

Our stack is Node/Express, Twilio, and an API from the Sunlight Foundation that allows us to look up representatives for a given zip code.

Here’s how it works:

  1. User calls our Twilio phone number
  2. Phone number asks them for their zip code
  3. User enters their zip code
  4. Phone number connects them with their representatives

call-congress-animated.gif

The phone number calls representatives one after the other.  After the user inputs a zip code, we call their first representative.  Once that call is complete, we automatically dial their second representative (and third, and so on).

In order to create this call flow a simple Node app uses the Twilio API to do the following:

  1. Define an Express route that takes new inbound communications from Twilio.
  2. Ask the user for their zip code.
  3. Receive the zip code input from Twilio.
  4. Look up representatives for this zip code and have Twilio forward the call to their phone numbers.

Create an Express route to accept new inbound calls

Start by setting up a boilerplate Express app in index.js:

const express = require('express');
const bodyParser = require('body-parser');
const request = require('request');
const twilio = require('twilio');

// Set up and configure the server.
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// Twilio will load this route when you receive a new call.
app.post('/new_phone_call', function(req, res) {
   // New phone call logic will go here
});

app.listen(3000);

Note that you’ll have to npm init -y followed by npm install —save express body-parser request twilio.  This will create a package.json file with default values and then write our dependencies to it.

Ask the user for their zip code

We expect Twilio to request the new_phone_call route when a user makes a new call (to learn more about how Twilio works, see Responding to Phone Calls in Node.js).  Now we have to construct a response to Twilio’s request.

For this step, we’ll use the twilio.TwimlResponse object to build our response.  The TwilioResponse object gives us a node-friendly way to generate TwiML, the instructions Twilio uses to know what experience to create for the user who called our number.

First, we probably should  a greeting and instructions:


app.post('/new_phone_call', function(req, res) {
    // Construct our phone response.
    var call = twilio.TwimlResponse();
    call.say('Thanks for calling.  Enter your zip code to be connected with your senators and representatives!');
});

Then let’s <Gather> some user input:


app.post('/new_phone_call', function(req, res) {
    // Construct our phone response.
    var call = new twilio.TwimlResponse();
    call.say('Thanks for calling.  Enter your zip code to be connected with your senators and representatives!');
    call.gather({
      numDigits: 5,
      timeout: 30,
      action: 'redirect_to_congress',
      method: 'POST',
    });
     
    // Send phone response back to Twilio.
    res.type('text/xml').send(call.toString());
});

The gather() function does a few very important things.  In order to look up representatives by zip code, we need a normal 5-digit zip, so we tell Twilio to wait for the user to enter 5 digits using the numDigits attribute.  The timeout attribute tells Twilio to wait for at most 30 seconds for those 5 digits to be entered before giving up.

The action and method attributes tell Twilio how to notify us after the user has completed
their input.  In this case, we instruct Twilio to POST a new HTTP request to an endpoint on our server called redirect_to_congress.  We haven’t built that endpoint yet, but we’re about to.

Receive zip code input from Twilio and look up Congresspeople

For this new request from Twilio let’s set up a new route with a new response.  The zip code entered by the user will come in the HTTP request as the Digits parameter.  We can ask Express to give that parameter to us using req.body.Digits.

Using the zip code we’d like to look up their Congressional representatives.  We’ll use the Sunlight Foundation API to do this.

The API can be called with an HTTP GET request:

app.post('/redirect_to_congress', function(req, res) {
    // Get the zip code digits inputted by the user.
    var zip = req.body.Digits;
    request('https://congress.api.sunlightfoundation.com/legislators/locate?zip=' + zip, function(err, resp, body) {
        // A list of objects that represent members of Congress.
        if (err) {
            console.error('Could not look up zip code', zip);
            res.send({success: false, message: 'Zip code lookup failed'});
            return;
        }
        var people = JSON.parse(body).results;

        // List will be processed here...
    });
});

The Sunlight API will return a list of objects, one for each congressional representative associated with the zip code entered by the caller.  Now, let’s add some call logic that loops through the list of Congresspeople and dials them in sequence:

// A list of objects that represent members of Congress.
var people = JSON.parse(body).results;

var call = new twilio.TwimlResponse();
people.forEach(function(person) {
    var name = person.first_name + ' ' + person.last_name;
    call.say('Now connecting you with ' + name);
    call.dial(person.phone);        
});

// Send it all back to the caller.
res.status(200).type('text/xml').send(call.toString());

That’s it!  You’re done writing code.

Hosting your server

Now let’s run the app.

Normally you’d deploy this app to a server of some sort – AWS, Heroku, or similar but setting up this app in production is beyond the scope of this post.  Instead we’ll just test the app by running it local.

To do that first install LocalTunnel.  We’ll do a global install for simplicity, but you can also add this to your package.json:

npm install -g localtunnel

Now run the app with:

node index.js

This will start the app on port 3000.  In a separate terminal window, type:

lt —port 3000

Localtunnel will generate a random, public URL that forwards HTTP requests to the application running on your localhost..  The lt command will produce output that looks like:

your url is: https://nhycchbacp.localtunnel.me

Take note of this URL.  We’re about to set up Twilio to talk to it.

Pointing Twilio to your server

Now it’s time to connect the dots.  We need to set up a Twilio phone number and tell Twilio where to find our app.

Go to the Phone Numbers dashboard and add a new phone number.

Once you’ve created a phone number, click on it to configure it.

We’re only dealing with Voice right now, so we just need to modify the numbers Voice configuration.  Enter the URL of your server or localtunnel, followed by the /new_phone_call entry point.

When a new call is make to your Twilio number, Twilio will make an HTTP POST request to the URL you have configured.

You’re done!  Dial your phone number, enter your zip code, and you’ll be connected with your representatives in Congress.

Overall

Taking a step back.  We’ve built an incredibly powerful tool in just 46 lines of code.

In the past 6 months, a simple application based on the code above has handled over 10,000 calls to Congress.  Useful political advocacy tools don’t have to be complicated or advanced — in fact, the best tools are often the simplest.  Mobilizing thousands of people is no small feat, and it turns out the Twilio API is an excellent way to get started.

Check out the full source code for this tutorial on Github.

Imagine what else could be done to extend this:

  • Robot voices are impersonal.  Record real audio prompts to greet the callers, ask them to enter their zip code, etc.
  • The Sunlight Foundation API dependency introduces lag and an extra point of failure.  We could cache zip code lookups or just store the phone number lookup on the server.
  • Add a switchboard so the user can listen to and choose among different options (“dial 1 for X, dial 2 for Y….”)
  • Extend this to include state representatives.

We’ve done all this and more with the General Congress Hotline project, which builds on this 46-line Call Congress example.

Contact me if you have questions or want to learn more!