Interactive SMS Integrations with Twilio and Slack

Aoibhe Wilson
Valtech Switzerland
13 min readJul 3, 2020

Let’s say we have some clients, maybe they’re working remotely in the field, and they need to communicate some requests to your support team. The support team are also remote but they coordinate together using Slack. The main thing your clients have in common in the field is SMS capability. So, let’s look at building an SMS integration to your support team’s Slack channel. We’ll use a free Twilio account to handle the incoming SMS message and we’ll wire that up to the Slack API and use BlockKit to make some nice, useable, interactive messages in Slack. So, let’s get started…

Setting up a Twilio Project

To set up a phone number to receive incoming messages from our clients we will need to setup a new project. First, you will need to set up a login with Twilio if you don’t already have one (if you don’t want to use my referral link, use this one instead). The initial sign-up should walk you through setting up your first project, but if it doesn’t you can follow from here. Once you’re signed in and have set up a project, you will usually find yourself on the project overview page for the last project you were working in. To create a new project we need to go to the project summary. You can find the link in a dropdown under the title of your current project in the top left. Or, if you want, you can browse directly to it at https://www.twilio.com/console/projects/summary.

An animated Gif showing a screen recording of the login process and navigation to the project summary page in Twilio.

On the projects summary page you will find a list of your existing projects and, at the top right of the list, you’ll find the button to create a new project.

Let’s select the create new project button and set up a new account and number. On the first screen we are asked for a name for our project. I’m going to call mine Field Operations Manager but you can chose whatever name you would like. It’s best to be descriptive here so you can tell them apart in the list. Enter your name then select the Verify button.

In the verification step Twilio will ask you to verify an email and possibly a phone number. Follow their instructions to complete the verification process. After the verification is complete there is a short onboarding process.

Animated GIF depicting the Project Creation and Oboarding UI from Twilio.com as the user creates a Node.JS project

Onboarding asks us if we write code. For our purposes, say yes; even if you don’t know how to code, we’ll do the code together in a little bit. Then you’ll be asked for your preferred coding language. Chose the language you’ll use for the project; in our case, Node. Then you’ll be asked what you want to accomplish. Feel free to explore the other options another time, but for now, we’re going to skip to the Dashboard and dive into the project.

Once we’re at the Dashboard we have a good breakdown of our project, its settings, and its parts. You’ve been granted a $15 trial credit for the project, which is more than enough for now. Scrolling a little bit, you can also see your Account SID and AuthToken which we’ll need for later.

Now we get to the phone number. Our program will be a bit useless for our field operatives if there’s no number for them to call. Let’s select the big, red, button labelled “Get a Trial Number”.

Animated GIF depicting the Twilio UI for getting a phone number for your project.

You’ll get a pop-over with a new US-based Twilio number already selected. If you don’t like that number or need a number for another country, there is a link for you to chose a new number. Depending on the country you select for the number, there may be some verification or regulatory information needed, and a cost for the number.

So, now we have our number. You can send an SMS to this number now but nothing is going to happen. We need to build out our integration with Slack.

Setting up a Slack Integration

For this next step you’ll need to be an administrator in a Slack Workspace. While you could do this in any Workspace you have permissions for, I prefer to create a new Workspace of my own so that my testing and troubleshooting don’t get in the way of people who are working or sharing GIFs.

If you haven’t set up a Slack Workspace before, you create them from the same menu you use to add existing workspaces. Click the plus, select ‘Create new workspace’ and follow the steps that open in your browser. Once you’re all set up, you can open your new Workspace in Slack.

Navigate to the Slack API homepage you should already be logged in but if not go ahead and do that now. Here you’ll find a lot of tools and resources for working with Slack Apps and Integrations. From the homepage we’ll select the ‘Start Building’ call-to-action to create our new App. In the popup that opens, enter a name for your App and select your newly created Workspace as the development Workspace. That’s all it takes.

The App dashboard contains a lot of information. Scrolling down the page you find your Authentication properties, we’ll need these in just a moment.

In order to allow our App to communicate in Slack we need to assign some capabilities and permissions. There’s a lot to choose from here and making sure you get the right combination can be a little confusing but Slacks tutorials do a good job walking you through most of them.

For our purposes, we’ll be posting messages as a Bot account. To start out we need permissions for our Bot to write messages in slack. Under ‘Add features and functionality’ select the ‘Permissions’ section. Scroll down slightly to the ‘Scopes’ section. We’re going to be using Bot Token Scopes for now and will need to add two separate scopes. Select the ‘Add an OAuth Scope’ button under ‘Bot Token Scopes’ and in the field that comes up type in ‘chat:’ and you should see chat:write. Select chat:write to add it, this will give the Bot the ability to write messages to the slack channel. Select ‘Add an OAuth Scope’ again and, again, type ‘chat’ then select chat:write.customize. Now you have the ability to customise how the Bot user appears to other users.

Next we will go back to the Dashboard where we will see that Slack has automatically added the ‘Bots’ feature to our App. Select the ‘Bots’ feature so we can customise how our Bot will appear. Select the ‘Edit’ button beside ‘App Display Name’ and add a display name and username for your Bot. These will show up in the Slack channel the same way a normal user would appear. Save the changes in the popup. Optionally, you can select the toggle beneath the name options to make it appear as though your Bot is online all the time.

With your settings configured we’re now ready to Install our App into our Slack Workspace. Back on the project Dashboard again, expand the ‘Install your app to your workspace’ section and select the button there to install. You’ll see a page asking you to approve the installation, select allow. Now go into your Slack Workspace and select ‘Add App’ in the channel where you want the Bot to be able to post, we’ll use ‘general’ for now.

So, we now have a Twilio phone number and a Slack integration. The last thing we need to do is write the handler that will wire the two together.

Wiring it all together

Twilio provides us with a number of ways we can configure our project, with APIs for all of their services. We’re going to use Twilio’s Runtime service to add a function to handle our messages through the Programmable SMS API.

In your Twilio Dashboard find and expand the ‘Products and Services’ menu on the lefthand side of the page under your dashboard home button. Scroll down through the options until you reach the runtime section and select ‘Functions’ you can pin ‘Functions’ to your menu by clicking on the pin so it will be easier to get to later. Select ‘Functions’ to open the page and let’s select the ‘Create a Function’ button to get started.

Twilio provides a number of templates for you to use as a quickstart if you’d like. For now, though, we’re going to chose the ‘blank’ template. Once your function is create you’ll be looking at the configuration page. Give your function a meaningful name, I’ll be using ‘Field Operations Inbound’, and I’ll also set the path to ‘/inbound’. Under ‘Configuration’ select ‘Incoming messages’ as the ‘Event’. Now select the ‘Save’ button at the bottom of the page.

Next we need to copy the inbound URL and assign it to the Webhook for our number. In the functions menu on the left side of the console find the ‘Phone numbers’ section. This will open a list of your active number. Select the phone number for this project. On this page scroll down until you see the Messaging section. Replace the default Twilio Webhook URL with the URL you copied. Make sure to select the ‘Save’ button at the bottom of the page.

We’re now set up to listen for incoming messages at our project and to process them with a handler function that we’ll write next.

Twilio has already provided a very basic boilerplate handler function. Let’s delete the contents of the handler and quickly take a look at what we have.

exports.handler = function(context, event, callback) {
// Do stuff
};

The handler function takes three parameters: ‘context’ contains information about the environment in which our function runs. You can think of this like ‘process.env’ in a locally running Node script. We’ll define some parameters shortly that will be available here. ‘Event’ provides information about the incoming message event and content. ‘Callback’ gives you a function to return a response to Twilio and indicates to Twilio that your handler has completed.

For our handler, we want to recieve the incoming message, get its contents, and its sender, and then hand that data off to Slack for posting. To do this, we’re going to want to add the Slack API to our function context. Open the ‘Configuration’ page from the left menu. Here we’ll see a section for environment variables and Dependencies.

Twilio Function Configuration UI showing Environment Variable and NPM Module configuration.

Select the ‘plus’ button under dependencies and add ‘@slack/web-api’ with version ‘^5.8.0’. While we’re here, let’s add an environment variable as well. In Slack on the ‘OAuth and Permissions’ copy your bot token. In Twilio add an environment variable named ‘SLACK_BOT_TOKEN’ and paste the value you just copied from Slack. Now add one more environment variable named ‘SLACK_CHANNEL’ and give it a value of ‘general’. The first will put our token in context so that we can authenticate with the Slack API, the second will give us an easy way to change the target channel for our Slack messages without the need to modify the code. Select the ‘Save’ button once more and then return to the ‘Manage’ page to go back to editing our function and write our handler.

First, we want to initialise our Slack Web API Client and select our target channel.

exports.handler = function(context, event, callback) {
const { WebClient } = require('@slack/web-api');
const token = context.SLACK_BOT_TOKEN;
const client = new WebClient( token );

const channel = context.SLACK_CHANNEL;
callback(err, null);
}

We include the Slack API through require. Then we pull the value of our Bot token and our channel from the context and we initialise Slack’s WebClient with the token. Next let’s add some code to handle the message and pass it on to Slack. We’ll replace our callback function call with this:

...    async function handleMessage() {
// Post a message to the channel, and await the result.
const {To, From, Body} = event;
const [err, result] = await resolver(
client.chat.postMessage({
...blocks(To, From, Body),
channel: channel,
icon_emoji: ":iPhone:",
username: "Field Ops Support"
})
);

callback(err, null);
};
return handleMessage();...

So, let’s break this down. We create an async function ‘handleMessage’ and return it to our handler function. In ‘handleMessage’ we first destructure the event object and assign the ‘To’, ‘From’, and ‘Body’ properties. As their names suggest, these are the contents, sender, and receiver of our message.

Next we call a resolver function that wraps our Slack message post call. The resolver lets us return an object with either the error or data from a promise and assign it with destructuring. We’ll add the resolver function in a second.

Our message is posted through the call to ‘client.chat.postMessage’ and we pass it an object with some configuration for the message. The first property is ‘blocks’; blocks are a format Slack uses to define the structure of a message. Here we use a function to take our message data and insert it into a block structure. We’ll write that function in a moment. We also pass in our channel as a property and some optional customisation for the Bot’s User Icon and Name. The Icon can be any of Slack’s emojis.

Finally, once our resolver responds with the results of the Slack API call, we respond with the callback. We pass in any error we may have and we leave the response as ‘null’ as we won’t automatically respond to the SMS.

Now, let’s write our resolver and blocks functions:

function resolver(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}

Resolver takes in a promise and returns the results in an array with the format [error, data] where the data property is optional. If our promise is successful we return null for errors and the data from the promise, if it fails we return the errors and the data is assumed null. Since our Slack API call is a promise we can pass it to this resolver and get a nice clean response that we can destructure into errors and data.

Our blocks function assembles our message data into a set of slack Block objects. Blocks in Slack are a structured way of representing the various messages you see in the Slack UI. There are a number of Blocks to chose from, from basic messages, to interactive elements, you can build them out visually using the Block Kit Builder tool. An array of Blocks can look a bit cluttered however which is why I’ve moved their definition into a separate function.

function blocks( to, from, body ) {
return {
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `Received a request for support from *${from}*:`
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `> ${body}`
}
}
]
},
{
"type": "context",
"elements": [
{
"type": "plain_text",
"text": `Received on: ${to} `,
"emoji": false
}
]
}
}

In Block Kit, I’ve added two Markdown sections and a context section. We’ll use template strings to insert our ‘to’, ‘from’, and ‘body’ data into the blocks and then we return the whole payload. When we expand this in our options object for the API call we get a well formatted ‘blocks’ property with our message definition.

So, all together, our code should look like this:

function blocks( to, from, body ) {
return {
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `Received a request for support from *${from}*:`
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `> ${body}`
}
},
{
"type": "context",
"elements": [
{
"type": "plain_text",
"text": `Received on: ${to} `,
"emoji": false
}
]
}
]
};
}
function resolver(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
exports.handler = function(context, event, callback) {
const { WebClient } = require('@slack/web-api');
const token = context.SLACK_BOT_TOKEN;
const client = new WebClient( token );

const channel = context.SLACK_CHANNEL;
async function handleMessage() {
// Post a message to the channel, and await the result.
const {To, From, Body} = event;
const [err, result] = await resolver(
client.chat.postMessage({
...blocks(To, From, Body),
channel: channel,
icon_emoji: ":iPhone:",
username: "Field Ops Support"
})
);

callback(err, null);
};
return handleMessage();
}

Make sure you save your function before moving on to anything else.

Moment of truth; let’s try it out, send a message to your Twilio number and have a look in your Slack channel. The message should come up almost instantly.

Screen capture of a text message sent from an Apple device. Contains a message requesting support.

Sent and…

Tadaa! Assuming all went well for you, you should now have a message in your Slack channel. If not, the bug icon in your Twilio console is probably flashing red, or you’ll get a response from Twilio. For me, I had to make sure my webhook was set and the bot was added to the channel.

There’s a whole lot more that you can do with this integration including providing response options in the message in Slack. I hope to have a follow-up article for you where we expand this integration a bit more so we can dive into the two-way communication opportunities we have available.

Thanks for following along! I hope I’ve given you enough to dive in and explore your options.

--

--