Frequently Asked Questions
You can send SMS messages by creating a NestJS service that uses the Twilio Node.js SDK. This service interacts with the Twilio API to send messages. A controller then uses this service, handling the request and formatting the message appropriately before sending it via the Twilio API. See provided code examples for implementation details.
Incoming messages are handled via webhooks. Set up a dedicated endpoint in your NestJS application. When a message arrives at your Twilio number, Twilio sends an HTTP POST request to this endpoint. Your application processes this request, which contains details like the sender, message body, and Twilio SID. See provided code examples for implementing webhooks.
Create a controller with a POST route designated to handle incoming webhooks. Use this route's public URL in your Twilio phone number configuration so Twilio knows where to send incoming message notifications. Use a service like ngrok
to create a public URL during local development and configure a more permanent URL such as your deployed site for production environments.
While the article utilizes PostgreSQL with Prisma, you could use other databases like MySQL, SQLite, and more that are compatible with Prisma. The code examples predominantly use PostgreSQL for storing conversation history and message details.
Validation is crucial for webhook security. Use the validateRequest
or validateRequestWithBody
function from the Twilio Node.js library. Provide the request signature, full URL, and request parameters. Ensure the URL matches what Twilio sent exactly. See provided code examples for correct validation implementation.
Prisma is a next-generation ORM for Node.js and TypeScript. It simplifies database access with type safety and auto-completion. Prisma handles database migrations, allowing seamless schema updates, and simplifies interactions within services throughout your codebase. The code demonstrates Prisma alongside PostgreSQL.
Create separate modules for Twilio integration, database interaction (Prisma), message sending (controller), and webhook handling (controller). The Twilio
module should contain the Twilio Client and message validation functions. The Messages
module houses API routes to trigger outbound SMS, while a separate webhooks
module handles incoming messages sent to your Twilio number.
Install the twilio
package. Create a Twilio
module and service to encapsulate the Twilio client instantiation and methods for sending messages, validating requests. Inject the ConfigService
from the @nestjs/config
module to get credentials from .env
. See code examples for implementation steps.
Webhook validation ensures that incoming requests genuinely originate from Twilio, preventing malicious actors from sending fake messages or accessing your application. Always validate incoming webhook requests using the Twilio library's validation function and a comparison between the signature Twilio sends with the body of the request.
Ngrok creates a temporary public URL that tunnels requests to your local development server. This lets you receive Twilio webhooks even though your NestJS app is running locally and not publicly available, crucial for the early phases of development and webhook configuration.
Use the Twilio Programmable Messaging API for sending and receiving SMS messages within your NestJS application. The API is integral to two-way SMS communication, covering functionalities like sending messages from your app and processing replies received via webhooks.
Yes, Prisma supports various databases including MySQL, SQLite, MongoDB, and more. While the provided code samples use PostgreSQL, you can adapt the database connection string and Prisma schema to your preferred database by modifying the provider
in your schema.prisma
file and updating the DATABASE_URL
in the .env
file.
Use a unique constraint in your database for the twilioSid
(Twilio's unique Message SID). If you try to store a message with an existing twilioSid
, Prisma will throw a unique constraint violation error. This ensures idempotency—processing the same webhook multiple times will not create duplicate entries. Catch this error to gracefully handle duplicate webhooks.
Store credentials (Account SID, Auth Token) securely in a .env
file. This file should be excluded from version control (.gitignore
). In your NestJS application, use the @nestjs/config
package to load environment variables from the .env
file. Never directly embed credentials into your code.
Build Two-Way SMS with Twilio, Node.js & NestJS: Complete Webhook Guide
Build a production-ready two-way SMS messaging system using NestJS and Twilio's Communications API. Master webhook handling, conversation state management, security validation, and database integration for interactive SMS applications.
Create applications that both initiate SMS conversations and intelligently handle incoming replies, linking them to ongoing interactions. Essential for customer support chats, notifications with expected responses, appointment confirmations, or any interactive SMS communication scenario.
Technologies Used:
System Architecture:
Here's a high-level overview of how the components interact:
Prerequisites:
ngrok
installed.Final Outcome:
By the end of this guide_ you will have a functional NestJS application capable of:
1. Setting up the Project
Let's scaffold our NestJS project and install the necessary dependencies.
1.1 Install NestJS CLI:
If you don't have it installed globally_ run:
1.2 Create New Project:
This creates a new project with a standard structure.
1.3 Install Dependencies:
We need packages for Twilio integration_ database interaction (Prisma)_ configuration management_ and validation.
@nestjs/config
: For managing environment variables.twilio
: The official Twilio Node.js SDK.prisma
_@prisma/client
: Prisma ORM CLI and client.joi
_@types/joi
: Optional schema description and data validation (NestJS also usesclass-validator
).class-validator
_class-transformer
: Used by NestJS for validation pipes with DTOs.@nestjs/throttler
: For rate limiting.1.4 Environment Variables:
Create a
.env
file in the project root for storing sensitive credentials and configuration. Never commit this file to version control. Add it to your.gitignore
file if it's not already there.API_BASE_URL
is important for Twilio webhook validation_ especially during local development withngrok
.1.5 Configure NestJS ConfigModule:
Import and configure the
ConfigModule
in your main application module (src/app.module.ts
) to load environment variables from the.env
file.1.6 Initialize Prisma:
Set up Prisma to manage our database schema and interactions.
This command does two things:
prisma
directory with aschema.prisma
file..env
file with aDATABASE_URL
placeholder (which we've already populated).Ensure your
prisma/schema.prisma
file correctly points to PostgreSQL and uses the environment variable:2. Implementing the Twilio Module
This module will encapsulate the Twilio client setup and provide a service for sending messages and validating webhooks.
Update the service to initialize the Twilio client and handle validation.
Make sure the
TwilioService
is provided and exported byTwilioModule
.Remember to import
TwilioModule
intoAppModule
(as shown in section 1.5).3. Creating a Database Schema and Data Layer (Prisma)
We need to define our database models using Prisma to store conversations and messages.
3.1 Prisma Module:
It's good practice to encapsulate Prisma client instantiation in its own module.
Configure the
PrismaService
to connect and disconnect gracefully.Configure the
PrismaModule
. Making it global simplifies dependency injection.Remember to import
PrismaModule
intoAppModule
(as shown in section 1.5). Also, ensureenableShutdownHooks
is called in yourmain.ts
:3.2 Define Prisma Schema:
Update
prisma/schema.prisma
with models forConversation
andMessage
. We'll link messages to conversations based on the participant's phone number (stored in E.164 format).participantNumber
should be unique and ideally stored in E.164 format.Conversation
.direction
indicates if it was sent by the application (OUTGOING
) or to the application (INCOMING
).twilioSid
is crucial for tracking and preventing duplicates.onDelete: Cascade
ensures messages are deleted if their parent conversation is deleted.3.3 Run Database Migration:
Apply the schema changes to your database during development. Prisma will generate the SQL and execute it.
This command:
prisma/migrations/
.@prisma/client
) based on the new schema.3.4 Data Access (Example within Services):
You'll inject the
PrismaService
into other services (like theMessagesController
orTwilioWebhookController
) to interact with the database.4. Building the API Layer for Sending Messages
Let's create an API endpoint to initiate outbound messages.
4.1 Create Messages Module, Controller, and DTO:
Define a DTO (Data Transfer Object) for validating the request body using
class-validator
.4.2 Implement Message Sending Endpoint:
The controller uses
TwilioService
to send the SMS andPrismaService
to record it.Configure the
MessagesModule
:Remember to import
MessagesModule
intoAppModule
(as shown in section 1.5).4.3 Testing the Sending Endpoint:
Use
curl
or Postman:Replace
+15559876543
with a real test number in E.164 format. You should receive the SMS on the test number, and see logs in your NestJS console. A record should appear in yourConversation
andMessage
tables.5. Handling Incoming Messages (Webhook)
This is the core of two-way messaging. We need an endpoint that Twilio can call when it receives an SMS directed at your Twilio number.
5.1 Create Webhook Module and Controller:
5.2 Implement Webhook Endpoint:
This endpoint receives POST requests from Twilio (typically
application/x-www-form-urlencoded
). It must:TwilioService.validateWebhookRequest
).From
), recipient (To
), message body (Body
), andMessageSid
.From
number (ensure it's normalized).Configure the
WebhooksModule
:Remember to import
WebhooksModule
intoAppModule
(as shown in section 1.5).5.3 Configure Twilio Webhook URL:
https://<your-ngrok-subdomain>.ngrok.io/webhooks/twilio/sms
https://your-production-domain.com/webhooks/twilio/sms
HTTP POST
.5.4 Local Testing with ngrok:
If running locally:
npm run start:dev
(usually runs on port 3000).ngrok http 3000
.https
URL (e.g.,https://random123.ngrok.io
)..env
file'sAPI_BASE_URL
to this ngrok URL (e.g.,API_BASE_URL=https://random123.ngrok.io
). Restart your NestJS app for the change to take effect.https://random123.ngrok.io/webhooks/twilio/sms
) in the Twilio Console configuration.Now, when you send an SMS to your Twilio number, Twilio will forward it to your local NestJS application via ngrok. Check your NestJS logs and database.
6. Error Handling and Logging
ForbiddenException
,BadRequestException
, andInternalServerErrorException
. Custom exceptions can be created for more specific error scenarios.Enhanced Error Handling Strategy:
@nestjs/throttler
to prevent abuse (configured in dependencies)Webhook Response Time Requirements:
2xx
response within 10 seconds (industry standard: 5-10 seconds)200 OK
immediately and process asynchronously to meet timing requirementsTwilio Rate Limits (2024-2025 Verified):
Logging Best Practices:
Frequently Asked Questions About Twilio Two-Way SMS with NestJS
How do I validate Twilio webhook signatures?
Twilio computes an HMAC-SHA1 signature using your Auth Token and includes it in the
X-Twilio-Signature
header. Validate webhooks by: (1) retrieving the signature from the header, (2) reconstructing it using the full webhook URL (including query parameters), POST parameters, and your Auth Token, (3) comparing computed signature with received signature. Use Twilio SDK'svalidateRequest()
method (shown in this guide's Section 5.2) for automatic validation. Always validate in production to prevent unauthorized webhook calls.What is the Twilio SMS rate limit?
Twilio's default SMS rate limit is 1 message segment per second (MPS) for long codes in US/Canada (verified 2024-2025). Rate limits vary by phone number type (toll-free, short codes have higher limits). When exceeding your account's API concurrency limit, Twilio returns HTTP 429 (Error 20429). Implement exponential backoff retries and consider upgrading to toll-free numbers or short codes for higher throughput. Twilio also enforces a 30-message limit between two phone numbers within 30 seconds to prevent infinite loops.
How much does Twilio SMS cost?
Twilio SMS pricing varies by destination country. As of 2024-2025, typical US/Canada rates: $0.0079-0.01 per message segment (outbound), inbound messages typically free or minimal cost. International rates vary significantly (UK: ~$0.04/msg, India: ~$0.006/msg). Phone number rental: $1-2/month for local numbers, $2-3/month for toll-free. Check Twilio's pricing page for current rates. Volume discounts available for >100k messages/month.
Can I use Twilio with NestJS in production?
Yes, Twilio is enterprise-grade and production-ready with NestJS. Requirements: (1) stable HTTPS webhook URL with SSL certificate, (2) webhook signature validation enabled (shown in Section 5.2), (3) proper error handling and retry logic, (4) database for conversation state management, (5) rate limiting implementation (@nestjs/throttler), (6) monitoring and alerting, (7) compliance with regulations (TCPA for US, GDPR for EU). This guide provides production patterns for all requirements.
How do I test Twilio webhooks locally?
Test locally using ngrok: (1) start NestJS app (
npm run start:dev
on port 3000), (2) runngrok http 3000
to expose localhost, (3) copy ngrok HTTPS URL (e.g.,https://abc123.ngrok.io
), (4) configure in Twilio Console under Phone Numbers → Messaging → Webhook URL, (5) send test SMS to your Twilio number, (6) monitor NestJS logs and ngrok web interface (http://127.0.0.1:4040). Alternative: Use Twilio CLI withtwilio phone-numbers:update
or mock webhook requests with cURL/Postman.What Node.js version does Twilio SDK require?
Twilio Node.js SDK v4+ (released January 2024) supports Node.js 18.x, 20.x, and 22.x (verified Q4 2024). TypeScript support requires v2.9+. NestJS 10.x works seamlessly with these versions. Twilio Functions will default to Node.js v22 after June 1, 2025 (full migration by November 10, 2025). Ensure NPM dependencies are compatible with Node.js v22 before upgrading. This guide's code works with all supported Node.js LTS versions.
How do I track conversation state in two-way SMS?
Track conversation state using: (1) database table storing messages with
conversationId
linking related messages, (2) phone number as conversation identifier (create/retrieve conversation byfromNumber
), (3)status
field tracking conversation state (ACTIVE, CLOSED, AWAITING_REPLY), (4)lastMessageAt
timestamp for timeout handling, (5)metadata
JSONB field for custom context. This guide demonstrates full implementation using Prisma ORM with PostgreSQL (Section 3). Consider Redis for ephemeral state (5-15 minute expiry) or database for persistent history.How do I handle message delivery failures?
Handle delivery failures by: (1) implementing retry logic with exponential backoff (1s, 2s, 4s delays), (2) catching Twilio SDK errors and checking status codes (21211: invalid number, 21610: unsubscribed/blocked), (3) storing failed messages in database with
status: FAILED
anderrorCode
, (4) implementing dead-letter queue for messages failing after max retries (3-5 attempts), (5) alerting for failure rate thresholds (>5%), (6) distinguishing permanent failures (invalid numbers) from transient (network errors, rate limits). Log full Twilio error responses for debugging.Can I send MMS (multimedia messages) with Twilio and NestJS?
Yes, Twilio supports MMS in supported countries (US, Canada, UK). Send MMS using
mediaUrl
parameter in message body:await client.messages.create({ to, from, body, mediaUrl: ['https://example.com/image.jpg'] })
. Requirements: HTTPS URLs for media, max 10 media files per message, supported formats (JPEG, PNG, GIF, MP4), max file size 5MB. MMS costs 3-5× standard SMS rates. Not all recipients support MMS (fallback to SMS automatically). Update DTO to include optionalmediaUrl
array field.How do I secure Twilio webhooks in production?
Secure webhooks by: (1) always validating X-Twilio-Signature header (shown in Section 5.2), (2) using HTTPS only (enforced by Twilio), (3) implementing rate limiting on webhook endpoint (@nestjs/throttler: 100 requests/minute), (4) whitelisting Twilio IP ranges (optional, check Twilio docs for current IPs), (5) using environment variables for Auth Token (never commit to git), (6) logging all webhook requests for audit trails, (7) monitoring for unusual patterns (sudden spikes, malformed requests), (8) implementing CORS properly if exposing APIs. Never skip signature validation in production.
What is the difference between Twilio and Sinch?
Twilio provides webhook signature validation (X-Twilio-Signature header with HMAC-SHA1), broader geographic coverage, extensive documentation, and 1 MPS default rate for long codes. Sinch has no native webhook signature validation (requires custom implementation), offers 30 msg/s rate limit (per project), and competitive enterprise pricing. Both support Node.js SDKs, two-way messaging, and production deployments. Choose Twilio for robust security features and better documentation; choose Sinch for higher default throughput and enterprise features. This guide focuses on Twilio but patterns apply to both providers.
How do I deploy Twilio NestJS app to production?
Deploy to production: (1) build application (
npm run build
), (2) set environment variables on hosting platform (Heroku, AWS, DigitalOcean, Railway), (3) use process manager (PM2:pm2 start dist/main.js -i max
for clustering), (4) configure reverse proxy (NGINX/Caddy) with SSL certificates (Let's Encrypt), (5) update Twilio webhook URL to production domain, (6) implement health checks for monitoring, (7) set up logging to external service (CloudWatch, Datadog), (8) configure auto-scaling based on CPU/memory, (9) use managed PostgreSQL (AWS RDS, DigitalOcean), (10) implement CI/CD pipeline (GitHub Actions, GitLab CI). Monitor webhook latency and Twilio API errors.Conclusion
Build production-ready two-way SMS messaging applications using Twilio, Node.js, and NestJS. This implementation provides:
Action Steps:
Master Twilio's 1 MPS rate limit, 10-second webhook timeout, and signature validation requirements for reliable production deployments.