Participate in Local Issues with a SMS Chatbot
Lizzie Siegle
Posted on July 23, 2020
If you have opinions about how your city is run, it’s more important than ever to make your voice heard. This blog post will go over how to build a SMS chatbot using Twilio Autopilot, Twilio SendGrid, Twilio Functions, TwiML Bins, and JavaScript to engage in local civic issues.
One example might be San Francisco, where the city is planning to permanently cut 40 of 68 bus lines and free bus rides for youth.
In this blog post, concerned citizens can tell members of the San Francisco County Transportation Authority to not cut bus lines. Read on for a step-by-step process to build the bot, and you can text the +14153068517 number to see the example!
Set Up your SMS Chatbot
To follow along with this post, you need three things:
- A Twilio account - sign up for a free one here and receive an extra $10 if you upgrade through this link
- A Twilio phone number with SMS capabilities - configure one here
- A SendGrid account - make one here
Go to your Autopilot console and under Select a Bot click Start from Scratch.
Give your bot a title like "MuniCall" and click Create bot.
Next to the Greeting task, click Program and replace the JSON there with a Say
and Listen
Action announcing what services the bot can provide.
{
"actions": [
{
"say": "Hi! I'm a chatbot that will automatically mass email or call ALL 11 San Francisco County Transportation Authority members for you. \n\n Send something like `email`, `call`, or `resources` to get started."
},
{
"listen": true
}
]
}
Click save.
Using Twilio Functions and TwiML Bins for Calls to Decision Makers
Click All Tasks and add a task called Call.
On the same line as that Call task, click Train to add samples that will trigger this task. Samples are different words or phrases the user might say to call their representatives, running this task. For the Call task you might add samples like "phone", "voicemail", "call representatives", "call reps", "dial", "leave a voicemail", and other similar phrases.
After adding your samples, click Switch to program task near the top-right.
Replace the JSON in the JSON bin with the following which uses a Collect Action to ask a series of questions and group them and a user's answers together.
{
"actions": [
{
"collect": {
"name": "call_collect",
"questions": [
{
"question": "Let us call all 11 members of the San Francisco County Transportation Authority or leave a voicemail! What is your first name?",
"name": "name"
},
{
"question": "What is your SF zip code? If you need one, use 94109.",
"name": "zip_code",
"type": "Twilio.NUMBER"
}
],
"on_complete": {
"redirect": {
"method": "POST",
"uri": "REPLACE-WITH-YOUR-FUNCTION-IN-THE-NEXT-PART.twil.io/municall"
}
}
}
}
]
}
After asking the user all the questions in the questions
array, this task redirects to a Twilio Function to make the phone calls using JavaScript. Create a new Function here by clicking the red plus button. Make a blank function and give it a name like "Muni Call" and add on to the path as shown below. Don't forget to replace the Function path in your Call
task above!
Replace the code in the Function with the following JavaScript:
exports.handler = function(context, event, callback) {
//Memory from the answered question
const memory = JSON.parse(event.Memory);
//get answers from Memory
let name = memory.twilio.collected_data.call_collect.answers.name.answer;
let zipCode = memory.twilio.collected_data.call_collect.answers.zip_code.answer;
const bodyCall = `Hello {{Supe}}. My name is ${name} and my zip code is ${zipCode}. Muni is a critical service to SF. Keep SF healthy, affordable, liveable, and accessible for all, including working families. The populations who ride Muni the most are the people our city is ALREADY failing to serve.`;
const numbers = {
'Aaron': '+14155547450',
'Rafael': '+14155546968',
'Dean': '+14155547630',
'Sandra': '+14155547410',
'Matt':'+14155547970',
'Gordon': '+14155547460',
'Hilary':'+14155545144',
'Ahsha':'+14155546975',
'Catherine': '+14155547752',
'Shamann': '+14155547670',
'Norman':'+14155546516'
};
const client = context.getTwilioClient();
var ctr = 0;
Object.keys(numbers).forEach(function(key) {
console.log(key, numbers[key]);
client.calls
.create({
machineDetection: 'Enable',
url: `https://handler.twilio.com/twiml/EH19fc2a326d3871000810eb5495c2d861/?Supe=${key}&Name=${name}&Zip=${zipCode}`,
to: numbers[key],
from: 'YOUR-TWILIO-NUMBER'
}).then(function(call) {
console.log(call.sid);
ctr ++;
console.log(ctr);
if (ctr == Object.keys(numbers).length) {
const resp = {
actions: [
{
say: "Thank you for calling all 11 reps of SF County Transit Authority. \n\n The call to them said: " + bodyCall + "\n\n You can also leave them a voicemail, sign this petition https://bit.ly/2ZNEfbv, spread the word to your friends, send more emails, and more. Want to get more involved? Fill out this form: https://forms.gle/FHqt7W62D9W2t164A"
}
]
};
callback(null, resp);
}
})
.catch(err => {
callback(err);
});
});
};
To make and customize your own civic engagement bot, you would replace the bodyCall
with your own message to representatives about the local issue you want to change and then replace numbers
to the phone numbers of the representatives you wish to call. Don't forget to include your name and precinct in the text!
First we access the memory to get the user's answers and save them as variables name
and zipCode
. bodyCall
is a short transcript (of what the phone call will say to each representative) to be texted back to the user and numbers
is an object containing the names of who to call and their corresponding phone numbers. We loop through that object and make an outbound call to each number with answering machine detection (AMD) enabled, passing it a URL to a TwiML bin containing the following TwiML:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="Polly.Joanna">Hello {{Supe}}. My name is {{Name}} and my zip code is {{Zip}}. Muni is a critical service to SF. Keep SF healthy, affordable, liveable, and accessible for all, including working families.</Say>
</Response>
You can read more on generating dynamic TwiML and templating with Twilio Bins here.
Once we reach the end of the object, go back in the Twilio Function to see that a text message is returned to the user confirming that each representative was called.
Contacting Decision Makers with Twilio SendGrid and Twilio Functions
To contact decision makers on a different medium, we're going to go back to your bot in your Autopilot console and add a new task called Email
. On the same line as Email
, click Train to add some samples that will trigger this task such as "email", "email reps", "email representatives", "email SFMTA", "send email", and similar phrases.
Then click Switch to program task near the top-right and add the following JSON that uses a similar Collect flow to the Call
task:
{
"actions": [
{
"collect": {
"name": "email_collect",
"questions": [
{
"question": "Let us email all 11 members of the San Francisco County Transportation Authority! What is your name?",
"name": "name"
},
{
"question": "What SF neighborhood do you live (or work)? If you need one, maybe try FiDi or Japantown.",
"name": "live_work"
},
{
"question": "What is your SF zip code? If you need one, use 94109.",
"name": "zip_code"
},
{
"question": "What are your demands? If you send `default` we will send one for you (ie. move funds from the police dept. to Muni.)",
"name": "demands"
},
{
"question": "Why is Muni important to you? How does it affect your day-to-day life? If you send `default` we will answer this for you (ie. `Muni is important to me and other San Franciscans because I take it to get to work, volunteer, see friends.`)",
"name": "important_to_you"
}
],
"on_complete": {
"redirect": {
"method": "POST",
"uri": "https://YOUR-TWILIO-FUNCTION.twil.io/muni_email"
}
}
}
}
]
}
The last two questions in the Collect
flow above are more open-ended: the user can type out a longer response or they can send "default" for the chatbot to instead use a default response.
Grab your SendGrid API key. In the Twilio Functions Configuration section, save it as the environment variable SENDGRID_API_KEY
like so:
Now it can be referenced with context.SENDGRID_API_KEY
in any of your Twilio Functions.
Make another new Twilio Function that the email
task will redirect to. Give it a name and path like this below, and don't forget to replace the Function path in your email
task.
In your Function, replace the code with the following JavaScript:
const sgMail = require('@sendgrid/mail');
exports.handler = function(context, event, callback) {
sgMail.setApiKey(context.SENDGRID_API_KEY);
//Memory from the answered question
let memory = JSON.parse(event.Memory);
//get answers from Memory
let name = memory.twilio.collected_data.email_collect.answers.name.answer;
let liveWork = memory.twilio.collected_data.email_collect.answers.live_work.answer;
let zipCode = memory.twilio.collected_data.email_collect.answers.zip_code.answer;
let demands = memory.twilio.collected_data.email_collect.answers.demands.answer.toLowerCase();
let important_to_you = memory.twilio.collected_data.email_collect.answers.important_to_you.answer.toLowerCase();
if (demands === 'default') {
demands = "We demand that Muni for Youth and the 40 Muni bus lines that are meant to be cut as a result of lack of funding are reinstated. It is routes like the 8, 9, 14, 29, etc.. that serve lower-income communities that are the most PACKED and clearly need MORE lines, not fewer.";
}
if (important_to_you == 'default') {
important_to_you = "Every SFUSD student benefits from Muni. A progressive state does not let its most critical services fail. Muni is about accessibility and connecting people--connecting us to family, friends, and what makes SF special.";
}
const messages = [
{
to: 'Aaron.Peskin@sfgov.org',
from: `${name} <we_love_and_need_muni@sf.com>`,
subject: '🍩Muni is a critical service to SF. 🍩',
html: `Hi Aaron! Keep SF healthy, affordable, liveable, and accessible for all, including working families. <p>40% of emissions in SF come from transportation.</p><p>My name is ${name}, I live and work in ${liveWork}, and my zip code is ${zipCode}.${demands} ${important_to_you}</p> <p>Thank you for your time.</p>`,
//...copy and paste this for each representative. Complete code https://github.com/elizabethsiegle/muni_call_email_representatives_chatbot/blob/master/email.mjs
},
];
sgMail.send(messages)
.then(response => {
const resp = {
actions: [
{
say: "Thank you for emailing all 11 members of the SF County Transit Authority. You can also leave them a voicemail, sign this petition https://bit.ly/2ZNEfbv, spread the word to your friends, send more emails, and more. Want to get more involved? Fill out this form: https://forms.gle/FHqt7W62D9W2t164A. \n\n If you want to contact just one representative, try SF Transport Authority Chair/District 3 supervisor Aaron Peskin at (415) 554-7450 and Aaron.Peskin@sfgov.org or District 9 supervisor Hillary Ronen (also on the Metropolitan Transportation Commission) at (415) 554-5144 and RonenStaff@sfgov.org. You can also send more calls and emails by chatting with this bot."
},
{
listen: true
}
]
};
callback(null, resp);
})
.catch(err => {
callback(err);
});
};
This code imports SendGrid at the top, gets the answers to each question in the email
task, and checks if the user sent "default" for the last two questions. If so, we send ready-made blurbs on why Muni is important and create an array called messages
to send bulk emails with SendGrid. When each email is sent, the chatbot sends a confirmation message back to the user. Check out this blog post for more information on sending bulk emails with SendGrid in Node.js.
You can test your chatbot in the Twilio console's Autopilot simulator, but now let's also hook up the bot to a Twilio phone number so your friends can text it!
Configure your Bot with a Twilio Number
Go to the Autopilot console and select Channels from the left-hand menu. Click Programmable Messaging.
Copy that Messaging URL and in a new tab configure your Twilio number in your phone number console.
If you don't have a Twilio number yet, go to the Phone Numbers section of your Twilio Console and search for a phone number in your country and region, making sure the SMS checkbox is ticked.
In the Messaging section of your purchased number, in the A Message Comes In section, set the Webhook to be your Messaging URL and hit Save.
Now you can get out your phone, text the number, and share the number with people to contact San Francisco County Transportation Authority representatives, or whoever you would like to reach! The complete code for this blog post and chatbot can be found here on GitHub.
What's Next for Civic-Minded Chatbots?
Twilio makes it easy to programmatically call, text, email, and generally communicate. You can make your chatbot more complex by adding other tasks to provide information such as:
- resources on helping keep Muni funded
- facts or statistics on public transit
- an option to provide an email confirmation to the user (so they get emailed a copy of the email sent to representatives) Of course, you can customize this code to whatever issues you are most passionate about. If you’re interested in using coding for social change, check out this interview with Sachin Medhekar who is trying to help fight homelessness, how to create successful advocacy campaigns, or see the work Twilio.org is doing!
Here's to hoping we can help keep mass transportation funded and better serve those who rely on it. Thanks for reading and let us know online or in the comments what you're building to make the world a better place!
- Twitter: @lizziepika
- GitHub: elizabethsiegle
- Email: lsiegle@twilio.com
Posted on July 23, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.