Frequently Asked Questions
Track Twilio SMS delivery status using status callback webhooks. Set up a webhook endpoint in your NestJS application that receives real-time updates from Twilio on the message status (e.g., sent, delivered, failed). This allows for immediate feedback and automated actions based on the delivery outcome.
A Twilio status callback webhook is a URL you provide to Twilio where Twilio sends real-time updates about the status of your messages. Each time a message's status changes, Twilio makes an HTTP POST request to your specified URL with details like the message SID and status.
Reliable SMS delivery tracking is essential for various applications like two-factor authentication, notifications, and customer communication workflows. It ensures you know whether messages reach recipients, allowing you to take appropriate action if a message fails, like resending via another channel.
Use a status callback for Twilio whenever you need to confirm message delivery or respond to delivery failures. This is crucial for time-sensitive information, essential notifications, and any situation where confirming successful receipt is vital.
Yes, ngrok is recommended for local Twilio webhook development. Ngrok provides a public URL that tunnels requests to your locally running application, allowing Twilio to reach your webhook endpoint even during development before deployment.
The "fire-and-forget" SMS approach refers to sending messages without actively tracking their delivery status. This guide improves upon this method by implementing a system to monitor delivery and respond accordingly, ensuring reliability.
Set up a NestJS project for Twilio SMS by installing the necessary packages such as twilio, dotenv, @nestjs/config, class-validator, and class-transformer. Initialize a Twilio client using your Account SID and Auth Token from the Twilio Console.
This guide uses Node.js, NestJS, the Twilio Node.js helper library, TypeScript, and dotenv. Optionally, it incorporates TypeORM and PostgreSQL for persistent storage of message logs, and ngrok for local development.
Twilio webhook requests need to be validated using the X-Twilio-Signature header. This header contains a cryptographic signature that ensures the request originates from Twilio. This security measure will be implemented later in the guide. Until then, the endpoint is not secure if exposed publicly.
TypeORM and PostgreSQL are optionally used for persistent storage of message logs and their delivery statuses. This facilitates analysis and allows building dashboards or automated actions based on historical data.
Handle Twilio SMS delivery failures by logging detailed error information received in status callbacks. Implement retry mechanisms with caution to avoid excessive calls to the Twilio API and potential additional costs.
Prerequisites include Node.js v16 or later, a Twilio account (free tier suffices), a Twilio phone number with SMS capabilities, basic TypeScript and REST API knowledge, and optionally PostgreSQL and ngrok.
NestJS is the underlying Node.js framework used to build the application due to its structured approach, dependency injection, and built-in features like decorators, creating a maintainable and scalable architecture.
A Twilio status callback provides crucial information such as MessageSid, MessageStatus (e.g. 'sent', 'delivered', 'failed'), ErrorCode (if applicable), and ErrorMessage, which gives you detailed insight into message delivery outcomes.
Your Twilio Account SID and Auth Token can be found in the Twilio Console Dashboard. These credentials are essential for initializing the Twilio client and making API calls.
How to Track Twilio SMS Delivery Status with NestJS: Complete Webhook Guide
Meta Description: Build production-ready SMS delivery tracking with Twilio webhooks and NestJS. Complete guide with status callbacks, signature validation, and database integration.
This guide provides a complete walkthrough for building a production-ready system using NestJS and Node.js to send SMS messages via Twilio and reliably track their delivery status using Twilio's status callback webhooks. You'll learn how to implement SMS delivery tracking, handle webhook callbacks, validate Twilio signatures, and store message status updates in a database. You'll cover everything from initial project setup to deployment and verification, ensuring you have a robust SMS delivery monitoring solution.
You'll focus on creating a system that not only sends messages but also listens for status updates (like
queued,sent,delivered,failed,undelivered) from Twilio, enabling features like real-time delivery confirmation, logging for auditing, and triggering subsequent actions based on message status.What You'll Build: SMS Delivery Tracking System
Project Components:
A NestJS application with the following capabilities:
Problem Solved:
Standard SMS sending often operates on a "fire-and-forget" basis. This guide addresses the need for reliable delivery confirmation. Knowing if and when a message reaches the recipient is crucial for many applications, including notifications, alerts, two-factor authentication, and customer communication workflows. Tracking delivery status provides visibility and enables automated responses to delivery failures.
Technologies Used:
System Architecture:
Prerequisites:
Final Outcome:
By the end of this guide, you'll have a fully functional NestJS application capable of sending SMS messages and reliably tracking their delivery status via Twilio webhooks, including optional database persistence and essential security measures.
How to Set Up Your NestJS Project for Twilio Integration
Initialize your NestJS project and set up the basic structure and dependencies for SMS delivery tracking.
Step 1: Create a new NestJS project
Open your terminal and run the NestJS CLI command:
Step 2: Install necessary dependencies
Install the Twilio helper library (v5.x), configuration management, validation pipes, and optionally TypeORM v0.3.27 for database interaction.
Step 3: Configure Environment Variables
Create a
.envfile in the project root. Never commit this file to version control. Add a.gitignoreentry for.env.Create a
.env.examplefile to track necessary variables:Step 4: Load Environment Variables using ConfigModule
Modify
src/app.module.tsto load and validate environment variables globally.Step 5: Enable Validation Pipe Globally
Modify
src/main.tsto automatically validate incoming request payloads usingclass-validator.Project Structure Explanation:
src/: Contains your application's source code.src/main.ts: The application entry point, bootstrapping the NestJS app.src/app.module.ts: The root module, organizing the application structure.src/app.controller.ts/src/app.service.ts: Default controller and service (can be removed or repurposed)..env: Stores sensitive configuration and credentials (ignored by Git).nest-cli.json: NestJS CLI configuration.tsconfig.json: TypeScript compiler options.At this point, you have a basic NestJS project configured to load environment variables and validate incoming requests.
How to Implement SMS Sending with Twilio Service
Build the services and controllers for sending SMS messages and handling delivery status callbacks.
Step 1: Create a Twilio Module and Service
This encapsulates Twilio client initialization and interaction logic.
Step 2: Configure the Twilio Service
Inject
ConfigServiceto access credentials and initialize the Twilio client.Step 3: Register the Twilio Module
Make the
TwilioServiceavailable for injection. AddTwilioModuleto theimportsarray insrc/app.module.ts.You now have a dedicated service to handle Twilio interactions.
How to Create API Endpoints for SMS and Webhook Callbacks
Create endpoints to trigger sending SMS and to receive the status callbacks from Twilio.
Step 1: Create an SMS Module and Controller
This module handles the API endpoint for sending messages.
Step 2: Define Data Transfer Object (DTO) for Sending SMS
Create a DTO to define the expected request body structure and apply validation rules.
Step 3: Implement the SMS Sending Endpoint
Inject
TwilioServiceintoSmsControllerand create a POST endpoint.Step 4: Register the SMS Module
Add
SmsModuletosrc/app.module.ts.Step 5: Create the Twilio Callback Controller
This controller houses the endpoint that Twilio calls with status updates.
Modify the generated
src/twilio/twilio.controller.ts:Update
src/twilio/twilio.module.tsto include the controller:Testing API Endpoints:
Sending SMS (
POST /sms/send):Use
curlor Postman:(Replace
+15559876543with a real number verified on your Twilio trial account)Expected Response (Status: 202 Accepted):
Receiving Callbacks (
POST /twilio/status): This endpoint is called by Twilio. To test locally:ngrok http 3000(replace 3000 if your app uses a different port).https://URL provided (e.g.,https://abcd-1234.ngrok-free.app)..env: SetAPP_BASE_URLto your ngrok URL.APP_BASE_URL./sms/sendendpoint again.TwilioControllerlogging the incoming callback data (MessageSid,MessageStatus, etc.) as Twilio updates the status.http://localhost:4040) shows incoming requests to your/twilio/statusendpoint.Expected Callback Body (Example for
deliveredstatus):Expected Callback Body (Example for
failedstatus):How to Configure Twilio Webhooks for Status Updates
You've already integrated the
twiliolibrary. This section summarizes the key configuration points for SMS delivery tracking.TWILIO_ACCOUNT_SIDandTWILIO_AUTH_TOKENare sourced from.envviaConfigServiceand used to initialize the Twilio client inTwilioService. Obtain these from your Twilio Console Dashboard.TWILIO_PHONE_NUMBERis sourced from.envand used as thefromnumber when sending SMS. Ensure this number is SMS-capable and matches the one in your Twilio account.statusCallbackURL is dynamically constructed inTwilioServiceusingAPP_BASE_URLfrom.envand the fixed path/twilio/status. This URL is passed in theclient.messages.createcall.statusCallbackduring the API call provides flexibility, allowing different message types or workflows to potentially use different callback handlers if needed, though you use a single one here.statusCallbackparameter in the API call overrides any console settings for outgoing message status updates. Rely solely on the API parameter here.statusCallbackEventparameter with an array like['queued', 'sent', 'failed', 'delivered', 'undelivered']to receive callbacks for intermediate states. Note: Status callbacks may arrive out of order due to network conditions and processing delays.TWILIO_ACCOUNT_SID: Your main account identifier. Found on the Twilio Console dashboard. Format:ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.TWILIO_AUTH_TOKEN: Your secret API key used for both authentication and webhook signature validation (HMAC-SHA1). Found on the Twilio Console dashboard. Keep this secret. Format: Alphanumeric string.TWILIO_PHONE_NUMBER: The E.164 formatted Twilio number you're sending from. Format:+15551234567.APP_BASE_URL: The public base URL where your application is accessible by Twilio. During development, this is your ngrok HTTPS URL. In production, it's your deployed application's domain. Format:https://your-domain.comorhttps://sub.ngrok-free.app.How to Handle Errors and Implement Retry Logic
Robust applications need proper error handling and logging for SMS delivery monitoring.
Error Handling Strategy:
ValidationPipeinmain.ts. Invalid requests return 400 Bad Request automatically.TwilioService(sendSmsmethod). Logged with details. Currently re-thrown as genericError, which NestJS maps to 500 Internal Server Error. Consider mapping specific Twilio error codes (e.g., authentication failure → 401/403, invalid number → 400) using custom exception filters for more precise client feedback.handleStatusCallbackshould be logged. Crucially, still respond 2xx to Twilio to acknowledge receipt, preventing Twilio from retrying the callback unnecessarily for your internal processing errors. Handle internal failures separately (e.g., dead-letter queue, alerts).ErrorCodefield only appears in callback events for failed or undelivered messages. Ensure your callback handler checks for this field but doesn't require it, as it will be absent for successful deliveries.Logging:
Logger.MessageSidin logs related to callbacks.logfor general info,warnfor potential issues,errorfor failures,debugfor verbose development info. Control log levels based on environment (e.g.,debugin dev,logorinfoin prod). Configure NestJS logger levels during app bootstrap.Retry Mechanisms:
client.messages.createcall fails due to network issues or temporary Twilio problems, you might implement retries withinTwilioService. Libraries likenestjs-retryor simple loop/delay logic can be used. Implement with caution (e.g., limit retries, use exponential backoff) to avoid excessive calls or costs. For this guide, keep it simple and re-throw the error.Testing Error Scenarios:
/sms/sendwith missing/invalidtoorbodyfields to testValidationPipe.TWILIO_AUTH_TOKENin.envand try sending SMS. Expect a 500 error from/sms/sendand corresponding logs inTwilioService.+15005550001for invalid number error, or+15005550004for SMS queue full, see Twilio Test Credentials) to triggerfailedstatus callbacks.throw new Error('Test DB Error');) insidehandleStatusCallbackbefore the response is sent (once DB logic is added). Observe the logs. Twilio should retry the callback (visible in ngrok/server logs). Ensure you still log the incoming callback data./twilio/statuswithout a validX-Twilio-Signatureheader. Expect a 403 Forbidden response.How to Store SMS Status in PostgreSQL Database
Storing message status provides persistence and enables analysis or UI updates. Use TypeORM v0.3.27 with PostgreSQL.
Step 1: Install DB Dependencies (if not done)
Important Note: TypeORM doesn't follow semantic versioning. Upgrading from 0.3.26 to 0.3.27 may include breaking changes. Always test upgrades in a non-production environment first.
Step 2: Configure TypeORM (Initial Setup)
Update
src/app.module.tsto configure the database connection usingConfigService. At this stage, don't add the specific entity yet.Step 3: Create MessageLog Entity and Module
Define the structure of your database table.
Create the entity file: