Back to Blog
Integration Guides

How to Send SMS with Twilio and Express.js: Step-by-Step Tutorial

Build a Twilio SMS endpoint in Express.js with proper route handling, credential management via dotenv, and webhook verification middleware.

DA
Danial A
Senior Twilio Consultant, Telphi Consulting
June 22, 2026
7 min read
Twilio
Integration
SDK
Mobile
How to Send SMS with Twilio and Express.js: Step-by-Step Tutorial

Twilio SMS and Express.js integrate directly through the Twilio Node.js helper library used inside Express route handlers, making Express the natural backend for serving SMS dispatch endpoints to frontend applications, mobile apps, or third-party services that need SMS capability without building a full framework. This integration is used by Node.js developers building REST APIs, backend-for-frontend services, or webhook receivers that need to dispatch or receive Twilio SMS with minimal configuration, and who want the control of an unopinionated Express setup rather than a full framework's conventions. The Twilio client is initialized once at module level with credentials from dotenv, a POST route dispatches SMS and returns the message SID, a separate POST route receives Twilio status callbacks and webhook events, and middleware validates the Twilio request signature to reject spoofed callbacks.

What You Need Before You Start

Initialize a Node.js project with npm init -y and install dependencies by running npm install express twilio dotenv. Create a .env file at the project root with TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER, and PORT set to 3000. Add .env to .gitignore before the first commit to prevent credential exposure. Collect your Twilio Account SID, Auth Token, and a provisioned SMS-capable phone number from the Twilio Console. Create an index.js entry point that calls require('dotenv').config() as the very first statement before any other require calls so environment variables are loaded before the Express app and Twilio client are constructed.

Step-by-Step Integration Guide

In index.js, after loading dotenv, require express, twilio, and create the Twilio client with const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN). Initialize the Express app, add app.use(express.json()) for JSON body parsing, and add app.use(express.urlencoded({ extended: false })) for URL-encoded body parsing needed for Twilio webhook callbacks. Define a POST /send-sms route that reads to and body from req.body, validates both are present with a 400 response if missing, calls await client.messages.create({ to, from: process.env.TWILIO_FROM_NUMBER, body }) inside a try block, and responds with JSON containing the message sid on success. In the catch block, log the error and respond with HTTP 500 and JSON containing the error code and message from the TwilioRestException. Define a POST /webhook/status route for delivery status callbacks that reads MessageSid and MessageStatus from req.body, updates your database or logs the status, and responds with HTTP 200 immediately. Start the server with app.listen(process.env.PORT).

Common Issues and How to Fix Them

The Twilio client constructor call throws 'accountSid must start with AC' when process.env.TWILIO_ACCOUNT_SID is undefined because dotenv.config() was not called before the twilio() constructor on module load, or the .env file is in the wrong directory. Ensure require('dotenv').config() is the first statement in index.js before any other require, verify the .env file is in the same directory from which you run node index.js, and add a startup assertion that throws if process.env.TWILIO_ACCOUNT_SID is undefined. The Express server receives Twilio status callback POSTs but req.body is empty or undefined because the route is defined before app.use(express.urlencoded({ extended: false })), and Twilio posts status callbacks as application/x-www-form-urlencoded rather than JSON. Move the app.use middleware registrations above all route definitions so body parsing middleware runs before route handlers. The /send-sms route is accessible from the internet without authentication, allowing anyone to trigger SMS sends billed to your Twilio account. Add API key authentication middleware that checks an Authorization header against a secret stored in an environment variable, returning HTTP 401 for missing or invalid keys, so only authorized callers can trigger SMS dispatch.

How to Get More from This Integration

Add Twilio webhook signature validation middleware by creating a validateTwilioSignature middleware function that reads the X-Twilio-Signature header and calls twilio.validateRequest(process.env.TWILIO_AUTH_TOKEN, signature, fullUrl, req.body) returning false when the signature is invalid, and applying it to all inbound Twilio webhook routes with app.post('/webhook/sms', validateTwilioSignature, smsHandler) to block spoofed requests. Build a rate limiting middleware using the express-rate-limit package by configuring a limiter with windowMs of 60000 and max of 10 that returns a 429 response with a Retry-After header when a single IP exceeds 10 SMS dispatch requests per minute, protecting your Twilio account from abuse on the send endpoint. Implement idempotency on the /send-sms route by accepting an idempotencyKey field in the request body, storing each processed key with its result SID in a Map or Redis cache with a 24-hour TTL, returning the cached SID immediately for duplicate requests, and only calling the Twilio API for keys not yet seen, preventing duplicate SMS when clients retry failed requests.

Conclusion

Express.js and Twilio together provide a lightweight, flexible SMS dispatch and webhook handling backend that can be deployed as a standalone API or embedded in a larger Node.js service. Contact Telphi Consulting to build and harden the Twilio SMS backend for your Express.js application.

Share this article:
0 views

Ready to Transform Your Business Communications?

Get a free consultation with our VoIP experts and discover how we can help you save costs, improve efficiency, and scale your business.

Comments (0)

Join the discussion and share your thoughts (AI-moderated for quality)

Protected by AI moderation

Be the first to comment

No comments yet. Share your thoughts below.