How to Send Bulk SMS with MessageBird Node.js and Express (2025 Guide)
Learn how to build a production-ready bulk SMS system with MessageBird, Node.js, and Express. Complete tutorial with batching, rate limiting, error handling, and delivery tracking. Includes code examples and best practices.
Send bulk SMS messages efficiently using MessageBird's API with Node.js and Express. This tutorial shows you how to build a production-ready bulk SMS broadcasting system with proper batching, rate limiting, error handling, and delivery tracking.
What You'll Build:
REST API endpoint for bulk SMS broadcasting
Intelligent message batching (50 recipients per batch)
Bulk SMS broadcasting lets you send the same message to multiple recipients simultaneously—a critical capability for modern business communications. You'll use this for:
Marketing Campaigns: Send promotional messages to your customer base and drive engagement
Emergency Alerts: Notify users about urgent situations requiring immediate attention
System Notifications: Broadcast service updates, maintenance windows, or status changes
Event Reminders: Send reminders to registered attendees to reduce no-shows
MessageBird's Bulk Messaging Capabilities:
Send to up to 50 recipients per API request
Support for international phone numbers in E.164 format
Delivery status tracking per recipient
Scheduled message sending
Template support for personalization
Why Use MessageBird for Bulk SMS?
MessageBird provides enterprise-grade SMS infrastructure with direct carrier connections in 190+ countries, competitive pricing starting at $0.0075 per message, and a developer-friendly API. The Node.js SDK simplifies integration and handles authentication, request formatting, and error handling automatically—making it ideal for developers building bulk SMS solutions with Node.js and Express.
Set Up Your MessageBird Account
You need a MessageBird account and API key to follow this tutorial.
Create a REST API endpoint that accepts recipient lists and broadcasts messages efficiently. This endpoint forms the core of your bulk SMS system, handling input validation, batching, and response formatting.
Create server.js:
JavaScript
require('dotenv').config();
const express = require('express');
const messagebird = require('messagebird')(process.env.MESSAGEBIRD_API_KEY);
const { body, validationResult } = require('express-validator');
const rateLimit = require('express-rate-limit');
const app = express();
app.use(express.json());
// Rate limiting: 100 requests per 15 minutes per IPconst limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests from this IP, please try again later.'
});
app.use('/api/', limiter);
// Bulk SMS endpoint
app.post('/api/sms/broadcast',
[
body('recipients').isArray({ min: 1 }).withMessage('Recipients must be a non-empty array'),
body('recipients.*').isMobilePhone().withMessage('Each recipient must be a valid phone number'),
body('message').isString().isLength({ min: 1, max: 1600 }).withMessage('Message must be 1-1600 characters'),
body('originator').isString().isLength({ min: 1, max: 11 }).withMessage('Originator must be 1-11 characters')
],
async (req, res) => {
// Validate requestconst errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { recipients, message, originator } = req.body;
try {
const results = awaitsendBulkSMS(recipients, message, originator);
res.json({
success: true,
totalRecipients: recipients.length,
totalBatches: results.length,
results: results,
summary: {
successful: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length
}
});
} catch (error) {
console.error('Bulk SMS error:', error);
res.status(500).json({
success: false,
error: 'Failed to send bulk SMS',
details: error.message
});
}
}
);
// Batching functionasyncfunctionsendBulkSMS(recipients, message, originator) {
constBATCH_SIZE = 50; // MessageBird maximum per requestconstDELAY_BETWEEN_BATCHES = 1000; // 1 second delayconst batches = [];
for (let i = 0; i < recipients.length; i += BATCH_SIZE) {
batches.push(recipients.slice(i, i + BATCH_SIZE));
}
const results = [];
for (let i = 0; i < batches.length; i++) {
try {
const batch = batches[i];
// Send batchconst result = awaitnewPromise((resolve, reject) => {
messagebird.messages.create({
originator: originator,
recipients: batch,
body: message
}, (err, response) => {
if (err) {
reject(err);
} else {
resolve(response);
}
});
});
results.push({
batchIndex: i,
success: true,
messageId: result.id,
recipientCount: batch.length
});
// Delay between batches (except for last batch)if (i < batches.length - 1) {
awaitnewPromise(resolve =>setTimeout(resolve, DELAY_BETWEEN_BATCHES));
}
} catch (error) {
results.push({
batchIndex: i,
success: false,
error: error.message,
recipientCount: batches[i].length
});
}
}
return results;
}
constPORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Bulk SMS API running on port ${PORT}`);
});
Key Implementation Details:
Batching Logic: You split large recipient lists into batches of 50 (MessageBird's maximum per request). This prevents API rejections and improves reliability.
Rate Limiting: You implement a 1-second delay between batches to respect MessageBird's rate limits (500 requests/second for POST operations, verified January 2025).
Error Handling: You track both successful deliveries and failures, returning detailed results for each batch.
Delivery Status Tracking: You store message IDs for later status checks through MessageBird's delivery reports.
How Batching and Rate Limiting Work
Intelligent batching and rate limiting are essential for reliable bulk SMS delivery. Understanding MessageBird's API constraints helps you optimize throughput while avoiding rate limit errors.
MessageBird API Rate Limits (Verified January 2025):
POST requests: 500 requests/second maximum
GET requests: 50 requests/second maximum
Exceeding limits results in HTTP 429 (Too Many Requests) errors
How to Calculate Optimal Batch Size:
MessageBird allows up to 50 recipients per request. With a 500 req/s limit:
Practical recommendation: 10,000–15,000 recipients/second with 1-second delays
Tuning Parameters:
JavaScript
// Recommended configuration for productionconstBATCH_SIZE = 50; // Maximum 50 recipients per MessageBird requestconstDELAY_BETWEEN_BATCHES = 1000; // 1 second delay to respect rate limitsconstMAX_RETRIES = 3; // Retry failed batches up to 3 timesconstRETRY_DELAY = 5000; // Wait 5 seconds before retrying
When to Adjust Batch Size:
High-priority messages: Reduce batch size to 25–30 for faster individual delivery tracking
Low-priority bulk campaigns: Keep at 50 for maximum throughput
Rate limit errors: Increase delay between batches to 2–3 seconds
Advanced Batching Strategies:
For campaigns exceeding 100,000 recipients, consider implementing:
Job Queues: Use BullMQ or AWS SQS to process batches asynchronously
Horizontal Scaling: Distribute batches across multiple worker processes
Priority Queuing: Process high-priority messages before bulk campaigns
Time-Based Scheduling: Send during optimal delivery windows for your audience
Track Message Delivery Status
Monitor message delivery in real-time using MessageBird's delivery status webhooks and API. Tracking delivery status is crucial for campaign analytics, compliance reporting, and troubleshooting delivery issues.
How MessageBird Delivery Tracking Works:
When you send messages, MessageBird returns a unique message ID for each batch. You use these IDs to:
Check current delivery status
Identify failed deliveries
Track delivery timestamps
Handle carrier-specific errors
Message Status Values:
scheduled – You scheduled the message for future delivery
sent – MessageBird sent the message to the carrier
buffered – The carrier queued the message for delivery
delivered – The recipient received the message
expired – The message expired before delivery
delivery_failed – Delivery failed permanently
Create Status Checking Endpoint:
JavaScript
// Get delivery status for a specific message
app.get('/api/sms/status/:messageId', async (req, res) => {
try {
const { messageId } = req.params;
messagebird.messages.read(messageId, (err, message) => {
if (err) {
return res.status(404).json({
error: 'Message not found',
details: err
});
}
res.json({
messageId: message.id,
status: message.recipients.items[0].status,
statusDatetime: message.recipients.items[0].statusDatetime,
recipient: message.recipients.items[0].recipient,
totalCost: message.recipients.totalDeliveryFailedCount
});
});
} catch (error) {
res.status(500).json({ error: 'Failed to fetch status' });
}
});
Set Up Delivery Report Webhooks:
You can configure webhooks to receive real-time delivery updates instead of polling the API.
In MessageBird Dashboard:
Navigate to Developers → Webhooks
Create new webhook
Set URL: https://your-domain.com/api/webhooks/delivery-status
Select event: Message Status Updates
Webhook Handler:
JavaScript
app.post('/api/webhooks/delivery-status', express.json(), (req, res) => {
const { id, recipient, status, statusDatetime } = req.body;
console.log(`Message ${id} to ${recipient}: ${status} at ${statusDatetime}`);
// Store status update in your database// Update customer dashboard// Trigger alerts for failed deliveries
res.sendStatus(200); // Acknowledge receipt
});
Best Practices for Delivery Tracking:
Store Message IDs: Save message IDs in your database with campaign metadata
Implement Webhooks: Use webhooks for real-time updates instead of polling
Monitor Failure Rates: Track delivery failure rates by country and carrier
Set Up Alerts: Notify administrators when failure rates exceed thresholds
When sending bulk SMS, you'll encounter various error codes that indicate different failure types. Understanding these errors helps you implement effective retry logic and maintain high delivery rates.
Common MessageBird Error Codes:
Error Code
Error Name
What It Means
How You Fix It
1
EC_UNKNOWN_SUBSCRIBER
The phone number doesn't exist or is disconnected
Remove from your recipient list
2
EC_ABSENT_SUBSCRIBER
The recipient's phone is turned off or out of coverage
You should log errors with sufficient detail for debugging while protecting sensitive information:
JavaScript
functionlogError(error, context) {
console.error({
timestamp: newDate().toISOString(),
context: context,
errorCode: error.code,
errorMessage: error.message,
// Don't log full recipient lists or message content in productionrecipientCount: context.recipientCount || 0
});
}
Handling Opt-Out Requests:
When you receive error code 103 (EC_SUBSCRIBER_OPTEDOUT), immediately:
Remove the recipient from your active lists
Add to suppression list to prevent future sends
Update your database with opt-out timestamp
Comply with TCPA and GDPR regulations
Optimize Bulk SMS Performance
Significantly improve throughput and reliability with proven optimization techniques. Performance optimization is critical when scaling from hundreds to thousands of messages per minute.
Use Job Queues for Large-Scale Sending:
For campaigns exceeding 10,000 recipients, you should use a job queue system to handle processing asynchronously.
You should store recipient data efficiently to support fast bulk operations:
SQL
-- Create indexed table for fast lookupsCREATE TABLE recipients (
id SERIAL PRIMARY KEY,
phone_number VARCHAR(20) NOT NULL,
opted_in BOOLEANDEFAULTtrue,
last_message_sent TIMESTAMP,
delivery_failures INTDEFAULT0,
INDEX idx_opted_in (opted_in),
INDEX idx_phone (phone_number)
);
-- Query only opted-in recipients with low failure ratesSELECT phone_number FROM recipients
WHERE opted_in =trueAND delivery_failures <3
LIMIT 10000;
Caching Strategy:
You can cache MessageBird API responses to reduce redundant lookups:
You should track these key metrics to identify bottlenecks:
Messages sent per minute
Average batch processing time
Error rate by error code
API response times
Queue depth and processing lag
Recommended Monitoring Tools:
Datadog: Application performance monitoring with SMS delivery tracking
Sentry: Error tracking and alerting for failed deliveries
CloudWatch: AWS infrastructure monitoring for queue-based systems
Prometheus + Grafana: Self-hosted metrics and dashboards
Security Best Practices:
API Key Security: Store keys in AWS Secrets Manager or HashiCorp Vault
Rate Limiting: Implement per-user rate limits to prevent abuse
Input Validation: Sanitize all user inputs to prevent injection attacks
HTTPS Only: Use SSL/TLS certificates for all API endpoints
Audit Logging: Log all SMS sends with timestamps and user IDs
Validate and Format Phone Numbers
Phone number validation is essential before sending bulk SMS—it reduces wasted API calls, improves delivery rates, and ensures MessageBird can properly route messages to international carriers.
Install Phone Number Validation Library:
Bash
npm install libphonenumber-js
Implement E.164 Format Validation:
MessageBird requires phone numbers in E.164 format: +[country code][subscriber number] (no spaces, hyphens, or parentheses).
Thoroughly testing your bulk SMS implementation before production deployment is critical. Testing helps identify rate limiting issues, validates error handling, and ensures your system can handle expected load.
Manual Testing with curl:
Bash
# Test with small recipient list
curl -X POST http://localhost:3000/api/sms/broadcast \
-H "Content-Type: application/json" \
-d '{
"recipients": ["+12025550123", "+12025550124"],
"message": "Test message from MessageBird",
"originator": "TestSender"
}'
Proper deployment configuration ensures production reliability, scalability, and security. Production deployments require environment-specific settings, monitoring, and infrastructure that differs from development.
FROM node:18-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
# Run as non-root user
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Prometheus + Grafana: Self-hosted metrics and dashboards
Security Best Practices:
API Key Security: Store keys in AWS Secrets Manager or HashiCorp Vault
Rate Limiting: Implement per-user rate limits to prevent abuse
Input Validation: Sanitize all user inputs to prevent injection attacks
HTTPS Only: Use SSL/TLS certificates for all API endpoints
Audit Logging: Log all SMS sends with timestamps and user IDs
FAQ: Bulk SMS with MessageBird, Node.js, and Express
How many SMS messages can I send per second with MessageBird?
MessageBird supports up to 500 POST requests per second, with each request handling up to 50 recipients. This means you can theoretically send to 25,000 recipients per second. However, we recommend a practical limit of 10,000–15,000 recipients per second with proper batching and 1-second delays to ensure reliability and avoid rate limit errors.
What is the maximum number of recipients per MessageBird API request?
MessageBird allows a maximum of 50 recipients per API request. For larger campaigns, you need to split recipients into batches of 50 and send multiple requests with appropriate delays between batches to respect rate limits.
How much does it cost to send bulk SMS with MessageBird?
MessageBird pricing varies by destination country. US/Canada messages cost $0.0075–$0.015 per SMS segment, Europe costs $0.02–$0.08 per segment, and Asia-Pacific costs $0.015–$0.12 per segment. Multi-segment messages (over 160 characters for GSM-7 or 70 characters for Unicode) multiply these costs. Check MessageBird's pricing page for current rates.
How do I handle phone number validation for international bulk SMS?
Use the libphonenumber-js library to validate and format phone numbers in E.164 format (+[country code][subscriber number]). This ensures MessageBird can properly route messages to international carriers. Always validate numbers before sending to avoid wasted API calls and delivery failures. See our E.164 phone format guide for more details.
What happens if a bulk SMS message fails to deliver?
MessageBird returns specific error codes for failed deliveries. Common failures include invalid numbers (error code 1), opted-out recipients (error code 103), and unregistered sender IDs (error code 104). Implement retry logic for temporary failures (like phones being turned off) and remove permanently failed numbers from your lists. Use delivery webhooks to monitor real-time status updates.
How do I set up delivery tracking for bulk SMS campaigns?
Configure MessageBird delivery webhooks in your dashboard under Developers → Webhooks. Set your webhook URL (e.g., https://your-domain.com/api/webhooks/delivery-status) and select Message Status Updates. MessageBird will POST delivery status changes to your endpoint in real-time, allowing you to track delivered, failed, expired, and other statuses without polling the API.
Can I schedule bulk SMS messages for future delivery with MessageBird?
Yes, MessageBird supports scheduled message delivery. Add a scheduledDatetime parameter to your API request in ISO 8601 format (e.g., 2025-01-20T14:30:00Z). This is useful for time-zone-optimized campaigns, appointment reminders, and promotional messages during peak engagement hours.
What's the difference between GSM-7 and UCS-2 encoding for SMS?
GSM-7 encoding supports 160 characters per SMS segment and includes standard Latin characters, numbers, and basic punctuation. UCS-2 encoding supports 70 characters per segment and is required for Unicode characters like emojis, Chinese/Arabic text, and special symbols. UCS-2 messages cost more due to fewer characters per segment. Use the calculateSegments() function in this tutorial to estimate costs before sending.
How do I register a sender ID for bulk SMS in the United States?
US A2P (Application-to-Person) messaging requires 10DLC registration through MessageBird. The process takes 2–4 weeks and includes business verification, use case approval, and brand registration. Without 10DLC registration, your messages may be blocked or heavily filtered by US carriers. Check MessageBird's documentation for country-specific sender ID requirements.
What's the best architecture for sending millions of SMS messages?
For campaigns exceeding 100,000 recipients, use a job queue system like BullMQ with Redis or AWS SQS. Split recipients into batches of 10,000–50,000 per job, process jobs asynchronously with worker processes, implement retry logic for failed batches, and monitor queue depth and processing lag. This architecture allows horizontal scaling and prevents API rate limit errors while maintaining high throughput.
Launch Your Production Bulk SMS System
You now have a complete, production-ready bulk SMS broadcasting system built with MessageBird, Node.js, and Express. This implementation provides everything needed to send high-volume SMS campaigns reliably and efficiently.
Your system includes intelligent batching, rate limiting, comprehensive error handling, delivery tracking, and phone number validation—all essential components for enterprise-grade bulk messaging.
What You Built:
REST API endpoint handling bulk SMS requests
Batching system processing 50 recipients per request
You're ready to start sending bulk SMS messages efficiently and reliably using MessageBird!
Frequently asked questions
Use Node.js with Express, the MessageBird API, and a batch sending service to efficiently handle large volumes of SMS messages. This setup allows you to send messages concurrently in controlled batches, minimizing server load and respecting API rate limits, which is crucial for bulk sending.
MessageBird is a Communication Platform as a Service (CPaaS) that provides a reliable SMS API with global reach. In a Node.js SMS application, MessageBird's Node.js SDK is used to interact with their API to actually send the SMS messages.
Express.js simplifies building a robust API endpoint to manage bulk SMS sending. It provides a structured framework for handling requests, routing, middleware (like validation and rate limiting), and responses, making it easier to organize and maintain your bulk SMS application logic.
Batching is essential when sending SMS to large recipient lists to avoid overwhelming server resources and hitting API rate limits. The article recommends starting with a batch size of 50 and a 1-second delay between batches, but these parameters should be adjusted based on testing and your MessageBird account limits.
Store your MessageBird Live API Access Key securely in a .env file, which should be added to your .gitignore. Then, load these environment variables into your Node.js application using the dotenv package. This keeps your API key secret and separate from your code.
Express-validator provides middleware for input validation and sanitization, ensuring your API receives data in the correct format, which is crucial for preventing errors. It's used to validate that recipients are provided as a non-empty array of strings and that the message body is a non-empty string.
Rate limiting protects your application from abuse and helps ensure you stay within MessageBird's API usage limits. The express-rate-limit middleware allows you to control the number of bulk send requests from a given IP address within a specific timeframe, preventing overload.
A persistent job queue, like BullMQ or Kue, is strongly recommended for production bulk SMS applications, especially with large volumes. This ensures that messages are sent reliably even if the server restarts and provides better scalability and retry mechanisms for failed sends.
A suitable schema includes a 'users' table with phone numbers, subscription status, and other user details. An 'sms_campaign_messages' table tracks campaign details, individual message statuses (e.g., pending, sent, failed), and links back to the user. Proper indexing is vital for performance.
Handle MessageBird API errors inside the Promise within your messaging service. Log the error details, collect them in a results object, and implement selective retries for transient errors like timeouts or 5xx server errors, but avoid retrying non-recoverable errors like invalid recipient or originator format.
The MESSAGEBIRD_ORIGINATOR environment variable sets the sender ID that recipients see. It must be a valid, purchased MessageBird number in E.164 format (e.g., +12025550187) or a registered alphanumeric sender ID. An incorrect originator will lead to errors.
Secure your bulk SMS endpoint using API key authentication (e.g., via headers), input validation, rate limiting, and appropriate authorization mechanisms. Never expose your MessageBird API key directly in client-side code or commit it to version control.
Use a library like async-retry or implement manual exponential backoff within the Promise handling each message send. Only retry on transient errors (timeouts, 5xx) and implement logic to avoid retrying non-recoverable errors (invalid recipient, insufficient balance).
Batch size controls the number of messages sent concurrently. A larger batch size increases throughput but also increases the risk of server overload or hitting API rate limits. It's crucial to tune this parameter based on testing and your server resources.
Yes, you can use a registered alphanumeric sender ID (up to 11 characters) as the MESSAGEBIRD_ORIGINATOR, but availability and regulations vary by country. Check MessageBird's documentation for specific requirements and register your sender ID in the MessageBird Dashboard if needed.
Send bulk SMS messages efficiently using MessageBird's API with Node.js and Express. This tutorial shows you how to build a production-ready bulk SMS broadcasting system with proper batching, rate limiting, error handling, and delivery tracking.
What You'll Build:
Prerequisites:
Table of Contents:
What Is Bulk SMS Broadcasting?
Bulk SMS broadcasting lets you send the same message to multiple recipients simultaneously—a critical capability for modern business communications. You'll use this for:
Marketing Campaigns: Send promotional messages to your customer base and drive engagement
Emergency Alerts: Notify users about urgent situations requiring immediate attention
System Notifications: Broadcast service updates, maintenance windows, or status changes
Event Reminders: Send reminders to registered attendees to reduce no-shows
MessageBird's Bulk Messaging Capabilities:
Why Use MessageBird for Bulk SMS?
MessageBird provides enterprise-grade SMS infrastructure with direct carrier connections in 190+ countries, competitive pricing starting at $0.0075 per message, and a developer-friendly API. The Node.js SDK simplifies integration and handles authentication, request formatting, and error handling automatically—making it ideal for developers building bulk SMS solutions with Node.js and Express.
Set Up Your MessageBird Account
You need a MessageBird account and API key to follow this tutorial.
Step 1: Create MessageBird Account
Step 2: Get Your API Key
live_)test_)Important Security Notes:
MessageBird Pricing Overview:
MessageBird charges per message segment sent. Costs vary by destination country:
Check MessageBird's pricing page for current rates in your target countries.
Initialize Your Node.js Project
Set up a new Node.js project with Express and MessageBird dependencies. This foundation enables you to build a production-ready bulk SMS API.
Create Project Directory:
mkdir messagebird-bulk-sms cd messagebird-bulk-sms npm init -yInstall Required Packages:
Package Versions (January 2025):
express: ^4.18.0 – Web frameworkmessagebird: ^4.0.0 – Official MessageBird SDKdotenv: ^16.4.0 – Environment variable managementexpress-validator: ^7.0.0 – Request validationexpress-rate-limit: ^7.1.0 – API rate limitingCreate Environment File (.env):
Add .env to .gitignore:
echo ".env" >> .gitignore echo "node_modules/" >> .gitignoreProject Structure:
Build the Bulk SMS Broadcasting Endpoint
Create a REST API endpoint that accepts recipient lists and broadcasts messages efficiently. This endpoint forms the core of your bulk SMS system, handling input validation, batching, and response formatting.
Create server.js:
require('dotenv').config(); const express = require('express'); const messagebird = require('messagebird')(process.env.MESSAGEBIRD_API_KEY); const { body, validationResult } = require('express-validator'); const rateLimit = require('express-rate-limit'); const app = express(); app.use(express.json()); // Rate limiting: 100 requests per 15 minutes per IP const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, message: 'Too many requests from this IP, please try again later.' }); app.use('/api/', limiter); // Bulk SMS endpoint app.post('/api/sms/broadcast', [ body('recipients').isArray({ min: 1 }).withMessage('Recipients must be a non-empty array'), body('recipients.*').isMobilePhone().withMessage('Each recipient must be a valid phone number'), body('message').isString().isLength({ min: 1, max: 1600 }).withMessage('Message must be 1-1600 characters'), body('originator').isString().isLength({ min: 1, max: 11 }).withMessage('Originator must be 1-11 characters') ], async (req, res) => { // Validate request const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { recipients, message, originator } = req.body; try { const results = await sendBulkSMS(recipients, message, originator); res.json({ success: true, totalRecipients: recipients.length, totalBatches: results.length, results: results, summary: { successful: results.filter(r => r.success).length, failed: results.filter(r => !r.success).length } }); } catch (error) { console.error('Bulk SMS error:', error); res.status(500).json({ success: false, error: 'Failed to send bulk SMS', details: error.message }); } } ); // Batching function async function sendBulkSMS(recipients, message, originator) { const BATCH_SIZE = 50; // MessageBird maximum per request const DELAY_BETWEEN_BATCHES = 1000; // 1 second delay const batches = []; for (let i = 0; i < recipients.length; i += BATCH_SIZE) { batches.push(recipients.slice(i, i + BATCH_SIZE)); } const results = []; for (let i = 0; i < batches.length; i++) { try { const batch = batches[i]; // Send batch const result = await new Promise((resolve, reject) => { messagebird.messages.create({ originator: originator, recipients: batch, body: message }, (err, response) => { if (err) { reject(err); } else { resolve(response); } }); }); results.push({ batchIndex: i, success: true, messageId: result.id, recipientCount: batch.length }); // Delay between batches (except for last batch) if (i < batches.length - 1) { await new Promise(resolve => setTimeout(resolve, DELAY_BETWEEN_BATCHES)); } } catch (error) { results.push({ batchIndex: i, success: false, error: error.message, recipientCount: batches[i].length }); } } return results; } const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Bulk SMS API running on port ${PORT}`); });Key Implementation Details:
Batching Logic: You split large recipient lists into batches of 50 (MessageBird's maximum per request). This prevents API rejections and improves reliability.
Rate Limiting: You implement a 1-second delay between batches to respect MessageBird's rate limits (500 requests/second for POST operations, verified January 2025).
Error Handling: You track both successful deliveries and failures, returning detailed results for each batch.
Delivery Status Tracking: You store message IDs for later status checks through MessageBird's delivery reports.
How Batching and Rate Limiting Work
Intelligent batching and rate limiting are essential for reliable bulk SMS delivery. Understanding MessageBird's API constraints helps you optimize throughput while avoiding rate limit errors.
MessageBird API Rate Limits (Verified January 2025):
How to Calculate Optimal Batch Size:
MessageBird allows up to 50 recipients per request. With a 500 req/s limit:
Tuning Parameters:
// Recommended configuration for production const BATCH_SIZE = 50; // Maximum 50 recipients per MessageBird request const DELAY_BETWEEN_BATCHES = 1000; // 1 second delay to respect rate limits const MAX_RETRIES = 3; // Retry failed batches up to 3 times const RETRY_DELAY = 5000; // Wait 5 seconds before retryingWhen to Adjust Batch Size:
Advanced Batching Strategies:
For campaigns exceeding 100,000 recipients, consider implementing:
Track Message Delivery Status
Monitor message delivery in real-time using MessageBird's delivery status webhooks and API. Tracking delivery status is crucial for campaign analytics, compliance reporting, and troubleshooting delivery issues.
How MessageBird Delivery Tracking Works:
When you send messages, MessageBird returns a unique message ID for each batch. You use these IDs to:
Message Status Values:
scheduled– You scheduled the message for future deliverysent– MessageBird sent the message to the carrierbuffered– The carrier queued the message for deliverydelivered– The recipient received the messageexpired– The message expired before deliverydelivery_failed– Delivery failed permanentlyCreate Status Checking Endpoint:
// Get delivery status for a specific message app.get('/api/sms/status/:messageId', async (req, res) => { try { const { messageId } = req.params; messagebird.messages.read(messageId, (err, message) => { if (err) { return res.status(404).json({ error: 'Message not found', details: err }); } res.json({ messageId: message.id, status: message.recipients.items[0].status, statusDatetime: message.recipients.items[0].statusDatetime, recipient: message.recipients.items[0].recipient, totalCost: message.recipients.totalDeliveryFailedCount }); }); } catch (error) { res.status(500).json({ error: 'Failed to fetch status' }); } });Set Up Delivery Report Webhooks:
You can configure webhooks to receive real-time delivery updates instead of polling the API.
In MessageBird Dashboard:
https://your-domain.com/api/webhooks/delivery-statusWebhook Handler:
app.post('/api/webhooks/delivery-status', express.json(), (req, res) => { const { id, recipient, status, statusDatetime } = req.body; console.log(`Message ${id} to ${recipient}: ${status} at ${statusDatetime}`); // Store status update in your database // Update customer dashboard // Trigger alerts for failed deliveries res.sendStatus(200); // Acknowledge receipt });Best Practices for Delivery Tracking:
Handle Errors and Failed Deliveries
When sending bulk SMS, you'll encounter various error codes that indicate different failure types. Understanding these errors helps you implement effective retry logic and maintain high delivery rates.
Common MessageBird Error Codes:
EC_UNKNOWN_SUBSCRIBEREC_ABSENT_SUBSCRIBEREC_ILLEGAL_SUBSCRIBEREC_FACILITY_NOT_SUPPORTEDEC_SUBSCRIBER_OPTEDOUTEC_SENDER_UNREGISTEREDImplement Comprehensive Error Handling:
async function sendBulkSMSWithRetry(recipients, message, originator, maxRetries = 3) { const BATCH_SIZE = 50; const DELAY_BETWEEN_BATCHES = 1000; const RETRY_DELAY = 5000; const batches = []; for (let i = 0; i < recipients.length; i += BATCH_SIZE) { batches.push(recipients.slice(i, i + BATCH_SIZE)); } const results = []; for (let i = 0; i < batches.length; i++) { let retries = 0; let success = false; while (retries < maxRetries && !success) { try { const batch = batches[i]; const result = await new Promise((resolve, reject) => { messagebird.messages.create({ originator: originator, recipients: batch, body: message }, (err, response) => { if (err) { // Check if error is retryable if (err.errors && err.errors[0]) { const errorCode = err.errors[0].code; // Retryable errors: rate limit, temporary carrier issues if ([2, 20, 21].includes(errorCode)) { reject({ retryable: true, error: err }); } else { // Permanent errors: invalid number, opted out, etc. reject({ retryable: false, error: err }); } } else { reject({ retryable: true, error: err }); } } else { resolve(response); } }); }); results.push({ batchIndex: i, success: true, messageId: result.id, recipientCount: batch.length, attempts: retries + 1 }); success = true; } catch (error) { retries++; if (error.retryable && retries < maxRetries) { console.log(`Batch ${i} failed, retrying (${retries}/${maxRetries})...`); await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); } else { results.push({ batchIndex: i, success: false, error: error.error.message, errorCode: error.error.errors?.[0]?.code, recipientCount: batches[i].length, attempts: retries }); break; } } } // Delay between batches if (i < batches.length - 1) { await new Promise(resolve => setTimeout(resolve, DELAY_BETWEEN_BATCHES)); } } return results; }Error Logging Best Practices:
You should log errors with sufficient detail for debugging while protecting sensitive information:
function logError(error, context) { console.error({ timestamp: new Date().toISOString(), context: context, errorCode: error.code, errorMessage: error.message, // Don't log full recipient lists or message content in production recipientCount: context.recipientCount || 0 }); }Handling Opt-Out Requests:
When you receive error code 103 (EC_SUBSCRIBER_OPTEDOUT), immediately:
Optimize Bulk SMS Performance
Significantly improve throughput and reliability with proven optimization techniques. Performance optimization is critical when scaling from hundreds to thousands of messages per minute.
Use Job Queues for Large-Scale Sending:
For campaigns exceeding 10,000 recipients, you should use a job queue system to handle processing asynchronously.
Example with BullMQ:
const { Queue, Worker } = require('bullmq'); const Redis = require('ioredis'); const connection = new Redis({ host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379 }); // Create queue const smsQueue = new Queue('bulk-sms', { connection }); // Add job to queue app.post('/api/sms/campaign', async (req, res) => { const { recipients, message, originator, campaignName } = req.body; const job = await smsQueue.add('send-bulk-sms', { recipients, message, originator, campaignName }); res.json({ success: true, jobId: job.id, message: 'Campaign queued for processing' }); }); // Process jobs const worker = new Worker('bulk-sms', async (job) => { const { recipients, message, originator } = job.data; const results = await sendBulkSMSWithRetry(recipients, message, originator); return { totalSent: results.filter(r => r.success).length, totalFailed: results.filter(r => !r.success).length, details: results }; }, { connection }); worker.on('completed', (job) => { console.log(`Job ${job.id} completed:`, job.returnvalue); }); worker.on('failed', (job, err) => { console.error(`Job ${job.id} failed:`, err.message); });Database Optimization:
You should store recipient data efficiently to support fast bulk operations:
-- Create indexed table for fast lookups CREATE TABLE recipients ( id SERIAL PRIMARY KEY, phone_number VARCHAR(20) NOT NULL, opted_in BOOLEAN DEFAULT true, last_message_sent TIMESTAMP, delivery_failures INT DEFAULT 0, INDEX idx_opted_in (opted_in), INDEX idx_phone (phone_number) ); -- Query only opted-in recipients with low failure rates SELECT phone_number FROM recipients WHERE opted_in = true AND delivery_failures < 3 LIMIT 10000;Caching Strategy:
You can cache MessageBird API responses to reduce redundant lookups:
const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 3600 }); // 1 hour TTL // Cache delivery status app.get('/api/sms/status/:messageId', async (req, res) => { const { messageId } = req.params; // Check cache first const cached = cache.get(messageId); if (cached) { return res.json(cached); } // Fetch from API messagebird.messages.read(messageId, (err, message) => { if (err) { return res.status(404).json({ error: 'Message not found' }); } const status = { messageId: message.id, status: message.recipients.items[0].status, statusDatetime: message.recipients.items[0].statusDatetime }; // Cache for 1 hour cache.set(messageId, status); res.json(status); }); });Monitor Performance Metrics:
You should track these key metrics to identify bottlenecks:
Recommended Monitoring Tools:
Security Best Practices:
Validate and Format Phone Numbers
Phone number validation is essential before sending bulk SMS—it reduces wasted API calls, improves delivery rates, and ensures MessageBird can properly route messages to international carriers.
Install Phone Number Validation Library:
Implement E.164 Format Validation:
MessageBird requires phone numbers in E.164 format:
+[country code][subscriber number](no spaces, hyphens, or parentheses).Examples of Valid E.164 Format:
+12025550123(country code 1 + 10-digit number)+447911123456(country code 44 + mobile number)+31612345678(country code 31 + mobile number)const { parsePhoneNumber } = require('libphonenumber-js'); function validateAndFormatPhone(phoneNumber, defaultCountry = 'US') { try { const parsed = parsePhoneNumber(phoneNumber, defaultCountry); if (parsed && parsed.isValid()) { return parsed.format('E.164'); // Returns +12025550123 format } return null; } catch (error) { return null; } } // Usage examples console.log(validateAndFormatPhone('(202) 555-0123', 'US')); // +12025550123 console.log(validateAndFormatPhone('202-555-0123', 'US')); // +12025550123 console.log(validateAndFormatPhone('+1 202 555 0123')); // +12025550123 console.log(validateAndFormatPhone('invalid')); // nullBulk Validation Function:
function validateBulkRecipients(recipients, defaultCountry = 'US') { const results = { valid: [], invalid: [] }; recipients.forEach(phone => { const formatted = validateAndFormatPhone(phone, defaultCountry); if (formatted) { results.valid.push(formatted); } else { results.invalid.push(phone); } }); return results; } // Usage in endpoint app.post('/api/sms/broadcast', async (req, res) => { const { recipients, message, originator } = req.body; // Validate all phone numbers const validation = validateBulkRecipients(recipients); if (validation.invalid.length > 0) { return res.status(400).json({ error: 'Some phone numbers are invalid', invalidNumbers: validation.invalid, validCount: validation.valid.length }); } // Send to valid numbers only const results = await sendBulkSMS(validation.valid, message, originator); res.json({ success: true, results }); });Character Encoding and Message Segmentation:
You should understand how message length affects costs:
Check Message Segment Count:
function calculateSegments(message) { // Check if message contains non-GSM characters const gsmRegex = /^[@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&'()*+,\-.\/:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà\n\r\^{}\\[~\]|€]*$/; const isGSM = gsmRegex.test(message); if (isGSM) { // GSM-7 encoding const singleSegmentLimit = 160; const multiSegmentLimit = 153; // Overhead for concatenation if (message.length <= singleSegmentLimit) { return 1; } return Math.ceil(message.length / multiSegmentLimit); } else { // UCS-2 encoding const singleSegmentLimit = 70; const multiSegmentLimit = 67; if (message.length <= singleSegmentLimit) { return 1; } return Math.ceil(message.length / multiSegmentLimit); } } // Usage const message = "Hello! Thanks for subscribing to our newsletter. 🎉"; const segments = calculateSegments(message); console.log(`This message will use ${segments} SMS segment(s)`);Sender ID Best Practices:
You can use alphanumeric sender IDs (like "YourBrand") or phone numbers:
Sender ID Registration by Country:
Test Your Bulk SMS System
Thoroughly testing your bulk SMS implementation before production deployment is critical. Testing helps identify rate limiting issues, validates error handling, and ensures your system can handle expected load.
Manual Testing with curl:
# Test with small recipient list curl -X POST http://localhost:3000/api/sms/broadcast \ -H "Content-Type: application/json" \ -d '{ "recipients": ["+12025550123", "+12025550124"], "message": "Test message from MessageBird", "originator": "TestSender" }'Expected Response:
{ "success": true, "totalRecipients": 2, "totalBatches": 1, "results": [ { "batchIndex": 0, "success": true, "messageId": "abc123def456", "recipientCount": 2 } ], "summary": { "successful": 2, "failed": 0 } }Automated Testing Recommendations:
You should implement unit tests for critical functions:
// tests/sms.test.js const { validateAndFormatPhone, calculateSegments } = require('../utils/phone'); describe('Phone Number Validation', () => { test('validates US phone numbers correctly', () => { expect(validateAndFormatPhone('(202) 555-0123', 'US')).toBe('+12025550123'); expect(validateAndFormatPhone('202-555-0123', 'US')).toBe('+12025550123'); expect(validateAndFormatPhone('+1 202 555 0123')).toBe('+12025550123'); }); test('rejects invalid phone numbers', () => { expect(validateAndFormatPhone('123')).toBeNull(); expect(validateAndFormatPhone('not-a-phone')).toBeNull(); }); }); describe('Message Segmentation', () => { test('calculates GSM-7 segments correctly', () => { expect(calculateSegments('a'.repeat(160))).toBe(1); expect(calculateSegments('a'.repeat(161))).toBe(2); expect(calculateSegments('a'.repeat(306))).toBe(2); expect(calculateSegments('a'.repeat(307))).toBe(3); }); test('calculates UCS-2 segments correctly', () => { expect(calculateSegments('😀'.repeat(70))).toBe(1); expect(calculateSegments('😀'.repeat(71))).toBe(2); }); });Load Testing:
You should test your system under realistic load conditions:
# Install Apache Bench for load testing # Send 1000 requests with 10 concurrent connections ab -n 1000 -c 10 -p payload.json -T application/json http://localhost:3000/api/sms/broadcastTest Scenarios to Cover:
Deploy Your Bulk SMS System to Production
Proper deployment configuration ensures production reliability, scalability, and security. Production deployments require environment-specific settings, monitoring, and infrastructure that differs from development.
Environment Configuration:
Create separate environment files for each stage:
# .env.production MESSAGEBIRD_API_KEY=live_xxxxxxxxxxxxx PORT=3000 NODE_ENV=production RATE_LIMIT_WINDOW_MS=900000 # 15 minutes RATE_LIMIT_MAX_REQUESTS=100 LOG_LEVEL=error REDIS_HOST=redis.production.internal REDIS_PORT=6379Production Dockerfile:
Docker Compose for Complete Stack:
version: '3.8' services: sms-api: build: . ports: - "3000:3000" environment: - MESSAGEBIRD_API_KEY=${MESSAGEBIRD_API_KEY} - REDIS_URL=redis://redis:6379 depends_on: - redis restart: unless-stopped redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data restart: unless-stopped volumes: redis-data:Job Queue Setup for Production:
You should use a robust job queue system for campaigns exceeding 10,000 recipients:
Option 1: BullMQ with Redis (Recommended)
Option 2: AWS SQS for Serverless
Option 3: RabbitMQ for Enterprise
Monitoring and Alerting:
You should monitor these critical metrics:
// Add health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', uptime: process.uptime(), timestamp: new Date().toISOString() }); }); // Add metrics endpoint for Prometheus/Grafana app.get('/metrics', (req, res) => { res.json({ messagesProcessed: globalMetrics.messagesProcessed, errorRate: globalMetrics.errors / globalMetrics.messagesProcessed, averageResponseTime: globalMetrics.totalResponseTime / globalMetrics.requests }); });Recommended Monitoring Tools:
Security Best Practices:
FAQ: Bulk SMS with MessageBird, Node.js, and Express
How many SMS messages can I send per second with MessageBird?
MessageBird supports up to 500 POST requests per second, with each request handling up to 50 recipients. This means you can theoretically send to 25,000 recipients per second. However, we recommend a practical limit of 10,000–15,000 recipients per second with proper batching and 1-second delays to ensure reliability and avoid rate limit errors.
What is the maximum number of recipients per MessageBird API request?
MessageBird allows a maximum of 50 recipients per API request. For larger campaigns, you need to split recipients into batches of 50 and send multiple requests with appropriate delays between batches to respect rate limits.
How much does it cost to send bulk SMS with MessageBird?
MessageBird pricing varies by destination country. US/Canada messages cost $0.0075–$0.015 per SMS segment, Europe costs $0.02–$0.08 per segment, and Asia-Pacific costs $0.015–$0.12 per segment. Multi-segment messages (over 160 characters for GSM-7 or 70 characters for Unicode) multiply these costs. Check MessageBird's pricing page for current rates.
How do I handle phone number validation for international bulk SMS?
Use the
libphonenumber-jslibrary to validate and format phone numbers in E.164 format (+[country code][subscriber number]). This ensures MessageBird can properly route messages to international carriers. Always validate numbers before sending to avoid wasted API calls and delivery failures. See our E.164 phone format guide for more details.What happens if a bulk SMS message fails to deliver?
MessageBird returns specific error codes for failed deliveries. Common failures include invalid numbers (error code 1), opted-out recipients (error code 103), and unregistered sender IDs (error code 104). Implement retry logic for temporary failures (like phones being turned off) and remove permanently failed numbers from your lists. Use delivery webhooks to monitor real-time status updates.
How do I set up delivery tracking for bulk SMS campaigns?
Configure MessageBird delivery webhooks in your dashboard under Developers → Webhooks. Set your webhook URL (e.g.,
https://your-domain.com/api/webhooks/delivery-status) and select Message Status Updates. MessageBird will POST delivery status changes to your endpoint in real-time, allowing you to trackdelivered,failed,expired, and other statuses without polling the API.Can I schedule bulk SMS messages for future delivery with MessageBird?
Yes, MessageBird supports scheduled message delivery. Add a
scheduledDatetimeparameter to your API request in ISO 8601 format (e.g.,2025-01-20T14:30:00Z). This is useful for time-zone-optimized campaigns, appointment reminders, and promotional messages during peak engagement hours.What's the difference between GSM-7 and UCS-2 encoding for SMS?
GSM-7 encoding supports 160 characters per SMS segment and includes standard Latin characters, numbers, and basic punctuation. UCS-2 encoding supports 70 characters per segment and is required for Unicode characters like emojis, Chinese/Arabic text, and special symbols. UCS-2 messages cost more due to fewer characters per segment. Use the
calculateSegments()function in this tutorial to estimate costs before sending.How do I register a sender ID for bulk SMS in the United States?
US A2P (Application-to-Person) messaging requires 10DLC registration through MessageBird. The process takes 2–4 weeks and includes business verification, use case approval, and brand registration. Without 10DLC registration, your messages may be blocked or heavily filtered by US carriers. Check MessageBird's documentation for country-specific sender ID requirements.
What's the best architecture for sending millions of SMS messages?
For campaigns exceeding 100,000 recipients, use a job queue system like BullMQ with Redis or AWS SQS. Split recipients into batches of 10,000–50,000 per job, process jobs asynchronously with worker processes, implement retry logic for failed batches, and monitor queue depth and processing lag. This architecture allows horizontal scaling and prevents API rate limit errors while maintaining high throughput.
Launch Your Production Bulk SMS System
You now have a complete, production-ready bulk SMS broadcasting system built with MessageBird, Node.js, and Express. This implementation provides everything needed to send high-volume SMS campaigns reliably and efficiently.
Your system includes intelligent batching, rate limiting, comprehensive error handling, delivery tracking, and phone number validation—all essential components for enterprise-grade bulk messaging.
What You Built:
Next Steps to Enhance Your System:
Related Guides:
Additional Resources:
You're ready to start sending bulk SMS messages efficiently and reliably using MessageBird!