Building an App to Make Browser-based Calls to  Congress with Flask and Twilio.js on Heroku
Tags: javascript, twilio, python, heroku
Published: 2020-12-25 11:14:25

---
title: Building an App to Make Browser-based Calls to  Congress with Flask and Twilio.js on Heroku
published: true
date: 2020-11-10 08:39:33 UTC
tags: javascript,twilio,python,heroku
canonical_url: https://medium.com/@jmarhee/building-an-app-to-make-browser-based-calls-to-congress-with-flask-and-twilio-js-on-heroku-b4d85886206e
---

### Building an App to Make Browser-based Calls to Congress with Flask and Twilio.js on Heroku

![](https://cdn-images-1.medium.com/max/780/0*cha4tI7IdhxS0SBO.jpg)<figcaption>Your leaders should be accessible to the public</figcaption>

In 2015, I wanted to build an app to provide a way for administrator of public networks (school, libraries, etc.) to provide a look-up and dial tool for members of congress and have it deployable on any target (comparatively low-power machines, or on a personal laptop, or wherever phone access or this information is inaccessible for whatever reason), as well as as a platform application, which we built using these concepts.

Twilio seemed like a natural solution for this. I recently re-architectured the application, mostly to bring it into compliance with the latest Twilio JavaScript tool, and to refresh some of the clunkier parts of the original application. I elected to use Flask for this, and ultimately deployed it to Heroku.

**To see the live product, you can visit:** [**https://dial.public.engineering**](https://dial.public.engineering)

More information about the project can be found on our twitter, [at-publiceng](https://twitter.com/publiceng).

If you’re ready to check out how we went about building this tool…

**Setup**

This application has a few external dependencies:

- You will need a [Twilio number capable of making outbound calls](https://www.twilio.com/phone-numbers).
- You will need your [Twilio API Key](https://www.twilio.com/docs/iam/keys/api-key-resource#create-a-new-api-key) and SID.
- [A TwiML Application (which you will need the SID for this);](https://support.twilio.com/hc/en-us/articles/223136607-What-is-an-Application-SID-) when you set up the TwiML app, you will have the “Voice URL” set to something like “http://${your\_domain}/voice” (the URI used in the app we’ll walk through is /voice so unless you modify that, this should match)
- [A Google Civic API key](https://developers.google.com/civic-information) (for retrieving the representative’s contact information)

Your application will make use of environmental variables to set this, so when you deploy your application (in our case on Heroku), whatever facility (a PaaS like Heroku, or via a provisioning tool like Terraform, or on a flat Linux system) may exist for this should be used to set the following variables:

```
export twilio_sid=${twilio_sid}
export twilio_token=${twilio_token}
export twilio_twiml_sid=${twiml_sid}
export numbers_outbound="+12345678900"
export GOOGLE_API_KEY=${google_civic_api_key}
```

In your project root, you’ll need a requirements.txt :

```
Flask==1.1.2
gunicorn==20.0.4 # Only if you plan to deploy to Heroku
requests==2.24.0
twilio==6.47.0
jsonify==0.5
```

In your app.py , import the following, and we’ll make use of the above variables, before proceeding:

```
from flask import Flask, render_template, request, jsonify

import os

import requests

from twilio.rest import Client

from twilio.jwt.client import ClientCapabilityToken

from twilio.twiml.voice_response import VoiceResponse, Dial

import urllib

import base64

import random, string

TWILIO_SID = os.environ['twilio_sid']

TWILIO_TOKEN = os.environ['twilio_token']

TWILIO_TWIML_SID = os.environ['twilio_twiml_sid']

NUMBERS_OUTBOUND = os.environ['numbers_outbound']

GOOGLE_API_KEY = os.environ['GOOGLE_API_KEY']

app = Flask( __name__ )
```

**Building the application: Functions**

The app relies heavily on the passing and receiving of dictionaries as a messaging format, so most functions will send or receive one such dictionary, and these will eventually be used to populate the templates for the web UI itself.

![](https://cdn-images-1.medium.com/max/1024/1*zRWhH5G1zcDPy98QboGoAg.png)

First, a function to take a zip code, and retrieve representative contact info, and build a response containing formatting numbers, and other data I might use from that datasource. Then, I proceed to get some aesthetic data for the UI, like the name of the locality this area covers (for the House of Representatives, for example):

![](https://cdn-images-1.medium.com/max/1024/1*U4oh3jhFKWQ7sV9BCRgrqw.png)

From there, we go into the actual work of using this data, and making some calls. A small function to generate, and then set a default\_client which will be important for the callback from your TwiML application, which is a requirement to be able to make the outgoing calls:

```
def randomword(length):

   letters = string.ascii_lowercase

   return ''.join(random.choice(letters) for i in range(length))

default_client = "call-your-representatives-%s" % (randomword(8))
```

then a function to validate a phone number to ensure it comes from this datasource:

```
def numberVerify(zipCode, unformatted_number):

    reps = get_reps(zipCode)

    nums_found = []

    for r in reps:

        if unformatted_number in r['unformatted_phone']:

            nums_found.append(r['name'])

            photoUrl = r['photo']

   if len(nums_found) != 0:

       return { 'status': 'OK', 'zipCode': zipCode, 'name': nums_found[0], 'photo': photoUrl }

   else:

       return { 'status': 'FAILED' }
```

**The Flask Application and URL Routes**

With the helper functions completed, you’ll see how they are consumed in the decorated functions for Flask that run when a route is hit using a designated HTTP method, for example, for / :

![](https://cdn-images-1.medium.com/max/341/1*IbTteaKozC3TRf2ysiie2g.png)

the following template is returned:

![](https://cdn-images-1.medium.com/max/942/1*M3RMu_cEYLXQ8wi-3y5TYQ.png)

So, once you submit your Zip code, it is POST ‘d to the /reps URI:

![](https://cdn-images-1.medium.com/max/952/1*NXvu1W5vJDbnEhX0oKjgKw.png)

which, you’ll see, consumes the helper functions we wrote above: from the form in the template above, it retrieves your zip code, hands it to location\_name to get your locality name, to representatives to build a dict of your representatives and their info, and we use the default\_client we specified above which the Twilio.js tool (which I’ll demonstrate in a moment) will connect to in order to make the call from your browser. We use all of that data in the template, to populate a page like:

![](https://cdn-images-1.medium.com/max/874/1*fJlo4ZxIvMooAjZGHsKNKA.png)

You’ll see at the top, your default\_client will have a status indicator, and when it is ready, you can click Start Call on whichever representative to initiate a phone call from the browser.

In the template file, in this case call.html , anywhere in the <head> section, you’ll use the Twilio JS script:

```
<script src="https://media.twiliocdn.com/sdk/js/client/v1.3/twilio.min.js"></script>
```

and then use the following function inside of another script block to call your token endpoint:

```
function httpGet(Url)

{

var xmlHttp = _new_ XMLHttpRequest();

xmlHttp.open( "GET", Url, false ); // false for synchronous request

xmlHttp.send( null );

_return_ xmlHttp.responseText;

}
```

which looks like this, back in app.py :

![](https://cdn-images-1.medium.com/max/495/1*jYXA1qU8qPG_z_iSG5Dxyg.png)

This uses your Twilio token and SID to create a capability token, and then you can add capabilities using the TwiML SID, and for example, allow incoming callbacks using your default client to allow Twilio to connect a call from your browser back to the application.

So when you start the call, in the template, by clicking the button:

![](https://cdn-images-1.medium.com/max/1024/1*QVrbYmjckPZPb89mloeBvw.png)

The onclick action will connect your Twilio.Device to the phone number from that iteration of the representatives dictionary.

This will hand off the new token, the client ID, and the number you wish to call to the above Twilio device, which once received, will use the TwiML application’s callback URL, in this case, /voice to connect the browser to the call. The /voice function is somewhat involved and was probably one of the more complicated pieces to figure out, as some of this diverged pretty distinctly from the documentation as compiled:

![](https://cdn-images-1.medium.com/max/604/1*yM6ZcR3iJDxKbA-bGri9zg.png)

The purpose of TwiML apps is to provide a response to a call to Twilio APIs/phone number, and in this case, we’re providing a VoiceResponse() , so we need from the request it received the phone number to send that voice response to, which we’re splitting out of the request form as number:<whatever> , and in the absence of a number, the default\_client. NUMBERS\_OUTBOUND is your Twilio programmable voice number you acquired at the beginning, which will appear on the caller ID, and the Dial class will facilitate the rest.

**Deploying to Heroku**

[I have a repository](https://git-central.openfunction.co/public.engineering/call-your-representatives) (I will link to all of this again at the end) for deploying to DigitalOcean and to Heroku (where the app lives now), to show a couple of different methods of how I’ve handled deploying this app over time, however, this will focus on the application layout, and a baseline approach to deploying to Heroku with Terraform.

In your project root, you’ll need a Procfile which will inform Heroku how to run the application, in this case:

```
web: gunicorn app:app
```

This is one of the packages you might remember from your requirements.txt , and since Heroku prefers the Pipenv format for managing the application as a virtualenv, we can use it to generate the appropriate package manifest:

```
python3 -m pipenv install -r requirements.txt
```

and commit the resulting Pipenv file instead along with the Procfile.

With the Heroku requirements committed to your Git repo, you can proceed to create, in another directory, your Terraform project.

You’ll create the following vars.tf file:

```
variable "release_archive" {} #The Download URL of your git repo

variable "heroku_app_name" {}

variable "release" {

    default = "HEAD"

}

variable "twilio_sid" {}

variable "twilio_token" {}

variable "twilio_twiml_sid" {}

variable "numbers_outbound" {}

variable "google_api_key" {}
```

then, in main.tf we can start laying out the deployment:

```
provider "heroku" {

    version = "~> 2.0"

}

resource "heroku_app" "dialer" {

    name = "${var.heroku_app_name}"

    region = "us"

}
```

Then we’ll specify what Heroku should be building:

```
resource "heroku_build" "dialer_build" {

app = "${heroku_app.dialer.name}"

buildpacks = ["https://github.com/heroku/heroku-buildpack-python.git"]

source = {

    url = var.release_archive

    version = var.release

}

}
```

I am using the release variable to be something you can update in order to have Terraform redeploy the application, rather than anything to do with what version it deploys from; you’ll want to specify a tag or a branch in your release\_archive URL which will be something like:

```
release_archive = "https://${git_server}/${org}/call-your-representatives_heroku/archive/${branch_or_tag}.tar.gz"
```

this process allows you to re-apply the same version, but still have the state update in Terraform as a detectable change. The buildpack line just refers to the Heroku environment to use, in our case, their default Python stack:

```
buildpacks = ["https://github.com/heroku/heroku-buildpack-python.git"]
```

Now, our application which has a lot of environment variables, and because they’re credentials, we want them handled properly, we are going to specify the following blocks for our above Heroku application:

```
resource "heroku_config" "common" {

    vars = {

        LOG_LEVEL = "info"

    }

    sensitive_vars = {

        twilio_sid = var.twilio_sid

        twilio_token = var.twilio_token

        twilio_twiml_sid = var.twilio_twiml_sid

        numbers_outbound = var.numbers_outbound

        release = var.release

        GOOGLE_API_KEY = var.google_api_key

    }

}

resource "heroku_app_config_association" "dialer_config" {

    app_id = "${heroku_app.dialer.id}"

    vars = "${heroku_config.common.vars}"

    sensitive_vars = **"${heroku\_config.common.sensitive\_vars}"**

}
```

You’ll specify all of these values in your Terraform variables, or in your terraform.tfvars file:

```
release = "20201108-706aa6be-e5de"

release_archive = "https://git.cool.info/tools/call-your-representatives/archive/master.tar.gz"

heroku_app_name = "dialer"

twilio_sid = ""

twilio_token = ""

twilio_twiml_sid = ""

numbers_outbound = "+"

google_api_key = ""
```

There are other optional items (a Heroku formation, domain name stuff, and output), but this covers the deployment aspect from the above application layout, so you can proceed to set your [Heroku API key](https://help.heroku.com/PBGP6IDE/how-should-i-generate-an-api-key-that-allows-me-to-use-the-heroku-platform-api):

```
HEROKU_API_KEY=${your_key}
HEROKU_EMAIL=${your_email}
```

in order to initialize the Heroku Terraform provider:

```
terraform init
```

then you can check your deployment before you fire it off:

```
terraform plan
terraform apply -auto-approve
```

and then head to http://${heroku\_app\_name}.herokuapp.com to see the deployed state.

**More Resources**

[Follow public.engineering on Twitter](https://twitter.com/publiceng)

[Call Your Respentatives app source](https://git-central.openfunction.co/public.engineering/call-your-representatives_heroku)

[Call Your Representatives deployment scripts](https://git-central.openfunction.co/public.engineering/call-your-representatives)

[Single-use VPN Deployer app source](https://git-central.openfunction.co/public.engineering/vpn_deployer_heroku)

[Single-use VPN Deployer deployment scripts](https://git-central.openfunction.co/public.engineering/vpn_deployer) (also includes DigitalOcean and Terraform deployment plans)

**If you’d like to support the platform in keeping up with fees for the price of the calls, and that of hosting, or would just like to enable ongoing development for these types of projects, and to keep them free for the public’s use, please consider** [**donating**](https://liberapay.com/public.engineering/) **!**