Frequently Asked Questions
Build a Next.js app with an API endpoint that uses Twilio's Messaging Service. This endpoint accepts a list of phone numbers and a message, then sends the message to all recipients. This approach handles rate limiting and other bulk messaging challenges effectively.
A Twilio Messaging Service is a tool for managing sender numbers, ensuring compliance (like opt-out handling), and optimizing message delivery at scale. It's essential for robust bulk SMS applications.
Messaging Services simplify sending bulk SMS by handling sender rotation, opt-out management, compliance (like A2P 10DLC), and queuing, which prevents rate limiting issues and ensures deliverability.
Use a Twilio Messaging Service whenever you need to send the same SMS message to multiple recipients. This is essential for broadcasts, notifications, and other bulk messaging scenarios to ensure scalability and compliance.
Yes, you can use a local PostgreSQL database during development. The provided Prisma schema allows for database flexibility, but you'll need the correct connection string in your .env file.
Create a Messaging Service in the Twilio console, add your sender phone numbers, configure opt-out handling, and obtain the Messaging Service SID. Ensure compliance requirements, like A2P 10DLC, are met, especially for US numbers.
A2P 10DLC is a registration process required for sending application-to-person (A2P) messages using 10-digit long codes (10DLC) in the US. It's mandatory for compliance and ensures message deliverability.
Create an API route in Next.js to receive status updates from Twilio. Validate the webhook request using Twilio's helper library for security, then process and log the message status in your database using Prisma.
Twilio webhooks provide real-time status updates about your messages, such as queued, sent, delivered, failed, or undelivered. They are the only way to reliably track final message delivery.
Push your Next.js project to a Git repository and import it into Vercel. Configure the same environment variables as your local .env file in Vercel's project settings and deploy.
Prisma is used as an Object-Relational Mapper (ORM) to interact with the PostgreSQL database for logging broadcasts and individual message statuses, providing a structured way to manage data.
Zod is used for schema validation. It helps ensure the data received by the /api/broadcast endpoint matches the expected format, preventing errors caused by bad input.
The API endpoint is secured using an API secret key in the Authorization header. However, for improved security in a production environment, consider implementing more robust authentication and authorization methods.
Twilio Messaging Services inherently handle some rate limiting, but for additional control, consider implementing rate limiting at the application level in your Next.js API route, using libraries like upstash/ratelimit.
Potential improvements include adding a user interface, managing recipients in the database, implementing background jobs for very large lists, enhancing error reporting, and adding more advanced features like personalized messages.
Sending SMS messages one by one works for small volumes, but you need a robust architecture when scaling to hundreds or thousands of messages. This guide shows you how to build a production-ready bulk SMS broadcasting system using Next.js for the application layer and Twilio Programmable Messaging for reliable delivery at scale.
We'll build a Next.js application with an API endpoint that accepts a list of phone numbers and a message body, then efficiently sends that message to all recipients via Twilio's Messaging Services. This approach solves the challenges of rate limiting, sender number management, and compliance handling inherent in bulk messaging.
Project Overview and Goals
What We're Building:
/api/broadcast
) to initiate bulk SMS sends.Broadcast
,MessageStatus
). Note: This guide focuses on passing the recipient list directly via the API request rather than storing and retrieving it from the database for the broadcast operation itself./api/webhooks/twilio
) to receive message status updates from Twilio.Problem Solved: Provides a scalable and manageable way to send the same SMS message to a large list of recipients without manually iterating API calls in a way that hits rate limits or requires complex sender logic. Leverages Twilio's infrastructure for queuing, delivery optimization, and compliance features (like opt-out handling).
Technologies:
System Architecture:
Prerequisites:
Expected Outcome: A deployed Next.js application with a secure API endpoint that can reliably initiate bulk SMS broadcasts via Twilio and receive delivery status updates.
1. Setting up the Project
Let's initialize our Next.js project and install necessary dependencies.
1.1. Create Next.js Project:
Open your terminal and run the following command, choosing options suitable for this guide (TypeScript, Tailwind CSS, App Router):
Navigate into the project directory:
1.2. Install Dependencies:
We need the Twilio Node.js helper library, Prisma for database interaction, and Zod for validation.
create-next-app
with TypeScript selected will typically include necessary development dependencies like@types/node
andtypescript
.1.3. Setup Prisma:
Initialize Prisma with PostgreSQL as the provider:
This creates:
prisma/schema.prisma
: Your database schema definition file..env
: A file for environment variables (Prisma automatically addsDATABASE_URL
).1.4. Configure Environment Variables:
Open the
.env
file created by Prisma. It will initially contain theDATABASE_URL
. We need to add our Twilio credentials and a secret key for our API.Important: Never commit your
.env
file to Git. Add.env
to your.gitignore
file if it's not already there.DATABASE_URL
: Important: Replace the placeholder value with your actual PostgreSQL connection string. Ensure the database exists.TWILIO_ACCOUNT_SID
/TWILIO_AUTH_TOKEN
: Find these in your Twilio Console under Account Info (Dashboard) or Account > API keys & tokens.TWILIO_MESSAGING_SERVICE_SID
: We will create this in the Twilio Integration section (Step 4). You can leave it blank for now or add a placeholder likeMGxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
.API_SECRET_KEY
: Generate a strong, unique random string (e.g., using a password manager or online generator). This will be used to authenticate requests to our broadcast API.NEXT_PUBLIC_APP_URL
: Set this to your application's base URL. It's needed for constructing the webhook callback URL. Usehttp://localhost:3000
for local development and your deployed URL (e.g.,https://your-app.vercel.app
) for production.1.5. Define Database Schema:
Open
prisma/schema.prisma
and define models for logging broadcasts and message statuses.1.6. Apply Database Migrations:
Run the following command to create the necessary SQL migration files and apply them to your database:
This will:
prisma/migrations/
.Broadcast
andMessageStatus
tables.@prisma/client
).1.7. Initialize Twilio Client:
Create a utility file to initialize the Twilio client instance.
1.8. Initialize Prisma Client:
Create a utility file for the Prisma client instance.
Project setup is complete. We have Next.js running, dependencies installed, environment variables configured, the database schema defined and migrated, and utility files for Twilio/Prisma clients.
2. Implementing Core Functionality (Simple Test Send)
Before tackling bulk sending, let's create a simple API route to verify our Twilio connection by sending a single message using a specific
from
number. This ensures credentials and basic API interaction are working.2.1. Create Simple Send API Route:
Create the file
app/api/send-simple/route.ts
:2.2. Testing the Simple Send:
You can test this using
curl
or a tool like Postman. ReplaceYOUR_VERCEL_URL
withhttp://localhost:3000
during local development, and<YOUR_PHONE_NUMBER_E164>
with your actual mobile number in E.164 format (e.g.,+14155551234
).Expected Response (Success):
You should also receive the SMS on your phone shortly. If you encounter errors, check:
.env
file has the correct Twilio Account SID and Auth Token.TWILIO_PHONE_NUMBER
constant inapp/api/send-simple/route.ts
has been replaced with a number you own in Twilio.to
number in your test command is valid and in E.164 format.npm run dev
) is running.3. Building the Bulk Broadcast API Layer
Now, let's build the core API endpoint (
/api/broadcast
) that will handle sending messages to multiple recipients using a Messaging Service.3.1. Define Request Validation Schema:
Using Zod, we define the expected shape of the request body.
3.2. Create Broadcast API Route:
Create the file
app/api/broadcast/route.ts
. This route will:Authorization
header containing ourAPI_SECRET_KEY
.TWILIO_MESSAGING_SERVICE_SID
andNEXT_PUBLIC_APP_URL
from environment variables.Broadcast
table).Explanation:
Authorization: Bearer <YOUR_API_SECRET_KEY>
. Added check forAPI_SECRET_KEY
existence.recipients
is an array of valid E.164 strings andmessage
is present.messagingServiceSid
instead offrom
. This tells Twilio to use the pool of numbers and rules defined in that service.statusCallback
parameter pointing to our future webhook endpoint, constructed usingNEXT_PUBLIC_APP_URL
.twilioClient.messages.create
.Promise.allSettled
: Waits for all these initial API calls to Twilio to either succeed (Twilio accepted the request) or fail (e.g., invalid SID, auth error, invalid 'To' number for submission). It does not wait for the SMS messages to be delivered.Broadcast
table before sending. It now also attempts to create an initialMessageStatus
record upon successful submission or logs a failure record immediately on submission error, linking them to thebroadcastId
. Updates theBroadcast
status after attempting all submissions.4. Integrating with Twilio Messaging Services
Using a Messaging Service is key for bulk sending. It manages sender phone numbers, handles opt-outs, ensures compliance, and intelligently routes messages.
4.1. Create a Messaging Service in Twilio:
4.2. Add Sender Numbers:
4.3. Configure Compliance (A2P 10DLC - US Specific):
A2P 10DLC Daily Volume Limits by Brand Type:
Important Compliance Notes:
4.4. Configure Opt-Out Management:
4.5. Obtain the Messaging Service SID:
MG...
).4.6. Update Environment Variable:
Paste the copied Service SID into your
.env
file:Restart your Next.js development server (
npm run dev
) for the new environment variable to be loaded.Your application is now configured to send messages through the Twilio Messaging Service, leveraging its scaling and compliance features.
5. Implementing Error Handling, Logging, and Status Webhooks
Robust error handling and understanding message status are vital.
5.1. Enhanced Error Handling (In API Route):
The
/api/broadcast
route already includestry...catch
blocks and logs Twilio error codes during submission. Key improvements:error.code
anderror.message
from Twilio helps diagnose issues (e.g.,21211
- Invalid 'To' phone number,20003
- Authentication error,21610
- Attempt to send to unsubscribed recipient).try...catch
to handle database connection issues or constraint violations, logging errors without necessarily halting the entire process if appropriate.console.log
,console.warn
,console.error
appropriately. In production, consider structured logging libraries (Pino, Winston) that output JSON for easier parsing by log management systems (like Vercel Logs, Datadog, etc.).Common Twilio Error Codes for Bulk Messaging:
TWILIO_ACCOUNT_SID
andTWILIO_AUTH_TOKEN
in environment variablesTWILIO_MESSAGING_SERVICE_SID
is correct and has active sendersReference: Full error code list at https://www.twilio.com/docs/api/errors
Error Handling Best Practices:
5.2. Implementing Status Webhooks:
Twilio can send HTTP requests (webhooks) to your application whenever the status of a message changes (e.g.,
queued
,sent
,delivered
,undelivered
,failed
). This is the only reliable way to know the final delivery status.5.2.1. Create Webhook Handler API Route:
Create the file
app/api/webhooks/twilio/route.ts
:Explanation:
twilio.validateRequest
with theTWILIO_AUTH_TOKEN
, thex-twilio-signature
header, the reconstructed request URL, and the parsed form-urlencoded body parameters. This is crucial security to ensure the request genuinely came from Twilio. Includes a helpergetAbsoluteUrl
to handle URL reconstruction, especially behind proxies.403 Forbidden
on invalid signature. Returns400 Bad Request
for missing parameters but crucially returns200 OK
even if there's a server configuration issue (missing Auth Token) or a database error during processing. This prevents Twilio from endlessly retrying the webhook due to downstream issues. Log these internal errors thoroughly.application/x-www-form-urlencoded
) sent by Twilio to getMessageSid
,MessageStatus
,To
,ErrorCode
, etc.prisma.messageStatus.upsert
to either create a new status record (if the webhook arrives before or instead of the initial submission log) or update the existing record for the givenmessageSid
.200 OK
response to Twilio upon successful validation and processing (or recoverable error) to prevent retries.5.2.2. Exposing the Webhook URL:
For Twilio to reach your webhook:
ngrok
(ngrok http 3000
) to expose your locallocalhost:3000
to the internet.ngrok
will give you a public URL (e.g.,https://<random-string>.ngrok.io
). UpdateNEXT_PUBLIC_APP_URL
in your.env
temporarily to thisngrok
URL.https://your-app-name.vercel.app
) is already public. EnsureNEXT_PUBLIC_APP_URL
is set correctly in your Vercel project's environment variables.5.2.3. Configuring the Webhook in Twilio (Optional but Recommended):
While we set the
statusCallback
URL per message, you can also set a default webhook URL at the Messaging Service level in the Twilio Console (Messaging > Services > [Your Service] > Integration). This acts as a fallback if the per-message callback isn't provided or fails. Ensure the URL points to/api/webhooks/twilio
.6. Deployment to Vercel
Deploying this Next.js application to Vercel is straightforward.
.env
is in your.gitignore
..env
file:DATABASE_URL
(use your production database connection string)TWILIO_ACCOUNT_SID
TWILIO_AUTH_TOKEN
TWILIO_MESSAGING_SERVICE_SID
API_SECRET_KEY
(use the same strong secret)NEXT_PUBLIC_APP_URL
(set this to your production Vercel domain, e.g.,https://your-app-name.vercel.app
)curl
or Postman to test your/api/broadcast
endpoint using the production URL and yourAPI_SECRET_KEY
. Check Vercel logs for output and Twilio console/debugger for message status. Ensure webhook calls are reaching your Vercel deployment and being processed correctly.Potential Improvements
UI: Build a simple frontend page to trigger broadcasts instead of using
curl
.Recipient Management: Implement features to upload, store, and manage recipient lists within the database (
Recipient
model).Advanced Status Tracking: Update the main
Broadcast
record status based on aggregatedMessageStatus
updates (e.g., calculate completion percentage, final status).Rate Limiting: Implement rate limiting on the
/api/broadcast
endpoint itself (e.g., usingupstash/ratelimit
).Idempotency: Ensure webhook processing is idempotent (processing the same webhook multiple times doesn't cause issues).
upsert
helps here.Detailed Error Reporting: Integrate an error tracking service (Sentry, etc.).
Background Jobs: For very large lists (> thousands), consider offloading the iteration and Twilio API calls to a background job queue (e.g., Vercel Cron Jobs, BullMQ, Quirrel) instead of processing synchronously within the API route's request lifecycle. This prevents serverless function timeouts (Vercel: 10s hobby, 60s pro, 300s enterprise).
Security: Implement more robust authentication/authorization if needed. Use timing-safe comparisons for secrets.
Understanding Twilio API Rate Limits for Bulk Messaging:
Twilio enforces rate limits on API requests to ensure platform stability. Understanding these limits is critical for bulk messaging:
API Concurrency Limits:
Throughput vs. Concurrency:
Best Practices for High-Volume Messaging:
Use Promise.allSettled() with batch processing: The current implementation sends all API requests concurrently. For >100 recipients, implement batching:
Monitor for Error 20429: Twilio returns HTTP 429 (Too Many Requests) when rate limits exceeded. Implement exponential backoff retry logic.
Use Messaging Services: Messaging Services provide built-in queueing, eliminating need for complex rate limit handling in your application.
Consider background job queues: For lists with >1,000 recipients, use background workers to avoid serverless function timeouts (Vercel: 10s hobby, 60s pro, 300s enterprise).
Reference: https://www.twilio.com/docs/usage/webhooks/webhooks-connection-overrides#connection-overrides
Frequently Asked Questions
What is the difference between using a Messaging Service and a regular Twilio phone number for bulk SMS?
A Messaging Service provides automatic sender pool management, compliance features (opt-out handling), geographic matching, and built-in queuing for bulk messaging. When you use a regular phone number with the
from
parameter, you must manually handle sender rotation, opt-outs, and rate limiting. Messaging Services simplify bulk SMS by handling these complexities automatically.How many SMS messages can I send per second with Twilio?
Twilio supports up to 100 concurrent API requests per Account SID by default. However, your message delivery throughput depends on your A2P 10DLC registration type: Sole Proprietor (1,000 segments/day to T-Mobile), Low Volume Standard (2,000–200,000 segments/day), or Standard (2,000 to unlimited based on Trust Score). The API concurrency limit differs from daily delivery limits – Twilio queues accepted messages for delivery within your approved daily volume.
Do I need A2P 10DLC registration for bulk SMS to US numbers?
Yes, you must register for A2P 10DLC when sending SMS to US numbers using 10-digit long codes. Unregistered traffic incurs additional carrier fees and faces message filtering. Registration requires creating a Brand (business information) and Campaign (use case details), with vetting taking 1–5 business days. Toll-free numbers and short codes have separate verification processes.
What happens when a recipient replies STOP to my bulk SMS messages?
When you enable Advanced Opt-Out in your Messaging Service, Twilio automatically handles STOP, UNSUBSCRIBE, START, and HELP keywords. Recipients who reply STOP are added to an opt-out list, and future messages to those numbers will fail with error code 21610. You should remove opted-out recipients from your database to avoid sending attempts. Twilio sends customizable confirmation messages when users opt out or opt in.
How do I handle Twilio rate limits when sending to thousands of recipients?
Implement batch processing to respect Twilio's 100 concurrent request limit. Process recipients in batches of 100 using
Promise.allSettled()
, waiting for each batch to complete before starting the next. For lists exceeding 1,000 recipients, use background job queues (BullMQ, Quirrel) to avoid serverless function timeouts. Messaging Services provide built-in queueing, so you don't need complex retry logic – Twilio handles delivery optimization automatically.What causes SMS messages to be billed as multiple segments?
SMS segmentation depends on character encoding. GSM-7 encoding (standard Latin characters) allows 160 characters per segment, while UCS-2 encoding (Unicode characters, emojis) limits you to 70 characters per segment. Hidden Unicode characters like smart quotes (") or em dashes (—) trigger UCS-2 encoding, doubling your costs. Enable the Smart Encoding feature in your Messaging Service to automatically replace Unicode characters with GSM-7 equivalents.
How do I track delivery status for bulk SMS campaigns?
Configure a
statusCallback
URL when creating messages, pointing to your webhook endpoint (e.g.,/api/webhooks/twilio
). Twilio sends POST requests to this URL whenever message status changes (queued, sent, delivered, undelivered, failed). Store these updates in yourMessageStatus
database table using themessageSid
as the unique identifier. This is the only reliable way to know final delivery status – don't rely on the initial API response, which only confirms Twilio accepted the request.Can I send MMS (multimedia messages) in bulk using this setup?
Yes, Twilio Messaging Services support MMS. Add a
mediaUrl
parameter to yourmessages.create()
call, providing URLs to images (JPEG, PNG, GIF up to 5 MB) or other media (up to 500 KB). Enable the MMS Converter feature in your Messaging Service to automatically convert MMS to SMS with media links when the carrier doesn't support MMS. Note that MMS messages cost more than SMS and may have lower delivery rates internationally.Related Resources
SMS Development Guides
A2P 10DLC & Compliance
Twilio Implementation Guides
Technical Implementation
Potential Improvements
UI: Build a simple frontend page to trigger broadcasts instead of using
curl
.Recipient Management: Implement features to upload, store, and manage recipient lists within the database (
Recipient
model).Advanced Status Tracking: Update the main
Broadcast
record status based on aggregatedMessageStatus
updates (e.g., calculate completion percentage, final status).Rate Limiting: Implement rate limiting on the
/api/broadcast
endpoint itself (e.g., usingupstash/ratelimit
).Idempotency: Ensure webhook processing is idempotent (processing the same webhook multiple times doesn't cause issues).
upsert
helps here.Detailed Error Reporting: Integrate an error tracking service (Sentry, etc.).
Background Jobs: For very large lists (> thousands), consider offloading the iteration and Twilio API calls to a background job queue (e.g., Vercel Cron Jobs, BullMQ, Quirrel) instead of processing synchronously within the API route's request lifecycle. This prevents serverless function timeouts (Vercel: 10s hobby, 60s pro, 300s enterprise).
Security: Implement more robust authentication/authorization if needed. Use timing-safe comparisons for secrets.
Understanding Twilio API Rate Limits for Bulk Messaging:
Twilio enforces rate limits on API requests to ensure platform stability. Understanding these limits is critical for bulk messaging:
API Concurrency Limits:
Throughput vs. Concurrency:
Best Practices for High-Volume Messaging:
Use Promise.allSettled() with batch processing: The current implementation sends all API requests concurrently. For >100 recipients, implement batching:
Monitor for Error 20429: Twilio returns HTTP 429 (Too Many Requests) when rate limits exceeded. Implement exponential backoff retry logic.
Use Messaging Services: Messaging Services provide built-in queueing, eliminating need for complex rate limit handling in your application.
Consider background job queues: For lists with >1,000 recipients, use background workers to avoid serverless function timeouts (Vercel: 10s hobby, 60s pro, 300s enterprise).
Reference: https://www.twilio.com/docs/usage/webhooks/webhooks-connection-overrides#connection-overrides