How to receive and reply to SMS messages with TypeScript and Twilio
Time to read: 6 minutes
We've looked at how to send an SMS using TypeScript, but with Twilio you can also receive and reply to incoming SMS messages.
When you send an SMS message to your Twilio phone number, Twilio will send a webhook, an HTTP request with all the details about the message, to a URL you associate with that number. You can reply to the message by responding to the webhook with TwiML (Twilio Markup Language).
In this post we will build a Node.js application with TypeScript, using Express and the Twilio Node package to reply to incoming SMS messages.
What you need
To follow this tutorial you will need:
- A Twilio account (if you don't have one yet, sign up for a free Twilio account here and receive $10 credit when you upgrade)
- A Twilio phone number that can send and receive SMS messages
- Node.js installed
- ngrok so that you can respond to webhooks in your local development environment
Once you've prepared those bits, let's get started building the project.
A new TypeScript and Express project
Follow these instructions to get a new TypeScript and Express project off the ground. Start by creating a new directory and then initialise it with a package.json
file:
Next, install the dependencies we're going to need. In our application dependencies, install Express, which will be used to receive and parse the incoming webhooks, and the Twilio package. For development dependencies, install TypeScript and the Definitely Typed types for Express.
With those installed, use the TypeScript command tsc
to initialise a tsconfig.json
file for us.
Open tsconfig.json
and uncomment "outDir", updating it like so:
This will compile the TypeScript to the dist
directory in our project.
Open up package.json
, so that we can make some updates here. First, change the "main" property to point to dist/index.js
. Then add the following to the "scripts" property, to compile and run the application:
tsc
is the TypeScript compiler, the "build" script will run compilation once and the "watch" script will keep recompiling as you make changes. The "start" script will run our application.
Create some files that we'll use in this application. We'll need index.ts
, server.ts
, and config.ts
for now.
Open server.ts
and create a test Express application to make sure things are going as planned.
This code requires Express, creates an application and sets up the root path to respond with "Hello world!". It then exports the app so we can use it elsewhere.
Open up config.ts
. We don't need a lot of configuration in this application, but I like to keep it together in one file so that it can be loaded when it is needed. For this application, we are going to make the port the web server starts on part of the configuration. Add this to config.ts
:
Open index.ts
, import the app and the port from the config and then use them to make the app listen on the supplied port:
Compile the application and run it.
Open http://localhost:3000 in your browser. You should see the text "Hello world!". Our TypeScript Express server is up and running successfully.
From now, you might find it easier to run the watch
command so that the project continues to compile as you make changes. Stop the server and run:
Responding to Twilio webhooks
Now our base application is working, let's add a route for the webhook. Create a new directory for routes and a file for the messages route:
Open routes/messages.ts
. In this file we will create a route that will respond to an incoming webhook request from Twilio. Webhook requests from Twilio are POST
requests in the format application/x-www-form-urlencoded
by default, so we will use Express's urlencoded
middleware to parse the data in the body of the request. To have Twilio respond to the message we need to return TwiML, which is a subset of XML, with instructions to send a message back. We'll use the Twilio package to generate the TwiML.
Start by importing the various elements that we need:
Next, create a router object and set up the urlencoded
middleware to parse the body:
Destructure the TwiML MessagingResponse
from the twiml
object.
Now we need to define the route. For this application we'll echo the message you send it back to you. We'll get the message from the request body, generate TwiML using the MessagingResponse
class, set the response content type to "application/xml" and return the response. Finally, we export the router.
Open server.ts
again and import the messaging router, then mount the router at the "/messages" path. You can also remove the initial "Hello world!" route.
You should get a response that looks like this:
This is looking good, so let's get the application connected to your Twilio phone number.
Connecting a Twilio number to your application
To connect a Twilio number to the app we've built we need to be able to access the application from a public URL. That's where ngrok comes in, it creates a temporary URL that tunnels through to your local machine. Our application is running on localhost port 3000, so we can open a tunnel to it with the command:
Once the tunnel has connected, ngrok displays the randomly generated URL that now points at your application. It should look like https://RANDOM_STRING.ngrok.io
. Open up your Twilio console to your incoming numbers, choose the number you want to use for this app, or buy a new one. Edit the number and add your ngrok URL, plus the /messages
path, as the webhook for when a message comes in.
If you have the Twilio CLI installed you can do this on the command line too, with the command:
Here's a bonus tip, if you have the Twilio CLI, you don't even need to start up ngrok yourself. Instead you can run:
The CLI will detect the localhost URL and set up the ngrok tunnel for you, keeping it alive until you quit the command.
Now you have connected the phone number, send it an SMS message from your own phone and you will receive a reply. You've received and replied to an SMS message with TypeScript!
Do we have enough types?
One thing I find myself asking when writing applications in TypeScript is whether I am taking advantage of TypeScript enough. In this case, I think there is more we can do to give us the confidence when we come to change the system later.
Open routes/messages.ts
again. The request and response objects that are passed into the route handler are as generic as they can be to be able to handle any application's input and output.
For example, we know we want the handler to respond with a string, our TwiML. We can encode this in the types of the handler. Start by importing the Response
object from Express:
Now, in the route definition we can encode that the response will always be a string, like so:
Try passing anything that isn't a string to res.send()
now. You will find that the compilation fails and, if your editor includes TypeScript checking, it will be highlighted.
Change that back to res.send(response.toString());
and everything will work again. That's one less mistake we can make.
But that's not all. We've addressed the response, but not the request. It bugs me that calling on req.body.Body
is typed as any
.
Can we do better than this? Well, we know what the shape of the request will look like; it's a webhook with parameters defined in the documentation. We can use this to define a request type that will set the type of the body of the request.
Create a new directory called types
and a file called request.ts
.
Open types/request.ts
, here we will define our new request type. First we need to define the type for the body itself. This is made up of the webhook parameters.
There are more parameters, I will leave adding the ones you need as an exercise.
The Express Request object is defined as:
For the purposes of this post, we aren't interested in most of that, but we can see that the ReqBody
is defined as any
. We can create a new type alias of this Request
and insert our MessagingWebhookBody
type as the ReqBody
. We need to import the Request
interface from "express" and the other "core" types from "express-serve-static-core".
Then we can define, and export, our MessagingRequest
like so:
Now we need to import and use this type in our messages route. Open routes/messages.ts
and import the request type:
Add the type to the route handler signature:
Now check out the types on req.body
. It's a MessagingWebhookBody
now and req.body.Body
is a string
.
We haven't changed the functionality of this route handler, but we have defined the types for the pieces that we are using and can be confident when using them in the future.
If you need to be sure everything still works, send your number another celebratory message!
Onward with TypeScript
In this post you've seen how to use TypeScript to build an Express server that can receive and respond to incoming SMS messages. You can check out the full code to this application in this GitHub repo of Twilio and TypeScript examples.
Now you can build upon this work to create more with Twilio, like sending messages using the REST API or building an SMS weather bot. You don't even need to build a server with Express as you can go serverless and write Twilio Functions with TypeScript.
Are you building something cool using TypeScript and Twilio? I would love to hear about it. Drop me an email at philnash@twilio.com or send me a message onTwitter at @philnash.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.