code examples
code examples
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.
How to Send Bulk SMS with MessageBird Node.js and Express (2025 Guide)
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)
- Rate limiting and retry logic
- Delivery status tracking
- Error handling and logging
Prerequisites:
- Node.js v18+ installed
- MessageBird account with API key
- Basic knowledge of Express.js and async/await
- Understanding of REST API concepts
Table of Contents:
- What Is Bulk SMS Broadcasting?
- Set Up Your MessageBird Account
- Initialize Your Node.js Project
- Build the Bulk SMS Broadcasting Endpoint
- How Batching and Rate Limiting Work
- Track Message Delivery Status
- Handle Errors and Failed Deliveries
- Optimize Bulk SMS Performance
- Validate and Format Phone Numbers
- Test Your Bulk SMS System
- Deploy Your Bulk SMS System to Production
- FAQ: Bulk SMS with MessageBird, Node.js, and Express
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:
- 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.
Step 1: Create MessageBird Account
- Visit MessageBird Dashboard
- Complete the registration process
- Verify your email address
Step 2: Get Your API Key
- Log in to your MessageBird dashboard
- Navigate to Developers → API access
- Copy your Live API Key (starts with
live_) - For testing, use your Test API Key (starts with
test_)
Important Security Notes:
- Never commit API keys to version control
- Store keys in environment variables
- Use test keys during development
- Rotate keys periodically for security
MessageBird Pricing Overview:
MessageBird charges per message segment sent. Costs vary by destination country:
- US/Canada: $0.0075–$0.015 per SMS segment
- Europe: $0.02–$0.08 per SMS segment
- Asia-Pacific: $0.015–$0.12 per SMS segment
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:
npm install express messagebird dotenv express-validator express-rate-limitPackage 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 limiting
Create Environment File (.env):
MESSAGEBIRD_API_KEY=your_live_or_test_api_key_here
PORT=3000
NODE_ENV=developmentAdd .env to .gitignore:
echo ".env" >> .gitignore
echo "node_modules/" >> .gitignoreProject Structure:
messagebird-bulk-sms/
├── .env
├── .gitignore
├── package.json
├── server.js
├── utils/
│ ├── phone.js
│ └── logger.js
└── tests/
└── sms.test.js
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):
- 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:
- Theoretical maximum: 25,000 recipients/second (500 × 50)
- Practical recommendation: 10,000–15,000 recipients/second with 1-second delays
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:
- 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 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 permanently
Create 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:
- Navigate to Developers → Webhooks
- Create new webhook
- Set URL:
https://your-domain.com/api/webhooks/delivery-status - Select event: Message Status Updates
Webhook 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:
- 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
- Retry Logic: Automatically retry temporary failures (carrier unavailable, phone off)
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:
| 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 | Retry after 30–60 minutes |
| 9 | EC_ILLEGAL_SUBSCRIBER | The recipient blocked messaging services | Remove from your recipient list permanently |
| 21 | EC_FACILITY_NOT_SUPPORTED | The carrier doesn't support SMS | Try alternative channels (voice, email) |
| 103 | EC_SUBSCRIBER_OPTEDOUT | The recipient opted out of messages | Remove immediately and update your database |
| 104 | EC_SENDER_UNREGISTERED | Your sender ID isn't registered in this country | Register sender ID with local authorities |
Implement 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:
- 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.
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:
- 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:
npm install libphonenumber-jsImplement 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:
- US:
+12025550123(country code 1 + 10-digit number) - UK:
+447911123456(country code 44 + mobile number) - Netherlands:
+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:
- GSM-7 encoding: 160 characters per SMS segment (standard Latin characters)
- UCS-2 encoding: 70 characters per SMS segment (Unicode, emojis, special characters)
- Concatenated messages: Messages exceeding limits split into multiple segments, each charged separately
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:
- Alphanumeric sender IDs: Maximum 11 characters, recipients cannot reply
- Numeric sender IDs: Must be a valid phone number in E.164 format, supports two-way messaging
- Country restrictions: Some countries require sender ID pre-registration (check MessageBird's country-specific guidelines)
Sender ID Registration by Country:
| Country | Registration Required | Processing Time | Additional Info |
|---|---|---|---|
| United States | Yes (10DLC registration) | 2–4 weeks | Mandatory for A2P messaging |
| United Kingdom | No | Immediate | Alphanumeric sender IDs allowed |
| India | Yes | 4–6 weeks | DLT registration required |
| China | Yes | 6–8 weeks | Strict content regulations |
| Saudi Arabia | Yes | 2–3 weeks | Wasel platform registration |
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:
- Single batch: 10–20 recipients (tests basic functionality)
- Multiple batches: 100–200 recipients (tests batching logic)
- Large campaign: 1,000+ recipients (tests rate limiting and performance)
- Invalid numbers: Mix of valid and invalid phone numbers (tests validation)
- Error handling: Simulate API failures (tests retry logic)
- Concurrent requests: Multiple campaigns simultaneously (tests resource management)
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:
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"]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)
npm install bullmq ioredisOption 2: AWS SQS for Serverless
npm install @aws-sdk/client-sqsOption 3: RabbitMQ for Enterprise
npm install amqplibMonitoring 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:
- Datadog: Application performance monitoring
- Sentry: Error tracking and alerting
- CloudWatch: AWS infrastructure monitoring
- 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
- Rate limiting respecting MessageBird's 500 req/s limit
- Comprehensive error handling with retry logic
- Delivery status tracking and webhook integration
- E.164 phone number validation
- Production deployment configuration
Next Steps to Enhance Your System:
- Add Database Persistence: Store message history, delivery statuses, and opt-out preferences in PostgreSQL or MongoDB
- Implement User Authentication: Secure your API with JWT tokens or API key authentication
- Build Admin Dashboard: Create a web interface for campaign management and analytics
- Add Template Support: Store message templates with variable substitution for personalization
- Set Up Scheduled Campaigns: Allow users to schedule bulk sends for optimal delivery times
- Implement A/B Testing: Test different message variations to improve engagement rates
- Add Cost Tracking: Monitor MessageBird API usage and calculate campaign costs
Related Guides:
- E.164 Phone Number Format Complete Guide
- 10DLC SMS Registration for US Businesses
- MessageBird SMS Pricing by Country
- Two-Way SMS with MessageBird Node.js
Additional Resources:
- MessageBird API Documentation
- MessageBird Node.js SDK GitHub
- Express.js Best Practices
- libphonenumber-js Documentation
You're ready to start sending bulk SMS messages efficiently and reliably using MessageBird!
Frequently Asked Questions
how to send bulk sms with node.js
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.
what is messagebird used for in node sms
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.
why use express for bulk sms node.js
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.
when should I batch sms messages
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.
how to set up messagebird api key in node.js
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.
what is the role of express-validator
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.
why is rate limiting important for bulk sms
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.
when to use a job queue for sms sending
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.
what database schema is suitable for sms app
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.
how to handle messagebird api errors in node
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.
what is the MESSAGEBIRD_ORIGINATOR variable
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.
how to secure a bulk sms api endpoint
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.
how to implement retries for failed sms messages
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).
why does batch size matter for bulk sms
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.
can I use an alphanumeric sender ID with messagebird
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.