How to Send SMS with Node.js and Express: MessageBird API Tutorial (2025)
Learn how to send SMS messages using Node.js, Express.js, and MessageBird API. Step-by-step tutorial with code examples, E.164 phone validation, error handling, and production deployment tips. Updated for 2025.
SDK Maintenance Status: The messagebird npm package (v4.0.1) has not been updated since 2022 and is no longer actively maintained. Consider this when choosing an SMS provider for new projects.
Bird Rebranding: MessageBird rebranded as "Bird" in February 2024. While the legacy API and SDK remain functional, the company now operates as Bird (bird.com) with updated APIs and services. Check Bird's API documentation for current offerings.
Content Note: This guide covers MessageBird integration with Express.js. Despite the filename reference to Next.js and NextAuth, this article focuses on Express.js implementation.
Express.js Version: This guide is compatible with Express.js 5.x (requires Node.js ≥18).
This comprehensive tutorial shows you how to send SMS messages programmatically using Node.js, Express.js, and the MessageBird SMS API. Whether you're building two-factor authentication, notification systems, or marketing campaigns, you'll learn everything from initial setup to production deployment.
By the end of this guide, you'll have a working Express API endpoint that sends SMS messages to any phone number worldwide using MessageBird's reliable messaging infrastructure.
Project Goals:
Create a Node.js Express server with SMS capabilities
Integrate the MessageBird Node.js SDK for SMS delivery
Securely manage API credentials using environment variables
Build a REST API endpoint to trigger SMS sending
Implement phone number validation and error handling
Provide guidance on testing and production deployment
Technology Stack:
Node.js: JavaScript runtime environment for server-side development
Express.js: Minimal and flexible Node.js web application framework for building REST APIs
MessageBird SMS API: Cloud-based SMS gateway providing reliable message delivery worldwide via the official Node.js SDK
dotenv: Secure environment variable management for API keys and credentials
System Architecture:
The system consists of a User/Client (e.g., using cURL or a frontend application) making an HTTP request to the Node.js/Express App's API endpoint (e.g., /send-sms). The Express application receives the request, validates the input (recipient number, message body), and uses the MessageBird Node.js SDK. The SDK, configured with the API key securely loaded from an .env file, handles authentication and communicates with the MessageBird API. The MessageBird API processes the request and sends the SMS message to the recipient's mobile phone.
Prerequisites:
Node.js and npm: Install Node.js 18 or higher (Express.js 5.x requires Node.js ≥18). Download Node.js
MessageBird API Key: Obtain your API key from the MessageBird Dashboard for authentication
Note: MessageBird provides both live and test API keys. Test keys validate your API integration (checking authentication and request structure) without sending actual SMS messages or incurring costs, but they won't result in a message arriving on a phone. This guide uses a live key to demonstrate the full sending process.
MessageBird Virtual Number: Purchase or use a trial virtual mobile number (VMN) capable of sending SMS from the MessageBird Dashboard – this will be your originator number
Basic Terminal/Command Line Knowledge: Familiarity with navigating directories and running commands
Code Editor: Such as VS Code, Sublime Text, or Atom
1. Setting Up Your Node.js SMS Project
Initialize your Node.js project and install the necessary dependencies for sending SMS messages.
Create Project Directory:
Open your terminal and create a new directory for your project, then navigate into it.
Bash
mkdir node-messagebird-sms
cd node-messagebird-sms
Initialize Node.js Project:
Create a package.json file to track your project's metadata and dependencies.
Bash
npm init -y
(The -y flag accepts the default settings)
Install Dependencies:
Install Express for the web server, the MessageBird SDK, and dotenv for managing environment variables.
Bash
npm install express messagebird dotenv
Create Project Files:
Create the main application file and files for environment variables and Git ignore rules.
Bash
touch index.js .env .gitignore
index.js: Main application code
.env: Stores sensitive information like API keys (configure later)
.gitignore: Specifies files Git should ignore (like .env and node_modules)
Configure .gitignore:
Open .gitignore and add these lines to prevent committing sensitive data and local dependencies:
Set Up Basic Express Server (index.js):
Open index.js and add the basic structure for an Express application.
JavaScript
// index.jsrequire('dotenv').config(); // Load environment variables from .env file FIRSTconst express = require('express');
const app = express();
constPORT = process.env.PORT || 3000; // Use port from env var or default to 3000// Middleware to parse JSON request bodies
app.use(express.json());
// Simple root route
app.get('/', (req, res) => {
res.send('MessageBird SMS Sender API is running!');
});
// Start the server
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Why dotenv first? Call require('dotenv').config() at the very top to ensure environment variables are loaded before any other code tries to access them.
Why express.json()? This middleware is crucial for parsing incoming requests with JSON payloads (which you'll use for your API endpoint).
Run the Basic Server:
Test if the basic setup works.
Bash
node index.js
You should see Server listening on port 3000 in your console. Open http://localhost:3000 in your browser to see the welcome message. Press Ctrl+C to stop the server.
2. Integrating the MessageBird SMS API
Configure the MessageBird SDK and obtain the necessary credentials for sending SMS.
Navigate to the Developers section in the left sidebar
Click on the API access (REST) tab
If you don't have a key, click Add access key. Create or use a Live API Key if you intend to send real messages (refer to the Prerequisites note about test vs. live keys)
Copy the generated Live API Key – treat this key like a password and keep it secret
Obtain MessageBird Virtual Number (Originator):
In the MessageBird Dashboard, navigate to Numbers in the sidebar
If you don't have a number, click Buy a number
Select your country and ensure the SMS capability is enabled
Choose a number and complete the purchase (or use a trial number if available)
Copy the full number in E.164 format (e.g., +12025550149, +442071838750) – this will be your sender ID (originator)
Configure Environment Variables (.env):
Open the .env file and add your API key and originator number. Replace the placeholder values with your actual credentials.
Bash
# .env# Your LIVE MessageBird API Key
MESSAGEBIRD_API_KEY=YOUR_LIVE_API_KEY_HERE
# Your purchased MessageBird Virtual Number (in E.164 format)
MESSAGEBIRD_ORIGINATOR_NUMBER=+12345678900
MESSAGEBIRD_API_KEY: The live access key copied from the MessageBird dashboard – authenticates your requests
MESSAGEBIRD_ORIGINATOR_NUMBER: The virtual number you purchased or obtained – the 'sender' number that recipients will see (subject to country regulations). Note: In some countries, using an alphanumeric sender ID (like MyCompany) is possible, but it often requires pre-registration and isn't universally supported. Using a virtual number is generally more reliable for programmatic sending.
Initialize MessageBird Client (index.js):
Modify index.js to initialize the MessageBird client using the API key from environment variables.
JavaScript
// index.jsrequire('dotenv').config();
const express = require('express');
// --- Add MessageBird initialization ---const messagebirdApiKey = process.env.MESSAGEBIRD_API_KEY;
if (!messagebirdApiKey) {
console.error('FATAL ERROR: MESSAGEBIRD_API_KEY environment variable not set.');
process.exit(1); // Exit if the key is missing
}
const messagebird = require('messagebird')(messagebirdApiKey);
// --- End initialization ---const app = express();
constPORT = process.env.PORT || 3000;
app.use(express.json());
// --- Add Originator check ---if (!process.env.MESSAGEBIRD_ORIGINATOR_NUMBER) {
console.warn('WARNING: MESSAGEBIRD_ORIGINATOR_NUMBER environment variable not set. Sending might fail or use default "MessageBird".');
// You might want to exit here too depending on requirements: // process.exit(1);
}
// --- End check ---
app.get('/', (req, res) => {
res.send('MessageBird SMS Sender API is running!');
});
// Placeholder for the SMS sending route (coming next)
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Why check environment variables? This simple check prevents the application from starting without essential configuration, making debugging easier.
3. Building the SMS Sending API Endpoint
Create the API endpoint that handles requests to send SMS messages programmatically.
Create the API Endpoint (/send-sms):
Add a new POST route to index.js to handle SMS sending requests.
JavaScript
// index.js// ... (previous code: require statements, app init, middleware, checks) ...
app.get('/', (req, res) => {
res.send('MessageBird SMS Sender API is running!');
});
// --- Add SMS Sending Route ---
app.post('/send-sms', (req, res) => {
const { recipient, message } = req.body; // Extract recipient and message from request body// 1. Input Validationif (!recipient || !message) {
console.warn('Send SMS failed: Missing recipient or message', { body: req.body });
return res.status(400).json({ error: 'Missing required fields: recipient and message' });
}
// E.164 validation: Must start with '+' followed by country code (1-9) and 3-14 additional digits// Total length: 4-15 digits (including country code)// This matches MessageBird's required formatif (!/^\+[1-9]{1}[0-9]{3,14}$/.test(recipient)) {
console.warn('Send SMS failed: Invalid recipient format', { recipient });
return res.status(400).json({ error: 'Invalid recipient format. Use E.164 format (e.g., +12025551234).' });
}
const originator = process.env.MESSAGEBIRD_ORIGINATOR_NUMBER || 'MessageBird'; // Use configured number or default// 2. Prepare Message Parametersconst params = {
originator: originator,
recipients: [recipient], // Must be an arraybody: message,
};
// 3. Send Message via MessageBird SDKconsole.log(`Attempting to send SMS to ${recipient} from ${originator}`);
messagebird.messages.create(params, (err, response) => {
if (err) {
// 4. Handle Errorsconsole.error('MessageBird API Error:', err);
let statusCode = 500;
let errorMessage = 'Failed to send SMS due to an internal server error.';
let errorCode = null;
if (err.errors && err.errors.length > 0) {
// Try to get specific error details from MessageBird responseconst firstError = err.errors[0];
errorCode = firstError.code;
errorMessage = `MessageBird Error ${errorCode}: ${firstError.description}`;
// Map common MessageBird error codes to HTTP status codes (selective mapping)if ([2, 9, 21, 22].includes(errorCode)) statusCode = 400; // Bad requestif (errorCode === 7) statusCode = 402; // Payment Requiredif (errorCode === 10) statusCode = 401; // Unauthorized
} elseif (err.statusCode) {
statusCode = err.statusCode;
errorMessage = `Network or Client Error: ${err.message || 'Failed to communicate with MessageBird API'}`;
}
return res.status(statusCode).json({ error: errorMessage, errorCode: errorCode, details: err.errors || err.message });
}
// 5. Handle Successconsole.log('MessageBird API Success:', response);
const recipientInfo = response.recipients.items[0] || {};
res.status(200).json({
message: 'SMS submitted successfully!',
details: {
id: response.id,
status: recipientInfo.status, // e.g., 'sent', 'scheduled'statusDatetime: recipientInfo.statusDatetime,
recipient: recipientInfo.recipient,
}
});
});
});
// --- End SMS Sending Route ---
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Request Body: Expects a JSON body with recipient (E.164 format phone number) and message (the text content)
Input Validation: Basic checks ensure required fields are present and the recipient number has a valid format – crucial for security and preventing errors
Parameters: The params object maps directly to the requirements of the MessageBird messages.create function. recipients must be an array, even for a single number
messagebird.messages.create: The core SDK function – takes the parameters and a callback function
Callback Function: The callback receives err (if an error occurred during the API call) or response (on success)
Error Handling: If err exists, log it and return a relevant HTTP status code (e.g., 400 for bad input, 402 for balance issues, 500 for server/API errors) and error message. Attempt to parse specific errors from the MessageBird response
Success Handling: If successful, log the response and return a 200 status with details like the MessageBird message ID and initial status
4. Testing Your SMS API with cURL and Postman
Test the endpoint using a tool like curl or Postman to verify SMS sending functionality.
Start the Server:
Ensure your .env file is correctly configured with your live API key and originator number.
Bash
node index.js
Send a Test Request (using curl):
Open a new terminal window (leave the server running in the first one). Replace +1YOUR_PHONE_NUMBER with your actual mobile number in E.164 format.
Bash
curl -X POST http://localhost:3000/send-sms \
-H "Content-Type: application/json" \
-d '{
"recipient": "+1YOUR_PHONE_NUMBER",
"message": "Hello from Node.js and MessageBird!"
}'
Check the Response:
Success: You should receive a JSON response similar to this in your curl terminal, and the SMS should arrive on your phone shortly (standard SMS charges may apply).
Your server console (where node index.js is running) should also show the success logs.
Error: If something is wrong (e.g., invalid API key, incorrect recipient format, insufficient funds), you'll get an error response:
JSON
// Example: Invalid recipient format{"error":"Invalid recipient format. Use E.164 format (e.g., +12025551234)."}
JSON
// Example: MessageBird API error (e.g., bad key or insufficient balance){"error":"MessageBird Error 7: No balance","errorCode":7,"details":[/* ... detailed error object from MessageBird ... */]}
Check the server console logs for more detailed error information.
Testing with Postman:
Open Postman
Create a new request
Set the method to POST
Enter the URL: http://localhost:3000/send-sms
Go to the "Body" tab, select "raw", and choose "JSON" from the dropdown
Enter the JSON payload:
JSON
{"recipient":"+1YOUR_PHONE_NUMBER","message":"Testing SMS via Postman!"}
Click "Send". Observe the response in Postman and the logs in your server console
5. Error Handling and Logging Best Practices
While basic error handling using console.log/console.error works for development, production systems need more robustness. This section introduces improvements incorporated into the final code (Section 13).
Structured Logging: Switch from console.log to a dedicated logging library like winston or pino. This provides significant advantages:
Different Log Levels: Distinguish between informational messages (info), warnings (warn), and critical errors (error)
Structured Output: Logging in JSON format makes logs easier to parse, query, and analyze by log management systems (like Datadog, Splunk, ELK stack)
Configurable Transports: Send logs to different destinations (console, files, external services) based on environment or severity
Example winston setup (as used in Section 13):
Bash
npm install winston
JavaScript
// Example setup with Winston (replaces console.log/error)const winston = require('winston');
const logger = winston.createLogger({
level: 'info', // Log only info, warn, error by defaultformat: winston.format.json(), // Log as JSONdefaultMeta: { service: 'sms-sender' }, // Add service contexttransports: [
// Write errors to error.lognew winston.transports.File({ filename: 'error.log', level: 'error' }),
// Write all logs (info and above) to combined.lognew winston.transports.File({ filename: 'combined.log' }),
],
});
// Also log to the console in non-production environmentsif (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(), // Use simpler format for console
}));
}
// Usage (replaces console.log/error):// logger.error('MessageBird API Error:', { error: err });// logger.info('SMS submitted successfully', { recipient: recipient, messageId: response.id });
Retry Mechanisms: For critical messages where transient network errors are unacceptable, implement a retry strategy (e.g., using libraries like async-retry) with exponential backoff. This adds complexity but improves reliability
6. Database Schema and Data Layer
For this basic SMS sending API endpoint, a database is not strictly required – messages are sent based on direct API requests.
However, if you were building a more comprehensive application (e.g., tracking message history, handling replies, managing user preferences), you would need a database (like PostgreSQL, MongoDB, MySQL). This would involve:
Defining Schemas/Models: Structure for storing message details (ID, recipient, sender, body, status, timestamps), user data, etc.
Data Access Layer: Using an ORM/ODM (like Sequelize for SQL, Mongoose for MongoDB) or plain database drivers to interact with the database
Storing Message Status: Updating message status based on delivery reports received via MessageBird webhooks (a more advanced topic)
7. Security Features
Security is critical when building APIs that handle potentially sensitive information.
Environment Variables:Never commit your .env file or hardcode API keys/credentials in your source code. Use dotenv for local development and your deployment platform's secure environment variable management in production. Ensure .env is listed in your .gitignore
Input Validation: Rigorously validate all input from external sources (the request body in this case)
Recipient: Ensure it adheres to the E.164 format. The regex /^\+[1-9]{1}[0-9]{3,14}$/ validates the format properly – it requires a + sign, followed by a country code digit (1–9), and 3–14 additional digits, for a total length of 4–15 digits. For maximum accuracy, consider libraries like google-libphonenumber (though potentially overkill for a basic service)
Message Body: Check for reasonable length. SMS messages have character limits (typically 160 GSM-7 characters, 70 Unicode characters per part). Very long messages are split and cost more. Reject empty or excessively long messages. Sanitize content if it could contain malicious input, especially if derived from user-generated content elsewhere in your system
Rate Limiting: Protect your API endpoint (and your MessageBird budget) from abuse (accidental or malicious). Use middleware like express-rate-limit to limit the number of requests from a single IP address within a time window. This is implemented in the final code (Section 13)
Bash
npm install express-rate-limit
JavaScript
// Example implementation (see Section 13 for full context)const rateLimit = require('express-rate-limit');
const smsLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutesmax: 10, // Limit each IP to 10 requests per windowmessage: { error: 'Too many SMS requests created from this IP, please try again after 15 minutes' }
});
app.post('/send-sms', smsLimiter, /* route handler */ );
Authentication/Authorization (If Applicable): If this API is not intended for public use, secure it. Ensure only authenticated and authorized clients (users, other services) can call the /send-sms endpoint. Methods include API keys specific to your service, JWT tokens, session authentication, OAuth, etc.
HTTPS: Always deploy your application behind a proxy that terminates SSL/TLS, ensuring traffic between clients and your server is encrypted (HTTPS). Most modern hosting platforms handle this automatically
8. SMS Delivery Best Practices and Special Cases
Originator Restrictions: Alphanumeric sender IDs (e.g., "MyCompany") are not supported in all countries (like the US and Canada) and often require pre-registration. Using a purchased virtual number (in E.164 format) as the originator is generally the most reliable approach for global sending. Always check Bird's country restrictions documentation (formerly MessageBird) for current regulations
Character Limits & Encoding: Standard SMS (using GSM-7 encoding) allows 160 characters. Using characters outside this set (like many emojis or non-Latin scripts) forces Unicode (UCS-2) encoding, reducing the limit to 70 characters per SMS part. MessageBird automatically handles splitting longer messages into multiple parts (concatenated SMS), but you are billed for each part. Inform users or truncate messages appropriately if length/cost is a concern
Invalid or Non-Mobile Numbers: The E.164 validation helps, but MessageBird's API will also perform checks. An invalid or non-existent number will result in an API error (often code: 2 – invalid parameter) or a failed delivery status later
Blocked Numbers/Opt-outs: Implement mechanisms to handle user opt-outs (e.g., responding to "STOP" messages, maintaining a suppression list). Sending to users who have opted out can violate regulations (like TCPA in the US). MessageBird offers features to help manage opt-outs
9. Performance Optimizations
For this specific function (a single API call per request), performance bottlenecks within the Node.js code itself are less likely than external factors.
Asynchronous Nature: Node.js is non-blocking. The messagebird.messages.create call is asynchronous, meaning your server isn't stalled waiting for MessageBird's response and can handle other requests concurrently. This is inherently performant for I/O-bound tasks
External API Latency: The primary performance factor will be the network latency and processing time of the MessageBird API itself
Load Testing: If high throughput is expected, use load testing tools (e.g., k6, artillery, autocannon) to simulate traffic. This helps identify potential limits (CPU, memory, network bandwidth) or issues with dependencies (like rate limits)
Caching: Not typically applicable for sending unique SMS messages. Caching might be relevant if you were frequently fetching message statuses via API calls, but using MessageBird's webhooks for status updates is generally more efficient
10. Production Monitoring and Observability
To ensure your service runs reliably in production:
Health Checks: Implement a simple /health endpoint that returns a 200 OK status. Monitoring systems (like uptime checkers, Kubernetes liveness probes) can ping this endpoint to verify the service is operational (added in Section 13)
JavaScript
// Example health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: newDate().toISOString() });
});
Application Performance Monitoring (APM): Use APM tools (e.g., Datadog, New Relic, Dynatrace, Sentry APM, OpenTelemetry) to monitor key metrics like request latency, throughput (requests per minute), error rates, CPU/memory usage. These tools help diagnose performance issues and bottlenecks
Structured Logging: As discussed in Section 5, use structured logging (e.g., Winston sending JSON to a log aggregator) for effective analysis and troubleshooting
Error Tracking: Integrate dedicated error tracking services (Sentry, Bugsnag, Rollbar) to capture, aggregate, alert on, and provide context for runtime exceptions in your Node.js application
MessageBird Dashboard: Regularly utilize the MessageBird Dashboard ("Messaging → Logs"). It provides invaluable insights into the status of each message (e.g., sent, delivered, failed), delivery timestamps, error codes if failures occur, and associated costs. This is crucial for debugging specific SMS delivery problems
11. Troubleshooting and Caveats
Common issues and things to be aware of:
Error: Authentication failed or Request not allowed (incorrect login details…) (Code 2, 10, 21):
Cause: Invalid MESSAGEBIRD_API_KEY in your environment variables. It might be mistyped, have extra whitespace, be a test key when a live one is needed, or belong to a different account
Solution: Double-check the API key in your .env file (or production environment variables) against the key shown in the MessageBird Dashboard (Developers → API access). Ensure you are using the correct key type (live/test). Restart your application after making changes
Error: recipient is invalid or similar parameter errors (Code 2, 22):
Cause: The phone number provided in the recipient field is not in the valid E.164 format (+ sign followed by country code and number, no spaces or dashes)
Solution: Verify the input number format. Ensure your validation logic (regex or library) correctly enforces E.164. Check the data being sent in the request body
Error: originator is invalid (Code 2, 21):
Cause: The MESSAGEBIRD_ORIGINATOR_NUMBER in your environment variables is incorrect, not associated with your MessageBird account, formatted incorrectly, or disallowed as a sender ID in the destination country
Solution: Confirm the number matches a number listed under Numbers in your MessageBird Dashboard and is in E.164 format. If using an alphanumeric ID, check country regulations and registration status
Error: No balance (Code 7):
Cause: Your MessageBird account lacks sufficient credits to cover the cost of the SMS message
Solution: Add funds to your account through the MessageBird Dashboard (Billing section)
SMS Not Received by Recipient:
Cause: Can be varied: transient carrier delays, recipient's phone is off/out of service, incorrect number provided, carrier spam filtering, number is blocked, MessageBird platform issues (rare)
Solution:
Verify the recipient number is absolutely correct
Check the MessageBird Dashboard logs (Messaging → Logs) for that specific message. Look at its status (delivered, failed, expired) and any error details provided
Wait a few minutes – delivery isn't always instantaneous
Test sending to a different number or carrier if possible to isolate the issue
If logs show delivered but the user insists they didn't receive it, the issue likely lies with the recipient's device or carrier
If logs show failed with an error code, investigate that specific code
Contact MessageBird support if issues persist despite logs showing sent or if you suspect a platform problem
Caveat: Cost: Sending SMS messages incurs costs based on the destination country and message volume. Monitor your MessageBird balance and be aware of pricing. Test keys are free but don't send real messages
Caveat: Delivery Status: The API response provides an initial status (like sent or scheduled). The final delivery confirmation (delivered or failed) happens asynchronously. To get these final statuses reliably, you need to implement webhooks to receive Delivery Reports from MessageBird (see "Next Steps"). The code examples provided map common API call errors, but a production system might need more comprehensive mapping or handling logic based on MessageBird's documentation
12. Deployment and CI/CD
To deploy your Node.js Express application:
Choose a Hosting Platform: Many options exist, including:
PaaS (Platform-as-a-Service): Heroku, Render, Fly.io (easy deployment from Git)
VPS/Servers: AWS EC2, Google Compute Engine, DigitalOcean Droplets (require more manual setup)
Prepare package.json: Ensure your scripts include a start command
JSON
// package.json excerpt{"scripts":{"start":"node index.js","test":"echo \"Error: no test specified\" && exit 1"}}
Environment Variables: Configure MESSAGEBIRD_API_KEY and MESSAGEBIRD_ORIGINATOR_NUMBER securely within your chosen platform's settings. Never commit the .env file to Git. Also set NODE_ENV=production for performance and security benefits
CI/CD Pipeline (Optional but Recommended): Automate testing, building, and deployment:
Use services like GitHub Actions, GitLab CI/CD, CircleCI, or Jenkins
Define workflows to run tests (npm test) and deploy on successful builds to your hosting platform
Example GitHub Actions workflow (.github/workflows/deploy.yml):
YAML
name:DeploytoProductionon:push:branches: [main]
jobs:deploy:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v3-uses:actions/setup-node@v3with:node-version:'18'-run:npmci-run:npmtest# Add your deployment steps here (e.g., Heroku deploy, AWS deploy)
Health Check Endpoint: Ensure your application has a /health endpoint (see Section 10) that monitoring systems can use to verify service availability
13. Complete Production-Ready Code
Here's the full index.js with all best practices integrated:
To extend your SMS application beyond basic sending:
Delivery Reports (DLRs) via Webhooks: Implement webhook endpoints to receive real-time delivery status updates from MessageBird. This allows you to track when messages are delivered, failed, or expired
Inbound SMS Handling: Set up webhooks to receive incoming SMS messages, enabling two-way communication (e.g., for customer support, surveys, or STOP/opt-out processing)
Message Scheduling: Schedule SMS messages to be sent at specific times using MessageBird's scheduling parameters
Bulk SMS Sending: Optimize for sending large volumes of messages by batching requests or using MessageBird's bulk sending features
Database Integration: Store message history, track delivery statuses, and manage contact lists using a database (PostgreSQL, MongoDB, etc.)
Template Management: Create reusable message templates with variable substitution for personalized bulk messaging
Multi-Channel Messaging: Extend beyond SMS to include WhatsApp, Voice, or Email using MessageBird's (Bird's) unified API
A/B Testing: Test different message variations to optimize engagement and conversion rates
Ready to send SMS with Node.js? Follow this tutorial to build your own production-ready SMS API using Express.js and MessageBird. Whether you're implementing authentication codes, notifications, or marketing campaigns, this comprehensive guide covers everything you need to know.
Frequently asked questions
Set up an Express server, install the MessageBird SDK and dotenv, configure your .env file with API keys, and create a /send-sms endpoint to handle requests. This endpoint will use the MessageBird SDK to send messages based on the provided recipient and message body. The article provides a detailed walkthrough of this process.
MessageBird is a messaging platform that provides APIs for various communication channels, including SMS. Its Node.js SDK simplifies the process of integrating SMS functionality into your Node.js and Express applications. You'll use their REST API via the official Node.js SDK.
Dotenv is essential for securely managing your MessageBird API keys. It loads environment variables from a .env file, preventing you from hardcoding sensitive credentials directly into your application code, thus enhancing security.
Always validate recipient phone numbers upon receiving them in your /send-sms endpoint. This prevents sending messages to invalid numbers, which can result in errors or unnecessary charges. Use a regex or a specialized library like google-libphonenumber for comprehensive validation.
Alphanumeric sender IDs are not universally supported and may be restricted or require pre-registration in certain countries. For reliable global SMS sending, use a purchased virtual number as the originator, which is supported across more regions.
Create a POST route (/send-sms) in your Express app that extracts recipient and message data from the request body. Validate the inputs, prepare the parameters, and then use messagebird.messages.create() to send the SMS through the MessageBird API.
The express.json() middleware is crucial for parsing incoming request bodies in JSON format, allowing your Express application to access the recipient and message data submitted to the /send-sms endpoint.
Implement robust error handling in your /send-sms route's callback function. Check for errors returned by the MessageBird API and respond with appropriate HTTP status codes and informative error messages. Logging errors with details is also crucial for debugging.
Storing API keys in environment variables, managed by dotenv locally and through your deployment platform in production, ensures that sensitive credentials are not exposed in your codebase or version control system.
Use tools like curl or Postman to send POST requests to your /send-sms endpoint with test data. Check the server console for logs and the response for success or error messages. Verify that the SMS arrives on the recipient's phone (if using a live API key).
For production applications, switch from console.log to a structured logging library like Winston. This allows for different log levels, JSON-formatted output for easier analysis, and configurable transports to send logs to various destinations.
Common errors include code 2 for invalid parameters (like recipient format or originator), code 7 for insufficient balance, and code 10 for authentication failures (invalid API key). Refer to the MessageBird API documentation for a complete list of error codes.
Use middleware like express-rate-limit to restrict the number of requests from a single IP address within a specified time window, preventing abuse and protecting your MessageBird budget. Configure the middleware with appropriate limits and error messages.
Essential security measures include using environment variables for API keys, validating all user inputs, implementing rate limiting, enforcing HTTPS, and adding authentication/authorization if the API is not public.
Check the MessageBird Dashboard logs for message status and any error codes. Verify the recipient number, consider carrier delays, and contact MessageBird support if necessary.
This comprehensive tutorial shows you how to send SMS messages programmatically using Node.js, Express.js, and the MessageBird SMS API. Whether you're building two-factor authentication, notification systems, or marketing campaigns, you'll learn everything from initial setup to production deployment.
By the end of this guide, you'll have a working Express API endpoint that sends SMS messages to any phone number worldwide using MessageBird's reliable messaging infrastructure.
Project Goals:
Technology Stack:
System Architecture:
The system consists of a User/Client (e.g., using cURL or a frontend application) making an HTTP request to the Node.js/Express App's API endpoint (e.g.,
/send-sms). The Express application receives the request, validates the input (recipient number, message body), and uses the MessageBird Node.js SDK. The SDK, configured with the API key securely loaded from an.envfile, handles authentication and communicates with the MessageBird API. The MessageBird API processes the request and sends the SMS message to the recipient's mobile phone.Prerequisites:
originatornumber1. Setting Up Your Node.js SMS Project
Initialize your Node.js project and install the necessary dependencies for sending SMS messages.
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
mkdir node-messagebird-sms cd node-messagebird-smsInitialize Node.js Project: Create a
package.jsonfile to track your project's metadata and dependencies.(The
-yflag accepts the default settings)Install Dependencies: Install Express for the web server, the MessageBird SDK, and
dotenvfor managing environment variables.Create Project Files: Create the main application file and files for environment variables and Git ignore rules.
touch index.js .env .gitignoreindex.js: Main application code.env: Stores sensitive information like API keys (configure later).gitignore: Specifies files Git should ignore (like.envandnode_modules)Configure
.gitignore: Open.gitignoreand add these lines to prevent committing sensitive data and local dependencies:Set Up Basic Express Server (
index.js): Openindex.jsand add the basic structure for an Express application.// index.js require('dotenv').config(); // Load environment variables from .env file FIRST const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; // Use port from env var or default to 3000 // Middleware to parse JSON request bodies app.use(express.json()); // Simple root route app.get('/', (req, res) => { res.send('MessageBird SMS Sender API is running!'); }); // Start the server app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); });dotenvfirst? Callrequire('dotenv').config()at the very top to ensure environment variables are loaded before any other code tries to access them.express.json()? This middleware is crucial for parsing incoming requests with JSON payloads (which you'll use for your API endpoint).Run the Basic Server: Test if the basic setup works.
You should see
Server listening on port 3000in your console. Openhttp://localhost:3000in your browser to see the welcome message. PressCtrl+Cto stop the server.2. Integrating the MessageBird SMS API
Configure the MessageBird SDK and obtain the necessary credentials for sending SMS.
Obtain MessageBird API Key:
Developerssection in the left sidebarAPI access (REST)tabAdd access key. Create or use a Live API Key if you intend to send real messages (refer to the Prerequisites note about test vs. live keys)Obtain MessageBird Virtual Number (Originator):
Numbersin the sidebarBuy a numberSMScapability is enabled+12025550149,+442071838750) – this will be your sender ID (originator)Configure Environment Variables (
.env): Open the.envfile and add your API key and originator number. Replace the placeholder values with your actual credentials.# .env # Your LIVE MessageBird API Key MESSAGEBIRD_API_KEY=YOUR_LIVE_API_KEY_HERE # Your purchased MessageBird Virtual Number (in E.164 format) MESSAGEBIRD_ORIGINATOR_NUMBER=+12345678900MESSAGEBIRD_API_KEY: The live access key copied from the MessageBird dashboard – authenticates your requestsMESSAGEBIRD_ORIGINATOR_NUMBER: The virtual number you purchased or obtained – the 'sender' number that recipients will see (subject to country regulations). Note: In some countries, using an alphanumeric sender ID (likeMyCompany) is possible, but it often requires pre-registration and isn't universally supported. Using a virtual number is generally more reliable for programmatic sending.Initialize MessageBird Client (
index.js): Modifyindex.jsto initialize the MessageBird client using the API key from environment variables.// index.js require('dotenv').config(); const express = require('express'); // --- Add MessageBird initialization --- const messagebirdApiKey = process.env.MESSAGEBIRD_API_KEY; if (!messagebirdApiKey) { console.error('FATAL ERROR: MESSAGEBIRD_API_KEY environment variable not set.'); process.exit(1); // Exit if the key is missing } const messagebird = require('messagebird')(messagebirdApiKey); // --- End initialization --- const app = express(); const PORT = process.env.PORT || 3000; app.use(express.json()); // --- Add Originator check --- if (!process.env.MESSAGEBIRD_ORIGINATOR_NUMBER) { console.warn('WARNING: MESSAGEBIRD_ORIGINATOR_NUMBER environment variable not set. Sending might fail or use default "MessageBird".'); // You might want to exit here too depending on requirements: // process.exit(1); } // --- End check --- app.get('/', (req, res) => { res.send('MessageBird SMS Sender API is running!'); }); // Placeholder for the SMS sending route (coming next) app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); });3. Building the SMS Sending API Endpoint
Create the API endpoint that handles requests to send SMS messages programmatically.
Create the API Endpoint (
/send-sms): Add a newPOSTroute toindex.jsto handle SMS sending requests.// index.js // ... (previous code: require statements, app init, middleware, checks) ... app.get('/', (req, res) => { res.send('MessageBird SMS Sender API is running!'); }); // --- Add SMS Sending Route --- app.post('/send-sms', (req, res) => { const { recipient, message } = req.body; // Extract recipient and message from request body // 1. Input Validation if (!recipient || !message) { console.warn('Send SMS failed: Missing recipient or message', { body: req.body }); return res.status(400).json({ error: 'Missing required fields: recipient and message' }); } // E.164 validation: Must start with '+' followed by country code (1-9) and 3-14 additional digits // Total length: 4-15 digits (including country code) // This matches MessageBird's required format if (!/^\+[1-9]{1}[0-9]{3,14}$/.test(recipient)) { console.warn('Send SMS failed: Invalid recipient format', { recipient }); return res.status(400).json({ error: 'Invalid recipient format. Use E.164 format (e.g., +12025551234).' }); } const originator = process.env.MESSAGEBIRD_ORIGINATOR_NUMBER || 'MessageBird'; // Use configured number or default // 2. Prepare Message Parameters const params = { originator: originator, recipients: [recipient], // Must be an array body: message, }; // 3. Send Message via MessageBird SDK console.log(`Attempting to send SMS to ${recipient} from ${originator}`); messagebird.messages.create(params, (err, response) => { if (err) { // 4. Handle Errors console.error('MessageBird API Error:', err); let statusCode = 500; let errorMessage = 'Failed to send SMS due to an internal server error.'; let errorCode = null; if (err.errors && err.errors.length > 0) { // Try to get specific error details from MessageBird response const firstError = err.errors[0]; errorCode = firstError.code; errorMessage = `MessageBird Error ${errorCode}: ${firstError.description}`; // Map common MessageBird error codes to HTTP status codes (selective mapping) if ([2, 9, 21, 22].includes(errorCode)) statusCode = 400; // Bad request if (errorCode === 7) statusCode = 402; // Payment Required if (errorCode === 10) statusCode = 401; // Unauthorized } else if (err.statusCode) { statusCode = err.statusCode; errorMessage = `Network or Client Error: ${err.message || 'Failed to communicate with MessageBird API'}`; } return res.status(statusCode).json({ error: errorMessage, errorCode: errorCode, details: err.errors || err.message }); } // 5. Handle Success console.log('MessageBird API Success:', response); const recipientInfo = response.recipients.items[0] || {}; res.status(200).json({ message: 'SMS submitted successfully!', details: { id: response.id, status: recipientInfo.status, // e.g., 'sent', 'scheduled' statusDatetime: recipientInfo.statusDatetime, recipient: recipientInfo.recipient, } }); }); }); // --- End SMS Sending Route --- app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); });recipient(E.164 format phone number) andmessage(the text content)paramsobject maps directly to the requirements of the MessageBirdmessages.createfunction.recipientsmust be an array, even for a single numbermessagebird.messages.create: The core SDK function – takes the parameters and a callback functionerr(if an error occurred during the API call) orresponse(on success)errexists, log it and return a relevant HTTP status code (e.g., 400 for bad input, 402 for balance issues, 500 for server/API errors) and error message. Attempt to parse specific errors from the MessageBird response4. Testing Your SMS API with cURL and Postman
Test the endpoint using a tool like
curlor Postman to verify SMS sending functionality.Start the Server: Ensure your
.envfile is correctly configured with your live API key and originator number.Send a Test Request (using
curl): Open a new terminal window (leave the server running in the first one). Replace+1YOUR_PHONE_NUMBERwith your actual mobile number in E.164 format.curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "recipient": "+1YOUR_PHONE_NUMBER", "message": "Hello from Node.js and MessageBird!" }'Check the Response:
curlterminal, and the SMS should arrive on your phone shortly (standard SMS charges may apply).{ "message": "SMS submitted successfully!", "details": { "id": "mb-message-id-string", "status": "sent", "statusDatetime": "2023-10-27T10:30:00Z", "recipient": "+1YOUR_PHONE_NUMBER" } }node index.jsis running) should also show the success logs.// Example: Invalid recipient format { "error": "Invalid recipient format. Use E.164 format (e.g., +12025551234)." }// Example: MessageBird API error (e.g., bad key or insufficient balance) { "error": "MessageBird Error 7: No balance", "errorCode": 7, "details": [ /* ... detailed error object from MessageBird ... */ ] }Testing with Postman:
POSThttp://localhost:3000/send-sms{ "recipient": "+1YOUR_PHONE_NUMBER", "message": "Testing SMS via Postman!" }5. Error Handling and Logging Best Practices
While basic error handling using
console.log/console.errorworks for development, production systems need more robustness. This section introduces improvements incorporated into the final code (Section 13).{ "error": "message", "errorCode": 123, "details": { ... } })console.logto a dedicated logging library likewinstonorpino. This provides significant advantages:info), warnings (warn), and critical errors (error)winstonsetup (as used in Section 13):// Example setup with Winston (replaces console.log/error) const winston = require('winston'); const logger = winston.createLogger({ level: 'info', // Log only info, warn, error by default format: winston.format.json(), // Log as JSON defaultMeta: { service: 'sms-sender' }, // Add service context transports: [ // Write errors to error.log new winston.transports.File({ filename: 'error.log', level: 'error' }), // Write all logs (info and above) to combined.log new winston.transports.File({ filename: 'combined.log' }), ], }); // Also log to the console in non-production environments if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple(), // Use simpler format for console })); } // Usage (replaces console.log/error): // logger.error('MessageBird API Error:', { error: err }); // logger.info('SMS submitted successfully', { recipient: recipient, messageId: response.id });async-retry) with exponential backoff. This adds complexity but improves reliability6. Database Schema and Data Layer
For this basic SMS sending API endpoint, a database is not strictly required – messages are sent based on direct API requests.
However, if you were building a more comprehensive application (e.g., tracking message history, handling replies, managing user preferences), you would need a database (like PostgreSQL, MongoDB, MySQL). This would involve:
7. Security Features
Security is critical when building APIs that handle potentially sensitive information.
.envfile or hardcode API keys/credentials in your source code. Usedotenvfor local development and your deployment platform's secure environment variable management in production. Ensure.envis listed in your.gitignore/^\+[1-9]{1}[0-9]{3,14}$/validates the format properly – it requires a+sign, followed by a country code digit (1–9), and 3–14 additional digits, for a total length of 4–15 digits. For maximum accuracy, consider libraries likegoogle-libphonenumber(though potentially overkill for a basic service)express-rate-limitto limit the number of requests from a single IP address within a time window. This is implemented in the final code (Section 13)// Example implementation (see Section 13 for full context) const rateLimit = require('express-rate-limit'); const smsLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 10, // Limit each IP to 10 requests per window message: { error: 'Too many SMS requests created from this IP, please try again after 15 minutes' } }); app.post('/send-sms', smsLimiter, /* route handler */ );/send-smsendpoint. Methods include API keys specific to your service, JWT tokens, session authentication, OAuth, etc.8. SMS Delivery Best Practices and Special Cases
originatoris generally the most reliable approach for global sending. Always check Bird's country restrictions documentation (formerly MessageBird) for current regulationscode: 2– invalid parameter) or a failed delivery status later9. Performance Optimizations
For this specific function (a single API call per request), performance bottlenecks within the Node.js code itself are less likely than external factors.
messagebird.messages.createcall is asynchronous, meaning your server isn't stalled waiting for MessageBird's response and can handle other requests concurrently. This is inherently performant for I/O-bound tasksk6,artillery,autocannon) to simulate traffic. This helps identify potential limits (CPU, memory, network bandwidth) or issues with dependencies (like rate limits)10. Production Monitoring and Observability
To ensure your service runs reliably in production:
/healthendpoint that returns a200 OKstatus. Monitoring systems (like uptime checkers, Kubernetes liveness probes) can ping this endpoint to verify the service is operational (added in Section 13)// Example health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); });sent,delivered,failed), delivery timestamps, error codes if failures occur, and associated costs. This is crucial for debugging specific SMS delivery problems11. Troubleshooting and Caveats
Common issues and things to be aware of:
Authentication failedorRequest not allowed (incorrect login details…)(Code 2, 10, 21):MESSAGEBIRD_API_KEYin your environment variables. It might be mistyped, have extra whitespace, be a test key when a live one is needed, or belong to a different account.envfile (or production environment variables) against the key shown in the MessageBird Dashboard (Developers→API access). Ensure you are using the correct key type (live/test). Restart your application after making changesrecipient is invalidor similar parameter errors (Code 2, 22):recipientfield is not in the valid E.164 format (+sign followed by country code and number, no spaces or dashes)originator is invalid(Code 2, 21):MESSAGEBIRD_ORIGINATOR_NUMBERin your environment variables is incorrect, not associated with your MessageBird account, formatted incorrectly, or disallowed as a sender ID in the destination countryNumbersin your MessageBird Dashboard and is in E.164 format. If using an alphanumeric ID, check country regulations and registration statusNo balance(Code 7):Billingsection)Messaging→Logs) for that specific message. Look at its status (delivered,failed,expired) and any error details provideddeliveredbut the user insists they didn't receive it, the issue likely lies with the recipient's device or carrierfailedwith an error code, investigate that specific codesentor if you suspect a platform problemsentorscheduled). The final delivery confirmation (deliveredorfailed) happens asynchronously. To get these final statuses reliably, you need to implement webhooks to receive Delivery Reports from MessageBird (see "Next Steps"). The code examples provided map common API call errors, but a production system might need more comprehensive mapping or handling logic based on MessageBird's documentation12. Deployment and CI/CD
To deploy your Node.js Express application:
package.json: Ensure yourscriptsinclude astartcommand// package.json excerpt { "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" } }MESSAGEBIRD_API_KEYandMESSAGEBIRD_ORIGINATOR_NUMBERsecurely within your chosen platform's settings. Never commit the.envfile to Git. Also setNODE_ENV=productionfor performance and security benefitsnpm test) and deploy on successful builds to your hosting platform.github/workflows/deploy.yml):name: Deploy to Production on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' - run: npm ci - run: npm test # Add your deployment steps here (e.g., Heroku deploy, AWS deploy)/healthendpoint (see Section 10) that monitoring systems can use to verify service availability13. Complete Production-Ready Code
Here's the full
index.jswith all best practices integrated:// index.js - Production-Ready Version require('dotenv').config(); const express = require('express'); const rateLimit = require('express-rate-limit'); // Initialize MessageBird const messagebirdApiKey = process.env.MESSAGEBIRD_API_KEY; if (!messagebirdApiKey) { console.error('FATAL ERROR: MESSAGEBIRD_API_KEY environment variable not set.'); process.exit(1); } const messagebird = require('messagebird')(messagebirdApiKey); const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(express.json()); // Rate limiting for SMS endpoint const smsLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 10, // Limit each IP to 10 requests per window message: { error: 'Too many SMS requests from this IP, please try again after 15 minutes' }, standardHeaders: true, legacyHeaders: false, }); // Validation checks if (!process.env.MESSAGEBIRD_ORIGINATOR_NUMBER) { console.warn('WARNING: MESSAGEBIRD_ORIGINATOR_NUMBER not set. SMS sending may fail.'); } // Health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString(), service: 'messagebird-sms-api' }); }); // Root endpoint app.get('/', (req, res) => { res.json({ message: 'MessageBird SMS API', version: '1.0.0', endpoints: { health: '/health', sendSms: 'POST /send-sms' } }); }); // SMS sending endpoint with rate limiting app.post('/send-sms', smsLimiter, (req, res) => { const { recipient, message } = req.body; // Input validation if (!recipient || !message) { console.warn('Send SMS failed: Missing fields', { hasRecipient: !!recipient, hasMessage: !!message }); return res.status(400).json({ error: 'Missing required fields: recipient and message' }); } // E.164 format validation if (!/^\+[1-9]{1}[0-9]{3,14}$/.test(recipient)) { console.warn('Send SMS failed: Invalid recipient format', { recipient }); return res.status(400).json({ error: 'Invalid recipient format. Use E.164 format (e.g., +12025551234).' }); } // Message length check (warn if > 160 chars) if (message.length > 160) { console.warn('Long message detected', { length: message.length, segments: Math.ceil(message.length / 160) }); } const originator = process.env.MESSAGEBIRD_ORIGINATOR_NUMBER || 'MessageBird'; const params = { originator: originator, recipients: [recipient], body: message, }; console.log(`Sending SMS to ${recipient} from ${originator}`); messagebird.messages.create(params, (err, response) => { if (err) { console.error('MessageBird API Error:', { errors: err.errors, statusCode: err.statusCode }); let statusCode = 500; let errorMessage = 'Failed to send SMS due to an internal server error.'; let errorCode = null; if (err.errors && err.errors.length > 0) { const firstError = err.errors[0]; errorCode = firstError.code; errorMessage = `MessageBird Error ${errorCode}: ${firstError.description}`; // Map error codes to HTTP status codes if ([2, 9, 21, 22].includes(errorCode)) statusCode = 400; if (errorCode === 7) statusCode = 402; if (errorCode === 10) statusCode = 401; } else if (err.statusCode) { statusCode = err.statusCode; errorMessage = `Network error: ${err.message || 'Failed to communicate with MessageBird API'}`; } return res.status(statusCode).json({ error: errorMessage, errorCode: errorCode, details: err.errors || err.message }); } console.log('SMS sent successfully:', { messageId: response.id, recipient: recipient, status: response.recipients.items[0]?.status }); const recipientInfo = response.recipients.items[0] || {}; res.status(200).json({ message: 'SMS submitted successfully!', details: { id: response.id, status: recipientInfo.status, statusDatetime: recipientInfo.statusDatetime, recipient: recipientInfo.recipient, } }); }); }); // 404 handler app.use((req, res) => { res.status(404).json({ error: 'Endpoint not found' }); }); // Error handler app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.status(500).json({ error: 'Internal server error' }); }); // Start server app.listen(PORT, () => { console.log(`MessageBird SMS API listening on port ${PORT}`); console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); });14. Next Steps and Advanced Features
To extend your SMS application beyond basic sending:
Related Resources
Ready to send SMS with Node.js? Follow this tutorial to build your own production-ready SMS API using Express.js and MessageBird. Whether you're implementing authentication codes, notifications, or marketing campaigns, this comprehensive guide covers everything you need to know.