Send SMS with Plivo API in Node.js Express: Complete 2025 Guide
Build a production-ready SMS API using Plivo and Node.js Express. Complete tutorial with authentication, error handling, rate limiting, and deployment best practices.
You'll build a Node.js application using the Express framework to send SMS messages via the Plivo API. This tutorial covers everything from initial project setup to deploying a functional API endpoint.
By the end, you'll have a simple but robust Express application capable of accepting API requests to send SMS messages, complete with basic error handling and considerations for production environments.
Project Overview and Goals
Goal: Create a backend service using Node.js and Express that exposes an API endpoint for sending SMS messages through Plivo.
Problem Solved: This service enables your applications to programmatically send SMS notifications, alerts, or messages without handling the complexities of SMS gateway protocols directly.
Technologies Used:
Node.js: A JavaScript runtime environment ideal for building scalable, non-blocking, event-driven servers.
Express.js: A minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
Plivo Node.js SDK: A library provided by Plivo to simplify interactions with their SMS API. Available as the plivo package on npm. Note: If you have legacy version 0.4.1 installed, uninstall it before installing the current version. The SDK is actively maintained at github.com/plivo/plivo-node.
Plivo: A cloud communications platform providing APIs for SMS, voice, and more.
dotenv: A zero-dependency module that loads environment variables from a .env file into process.env.
What Are the Prerequisites for Plivo SMS with Node.js?
Prerequisites:
Node.js and npm (or yarn): Install from nodejs.org. Node.js 18+ is required. As of January 2025, Node.js 22 is the current LTS version recommended for production applications. Node.js 18.x reaches end-of-life on April 30, 2025.
Plivo Auth ID and Auth Token: Find these on your Plivo Console dashboard after logging in.
Plivo Phone Number or Sender ID:
US/Canada: You must use a Plivo phone number capable of sending SMS. Purchase one via the Plivo Console (Phone Numbers → Buy Numbers).
Other Countries: You might be able to use a registered Alphanumeric Sender ID (check Plivo documentation and local regulations) or a Plivo phone number.
Verified Destination Number (Trial Accounts): If using a Plivo trial account, you can only send SMS to numbers verified in your Plivo Console (Phone Numbers → Sandbox Numbers).
Note: This guide uses Express.js 4.x. Express 5.x (released October 2024) is now stable and includes built-in async error handling, but requires Node.js 18+.
How Do You Set Up a Node.js Express Project for Plivo?
1. Setting Up the Project
Initialize your Node.js project and install the necessary dependencies.
Create Project Directory:
Open your terminal and create a new directory for your project, then navigate into it.
Bash
mkdir plivo-sms-sender
cd plivo-sms-sender
Initialize Node.js Project:
Initialize the project using npm. The -y flag accepts the default settings.
Bash
npm init -y
This creates a package.json file.
Install Dependencies:
Install Express for the web server, the Plivo SDK for interacting with the API, and dotenv for managing environment variables securely.
Bash
npm install express plivo dotenv
Create Project Structure:
Create the main application file and a file for environment variables.
Configure .gitignore:
Add node_modules and .env to your .gitignore file to prevent committing dependencies and sensitive credentials.
.gitignore
node_modules
.env
Why .gitignore? It prevents accidentally publishing sensitive information (like API keys in .env) and unnecessary large directories (node_modules) to version control systems like Git.
How Do You Configure Environment Variables for Plivo?
2. Environment Configuration
Store your sensitive Plivo credentials securely using environment variables.
Edit the .env file:
Open the .env file you created and add your Plivo credentials and sender information.
.env
# Plivo API Credentials – Find these on your Plivo Console Dashboard
PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID
PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN
# Plivo Sender Information
# Use a Plivo phone number (E.164 format, e.g., +14155551234) for US/Canada
# Or use a registered Alphanumeric Sender ID for supported countries
PLIVO_SENDER_ID=+1XXXXXXXXXX
# Server Configuration
PORT=3000
Replace Placeholders:
Replace YOUR_PLIVO_AUTH_ID and YOUR_PLIVO_AUTH_TOKEN with the actual values from your Plivo Console.
Replace +1XXXXXXXXXX with your SMS-enabled Plivo phone number (in E.164 format) or your registered Alphanumeric Sender ID.
Environment Variable Explanation:
PLIVO_AUTH_ID: Your unique Plivo account identifier. How to obtain: Log in to the Plivo Console – it's displayed prominently on the dashboard.
PLIVO_AUTH_TOKEN: Your secret token for API authentication. How to obtain: Log in to the Plivo Console – it's displayed below the Auth ID. Treat this like a password.
PLIVO_SENDER_ID: The identifier ("From" number or Alphanumeric ID) that will appear on the recipient's device. How to obtain/configure: For US/Canada, purchase an SMS-enabled number from Phone Numbers → Buy Numbers in the Plivo Console. For other regions, check Sender ID registration requirements with Plivo support or documentation.
PORT: The network port your Express application will listen on. 3000 is a common default for development.
Why use .env? It keeps sensitive credentials out of your source code, making your application more secure and easier to configure for different environments (development, staging, production).
How Do You Implement SMS Sending with Plivo SDK?
3. Implementing Core Functionality (Basic Send)
Initialize the Plivo client and create a function to send a single SMS.
Edit app.js:
Open app.js and add the following code to set up Express, load environment variables, initialize the Plivo client, and define your API endpoints.
JavaScript
// app.js'use strict'; // Enforces stricter parsing and error handlingconst express = require('express');
const plivo = require('plivo');
require('dotenv').config(); // Load environment variables from .env file// Validate essential environment variablesif (!process.env.PLIVO_AUTH_ID || !process.env.PLIVO_AUTH_TOKEN || !process.env.PLIVO_SENDER_ID) {
console.error("Error: Plivo credentials or sender ID missing in .env file.");
console.error("Please ensure PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_SENDER_ID are set.");
process.exit(1); // Exit if critical configuration is missing
}
const app = express();
app.use(express.json()); // Middleware to parse JSON request bodiesconst port = process.env.PORT || 3000;
// Initialize Plivo clientlet client;
try {
client = new plivo.Client(process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN);
console.log("Plivo client initialized successfully.");
} catch (error) {
console.error("Failed to initialize Plivo client:", error);
process.exit(1); // Exit if client cannot be initialized
}
// --- API Endpoints ---/**
* @route POST /send-sms
* @description Sends a single SMS message.
* @body {string} to - Destination phone number in E.164 format (e.g., +14155551234).
* @body {string} text - The message content. Max 160 GSM characters or 70 Unicode characters per segment.
*/
app.post('/send-sms', async (req, res) => {
const { to, text } = req.body;
const src = process.env.PLIVO_SENDER_ID;
// --- Input Validation ---if (!to || !text) {
console.error("Validation Error: 'to' and 'text' fields are required.");
return res.status(400).json({ error: "'to' and 'text' fields are required." });
}
// Basic E.164 format check (starts with '+', followed by digits)// For production, consider more robust validation using libraries like 'joi' or 'express-validator'.if (!/^\+\d+$/.test(to)) {
console.error(`Validation Error: Invalid 'to' number format: ${to}. Must be E.164 format.`);
return res.status(400).json({ error: "Invalid 'to' number format. Must start with '+' followed by digits (E.164 format)." });
}
// Basic text length check (optional, Plivo handles segmentation)if (text.length === 0) {
console.error(`Validation Error: 'text' field cannot be empty.`);
return res.status(400).json({ error: "'text' field cannot be empty." });
}
console.log(`Attempting to send SMS from ${src} to ${to}`);
try {
const response = await client.messages.create(
src, // Sender ID from .env
to, // Destination number from request body
text // Message text from request body
);
console.log("Plivo API Response:", response);
// Plivo's API usually returns a 202 Accepted on success// The actual delivery status comes via webhooks (if configured)
res.status(202).json({
message: "SMS request accepted by Plivo.",
message_uuid: response.messageUuid// Plivo's unique ID for the message request
});
} catch (error) {
// Log detailed error information internallyconsole.error(`[Plivo API Error] Failed sending SMS to ${to}. Status: ${error.statusCode || 'N/A'}. Message: ${error.message}. Details:`, error);
// Send a standardized error response to the client
res.status(error.statusCode || 500).json({
error: "SMS sending failed.",
// Optionally include a reference ID for correlation in production// error_reference: Date.now() // Simple example reference
});
}
});
/**
* @route GET /health
* @description Health check endpoint.
*/
app.get('/health', (req, res) => {
// Basic health check: checks if the server is running// More advanced checks could verify DB connection, Plivo connectivity (with caution) etc.
res.status(200).json({ status: 'UP', timestamp: newDate().toISOString() });
});
// --- Start the server ---// Check if the module is run directlyif (require.main === module) {
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
}
module.exports = app; // Export for testing
Code
*Code Explanation:*
* `'use strict';`: Enables strict mode.
* `require('dotenv').config();`: Loads variables from `.env`. Must run early.
* Environment Variable Check: Ensures critical Plivo config is present.
* `express()`: Creates an Express app.
* `app.use(express.json())`: Middleware for parsing JSON request bodies (`req.body`).
* `plivo.Client(…)`: Initializes the Plivo SDK client. Wrapped in `try…catch` for robustness.
* `/send-sms` Endpoint: Handles POST requests to send messages. Includes input validation and Plivo API call within a `try…catch` block.
* `/health` Endpoint: Provides a simple GET endpoint for basic health monitoring.
* `app.listen(…)`: Starts the server. Conditional check `require.main === module` prevents the server from auto-starting when the file is imported for testing.
* `module.exports = app;`: Exports the app instance for use in test files.
2. Run the Server:
Code
```bash
node app.js
```
You should see `Plivo client initialized successfully.` and `Server running on http://localhost:3000`. Press `Ctrl+C` to stop.
How Do You Build and Test the Plivo SMS API?
4. Building the API Layer
This section is now integrated into Section 3 where the app.js code, including the /send-sms and /health endpoints, is presented.
Testing the Endpoints:
Start the server:node app.js
Test /send-sms:
Open a new terminal window. Use curl or a tool like Postman.
Replace +1RECIPIENTNUMBER with a real phone number (verified in your Plivo sandbox if using a trial account).
Bash
curl -X POST http://localhost:3000/send-sms \
-H "Content-Type: application/json" \
-d '{
"to": "+1RECIPIENTNUMBER",
"text": "Hello from Node.js and Plivo!"
}'
Expected Success Output (HTTP 202):
JSON
{"message":"SMS request accepted by Plivo.","message_uuid":["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]}
What Are the Plivo Integration Details You Need to Know?
5. Integrating with Plivo (Details)
You've already initialized the client and used messages.create in app.js. Key points:
Authentication: Handled by new plivo.Client(process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN). Ensure .env values are correct. Find credentials on the Plivo Console Dashboard.
Sender ID (src): Set via PLIVO_SENDER_ID in .env. Must be a valid Plivo number (US/Canada) or potentially a registered Alphanumeric Sender ID. Manage numbers in the Phone Numbers section of the Plivo Console.
API Keys/Secrets: Handled via PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN. Use .env locally and secure environment variables in production. Never commit credentials.
Fallback Mechanisms: The current example lacks automatic fallback. For production:
Consider retrying failed requests (see Section 6 concept).
Use multiple Plivo numbers or alternative providers for high availability (complex).
How Do You Implement Error Handling for Plivo SMS?
6. Implementing Error Handling and Logging
Our app.js includes basic try...catch blocks and improved console.error logging.
Note on Express 5.x: If using Express 5.x (released October 2024, requires Node.js 18+), async middleware can return rejected promises that are automatically caught by the router as errors, eliminating the need for manual try-catch blocks in many cases. This guide uses Express 4.x syntax for broader compatibility.
Retry Mechanisms (Conceptual Enhancement):
Plivo requests might fail transiently. Implementing retries can improve reliability. Note: The following is a conceptual example to illustrate the pattern. It is not integrated into the main app.js code provided in Section 3. Implementing this would require modifying the /send-sms handler to use this function instead of calling client.messages.create directly.
Conceptual Example Function:
JavaScript
asyncfunctionsendSmsWithRetry(src, to, text, retries = 3, delay = 1000) {
try {
// Assume 'client' is the initialized Plivo client available in this scopereturnawait client.messages.create(src, to, text);
} catch (error) {
// Only retry on specific error types (e.g., 5xx server errors, maybe 429 rate limit)// Avoid retrying permanent errors like 400 Bad Request or 401 Unauthorized.const shouldRetry = retries > 0 && (error.statusCode >= 500 || error.statusCode === 429);
if (shouldRetry) {
console.warn(`Retrying SMS to ${to}. Retries left: ${retries - 1}. Delay: ${delay}ms. Error: ${error.message}`);
awaitnewPromise(resolve =>setTimeout(resolve, delay));
// Exponential backoff: double the delay for next retryreturnsendSmsWithRetry(src, to, text, retries - 1, delay * 2);
} else {
// Don't retry permanent errors or if retries exhaustedconsole.error(`SMS to ${to} failed permanently or retries exhausted. Error: ${error.message}`);
throw error; // Re-throw the error to be caught by the main handler
}
}
}
// To use this in the app.post('/send-sms', ...) handler, you would replace:// const response = await client.messages.create(src, to, text);// With:// const response = await sendSmsWithRetry(src, to, text);
Why Retry? Handles temporary network or service issues. Exponential backoff prevents overwhelming the API during outages. Caution: Carefully choose which errors to retry.
Testing Error Scenarios:
Provide invalid credentials in .env.
Send requests with invalid to number format (e.g., ""12345"").
Use an invalid/non-existent Plivo src number in .env.
Send to an unverified number using a trial account.
Temporarily disconnect internet to simulate network errors.
Send requests rapidly to test potential rate limits.
What Security Features Should You Add to Your SMS API?
7. Adding Security Features
Protecting your API is crucial.
Input Validation and Sanitization:
Our app.js includes basic checks for to (presence, format) and text (presence).
Enhancement: For production, use dedicated validation libraries like joi or express-validator to define schemas for req.body, check data types, enforce length limits, and potentially sanitize inputs more rigorously. Example mention added in app.js comments.
Check text length if specific limits are desired beyond Plivo's segmentation.
Authentication/Authorization for Your API (Conceptual Enhancement):
The /send-sms endpoint in our base app.js is currently open. This is insecure for production.
Recommendation: Implement API Key Authentication (or other methods like JWT/OAuth).
Conceptual Example: The following shows how you could add API key middleware. This is not integrated into the main app.js example but demonstrates the principle.
Add a secret key to your .env: INTERNAL_API_KEY=your_secret_key_here
Conceptual Middleware Function:
JavaScript
// ---- Conceptual API Key Middleware (Not applied in base app.js) ----constAPI_KEY = process.env.INTERNAL_API_KEY;
functionauthenticateApiKey(req, res, next) {
if (!API_KEY) {
console.error("INTERNAL_API_KEY not set. Cannot enforce API key authentication.");
// Fail closed: If key isn't configured, deny access.return res.status(500).json({ error: 'Server configuration error' });
}
const providedKey = req.headers['x-api-key'];
if (!providedKey || providedKey !== API_KEY) {
console.warn(`Authentication failed. Invalid or missing API key.`);
// Use 403 Forbidden if the key is missing/wrong, 401 often implies possibility of authenticatingreturn res.status(403).json({ error: 'Forbidden: Invalid API Key' });
}
next(); // Key is valid, proceed
}
// ---- How to Apply (Example - Do not add this directly to base app.js unless implementing) ----// To protect the /send-sms route, you would add this line *before* the route definition:// app.use('/send-sms', authenticateApiKey);// Or apply it globally (carefully):// app.use(authenticateApiKey); // Applies to all routes defined after this line// ---- End Conceptual Example ----
Clients would need to include the header X-API-Key: your_secret_key_here in their requests.
Default SMS Outbound MPS: 5 messages per second (MPS) at signup
Default MMS Outbound MPS: 0.25 messages per second at signup
API Concurrency Limit: 100 simultaneous requests (default)
Message Queueing: Messages exceeding MPS limits are queued, not dropped. Each Plivo source number maintains a dedicated queue.
Increasing Capacity: Distribute traffic across multiple Plivo numbers or contact support for higher MPS (additional fees apply)
Check Your Limits: View your allocated MPS in the Plivo Console under Account -> SMS Overview
Recommendation: Use libraries like express-rate-limit.
Conceptual Example: This shows how to set up rate limiting. It is not integrated into the main app.js example.
Bash
npm install express-rate-limit
JavaScript
// ---- Conceptual Rate Limiting Setup (Not applied in base app.js) ----// Place near the top of app.jsconst rateLimit = require('express-rate-limit');
const smsLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutesmax: 100, // Limit each IP to 100 requests per `windowMs`message: { error: 'Too many SMS requests created from this IP, please try again after 15 minutes' },
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headerslegacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
// ---- How to Apply (Example - Do not add this directly to base app.js unless implementing) ----// Apply the limiter to the SMS route:// app.use('/send-sms', smsLimiter);// ---- End Conceptual Example ----
Why Rate Limit? Protects against DoS attacks and cost overruns.
HTTPS: Always use HTTPS in production (typically handled by load balancers, reverse proxies like Nginx, or hosting platforms) to encrypt data in transit.
How Do You Handle Special Cases in SMS Messaging?
8. Handling Special Cases (Briefly)
Character Encoding (GSM vs. Unicode): Plivo auto-detects. GSM-7 (standard text) = up to 160 chars/segment. Unicode (emojis, special chars) = up to 70 chars/segment. Unicode segments cost the same but fit less text. See Plivo's Encoding Guide.
Long Messages: Plivo handles multipart messages automatically. Each part is billed separately.
Internationalization: Use E.164 format (+countrycode...). Sender ID rules vary greatly by country (check Plivo docs/regulations).
Time Zones: API uses server time (often UTC). Plivo logs are typically UTC. Delivery depends on carriers.
What Performance Optimizations Should You Consider?
9. Performance Optimizations (Considerations)
Asynchronous Operations: Node.js + async/await is efficient.
Caching: Not typically applicable for sending unique SMS.
Load Testing: Use tools (k6, artillery, ab) to test /send-sms under load. Monitor server resources (CPU, memory) and response times.
Example ab command:
Plivo Dashboard: Check Plivo Console Messages Logs for delivery status and costs.
Plivo Webhooks (Status Callbacks): Configure a Message Request URL in Plivo for real-time status updates (requires building an endpoint to receive POSTs from Plivo).
What Are Common Plivo SMS Issues and Solutions?
11. Troubleshooting and Caveats
Common Errors & Solutions:
401 Unauthorized from Plivo: Incorrect PLIVO_AUTH_ID or PLIVO_AUTH_TOKEN in .env.
400 Bad Request (Invalid dst): Ensure to is valid E.164 format (+1...).
400 Bad Request (Invalid src): Ensure PLIVO_SENDER_ID is a valid, SMS-enabled Plivo number or registered ID.
402 Payment Required: Insufficient Plivo credits.
Message Failed/Undelivered: Invalid destination, number blocked, carrier issues. Check Plivo logs. Trial accounts limited to verified numbers.
SMS Not Received (Plivo shows 'Delivered'): Device signal, number correct?, blocked sender?, carrier filtering?
ECONNREFUSED/Network Errors: Server can't reach Plivo API. Check connectivity, firewalls, Plivo status (status.plivo.com).
Platform-Specific Limitations:
Trial Accounts: Send only to verified sandbox numbers. Messages prefixed. Limited credits.
Version Compatibility: Use Node.js 18+ (Node.js 22 LTS recommended as of January 2025). Node.js 18.x reaches end-of-life April 30, 2025. Keep plivo SDK updated (npm update plivo). Check github.com/plivo/plivo-node for releases.
Rate Limits: Be aware of Plivo account limits (default: 5 MPS for SMS, 0.25 MPS for MMS, 100 concurrent API requests). Implement app-level rate limiting (Section 7 concept) and potential retries (Section 6 concept).
Encoding & Character Limits: GSM (160) vs. Unicode (70) impacts segment count/cost.
How Do You Deploy a Plivo SMS Application to Production?
12. Deployment and CI/CD
Deployment Environments: Heroku, AWS (EC2, Fargate, Lambda), DigitalOcean, etc.
Key Principles:
Environment Variables: Use platform's mechanism for PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, PLIVO_SENDER_ID, PORT, INTERNAL_API_KEY (if used), etc. NEVER hardcode.
NODE_ENV=production: Set this env var for performance.
Process Manager: Use pm2, systemd, Docker, or platform's manager to keep the app running. (pm2 start app.js --name plivo-sms)
Build Step: Include if using TypeScript, etc.
CI/CD Pipeline (Conceptual):
Trigger (e.g., push to main).
Checkout code.
Install dependencies (npm ci).
Lint/Test (npm test). Fail pipeline on errors.
Build (if needed).
Deploy to platform.
Restart application.
Rollback: Have a plan to revert to a previous working version.
How Do You Verify and Test Your Plivo Integration?
13. Verification and Testing
Manual Verification Checklist:
Deploy to staging.
Set environment variables correctly.
Send test request via curl/Postman to /send-sms.
Verify 202 Accepted response and message_uuid.
Check Plivo Console Logs for message status (Queued -> Sent -> Delivered).
Confirm SMS received on test phone.
Test /health endpoint -> verify 200 OK and status UP.
Test invalid input (to/text missing/invalid format) -> verify 400 Bad Request.
(If implemented) Test invalid/missing API Key -> verify 403 Forbidden.
(If implemented) Test rate limiting -> verify 429 Too Many Requests.
Check application logs for errors.
Automated Testing (Examples - using Jest):
Install Jest: npm install --save-dev jest supertest
Create a test file (e.g., app.test.js):
JavaScript
// app.test.jsconst request = require('supertest');
const app = require('./app'); // Import your express app// Mock the Plivo client to avoid actual API calls/costs during tests// Mock implementation simulates a successful API call by defaultconst mockCreate = jest.fn().mockResolvedValue({
message: [""SMS request queued for delivery""],
messageUuid: [""mock-uuid-"" + Date.now()],
apiId: ""mock-api-id""
});
jest.mock('plivo', () => ({
Client: jest.fn().mockImplementation(() => ({
messages: {
create: mockCreate // Use the mock function
}
}))
}));
// Load .env for consistency, but tests might override or rely on mocksrequire('dotenv').config();
describe('SMS API Endpoints', () => {
// Clear mocks before each test to ensure isolationbeforeEach(() => {
mockCreate.mockClear();
// Reset to default success behavior if needed after failure tests
mockCreate.mockResolvedValue({
message: [""SMS request queued for delivery""],
messageUuid: [""mock-uuid-"" + Date.now()],
apiId: ""mock-api-id""
});
});
// --- Test Suite for POST /send-sms ---describe('POST /send-sms', () => {
const validPayload = {
to: '+15551234567',
text: 'Test message'
};
it('should return 400 Bad Request if ""to"" is missing', async () => {
const res = awaitrequest(app)
.post('/send-sms')
.send({ text: 'Missing to field' }); // Missing 'to'expect(res.statusCode).toEqual(400);
expect(res.body).toHaveProperty('error', ""'to' and 'text' fields are required."");
expect(mockCreate).not.toHaveBeenCalled(); // Plivo should not be called
});
it('should return 400 Bad Request if ""text"" is missing', async () => {
const res = awaitrequest(app)
.post('/send-sms')
.send({ to: '+15551234567' }); // Missing 'text'expect(res.statusCode).toEqual(400);
expect(res.body).toHaveProperty('error', ""'to' and 'text' fields are required."");
expect(mockCreate).not.toHaveBeenCalled();
});
it('should return 400 Bad Request for invalid ""to"" format', async () => {
const res = awaitrequest(app)
.post('/send-sms')
.send({ to: '1234567890', text: 'Invalid number format' }); // Invalid 'to'expect(res.statusCode).toEqual(400);
expect(res.body).toHaveProperty('error', expect.stringContaining(""Invalid'to' number format""));
expect(mockCreate).not.toHaveBeenCalled();
});
it('should return 202 Accepted and message_uuid on valid request', async () => {
const res = awaitrequest(app)
.post('/send-sms')
.send(validPayload); // Valid payloadexpect(res.statusCode).toEqual(202);
expect(res.body).toHaveProperty('message', 'SMS request accepted by Plivo.');
expect(res.body).toHaveProperty('message_uuid');
expect(mockCreate).toHaveBeenCalledTimes(1);
expect(mockCreate).toHaveBeenCalledWith(
process.env.PLIVO_SENDER_ID, // Check if called with correct args
validPayload.to,
validPayload.text
);
});
it('should return 500 if Plivo API call fails', async () => {
// Simulate Plivo SDK throwing an errorconst plivoError = newError(""Plivo simulated error"");
plivoError.statusCode = 503; // Example Plivo error status
mockCreate.mockRejectedValueOnce(plivoError); // Make the mock throw an errorconst res = awaitrequest(app)
.post('/send-sms')
.send(validPayload);
expect(res.statusCode).toEqual(503); // Should reflect Plivo's error code if availableexpect(res.body).toHaveProperty('error', 'SMS sending failed.');
expect(mockCreate).toHaveBeenCalledTimes(1); // Ensure it was called
});
});
// --- Test Suite for GET /health ---describe('GET /health', () => {
it('should return 200 OK and status UP', async () => {
const res = awaitrequest(app).get('/health');
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('status', 'UP');
expect(res.body).toHaveProperty('timestamp');
});
});
});
Frequently Asked Questions About Plivo SMS with Node.js
What Node.js version is required for Plivo integration?
You need Node.js 18 or higher for modern Plivo implementations. As of January 2025, Node.js 22 is the current LTS version recommended for production applications. Node.js 18.x reaches end-of-life on April 30, 2025, so plan to upgrade to Node.js 20 or 22 for continued support and security updates.
What are the Plivo SMS rate limits?
Plivo assigns default rate limits at signup: 5 messages per second (MPS) for SMS and 0.25 MPS for MMS. The API supports up to 100 simultaneous requests by default. Messages exceeding these limits are queued, not dropped. You can increase capacity by distributing traffic across multiple Plivo numbers or contacting support for higher MPS allocations (additional fees apply).
How do you secure a Plivo SMS API endpoint?
Implement multiple security layers: use API key authentication via custom headers (e.g., X-API-Key), add rate limiting with express-rate-limit to prevent abuse, validate all input data using libraries like joi or express-validator, always use HTTPS in production, and store credentials in environment variables never in code. For production, consider implementing JWT tokens or OAuth 2.0 for more robust authentication.
What is E.164 phone number format for Plivo?
E.164 format is the international phone number standard required by Plivo: it starts with + followed by the country code and subscriber number with no spaces or special characters. Examples: +14155551234 (US), +442071234567 (UK), +91981234567 (India). All phone numbers in Plivo API calls must use this format for both source and destination numbers.
Does Plivo support sending SMS internationally?
Yes, Plivo supports international SMS to 190+ countries, but sender ID requirements vary significantly by country. US and Canada require Plivo phone numbers purchased through the console. Other countries may accept Alphanumeric Sender IDs with registration. Check Plivo's country-specific documentation for sender ID requirements, pricing, and any regulatory restrictions before sending to international destinations.
How do you handle SMS delivery failures with Plivo?
Implement robust error handling: catch Plivo API errors in try-catch blocks, log detailed error information including status codes, implement retry logic with exponential backoff for transient failures (5xx errors, 429 rate limits), configure webhook endpoints to receive delivery status callbacks, store message status in a database for tracking, and monitor common errors like 401 (invalid credentials), 402 (insufficient credits), and 400 (invalid parameters).
What is the difference between Express 4.x and Express 5.x for Plivo?
Express 5.x (released October 2024) requires Node.js 18+ and includes built-in async error handling where rejected promises are automatically caught by the router, eliminating many manual try-catch blocks. Express 4.x requires explicit error handling in async routes. Both versions work with Plivo SDK, but Express 5.x provides cleaner async/await syntax and improved security through updated dependencies.
How much does it cost to send SMS with Plivo?
Plivo SMS pricing varies by destination country and typically ranges from $0.0035 to $0.10 per message segment. US SMS costs approximately $0.0079 per segment. Messages over 160 GSM characters or 70 Unicode characters are split into multiple segments, each billed separately. MMS messages cost more than SMS. Check your Plivo Console dashboard for real-time pricing for specific countries and monitor your usage to control costs.
Can you send SMS from a trial Plivo account?
Yes, but with limitations: trial accounts can only send SMS to phone numbers verified in your Plivo Console (Phone Numbers → Sandbox Numbers). Messages are prefixed with a trial account notice. You have limited free credits. To send to any number, upgrade to a paid account by adding funds to your Plivo account. Trial accounts are perfect for development and testing before production deployment.
How do you implement webhook callbacks for Plivo SMS delivery status?
Configure a webhook URL in your Plivo Console (Messaging → Applications) pointing to a publicly accessible endpoint in your application (e.g., /plivo/status). Create a route that accepts POST requests from Plivo containing delivery receipt data. Validate webhook signatures using HMAC SHA1 with your auth token to ensure requests come from Plivo. Parse the status (delivered, failed, undelivered) and update your database records accordingly. Use services like ngrok for local development testing.
Frequently asked questions
Use the Plivo Node.js SDK and Express.js to create an API endpoint that handles sending SMS messages. This setup allows you to integrate SMS functionality directly into your Node.js applications. The provided app.js example demonstrates the implementation with basic input validation and error handling.
Plivo is a cloud communications platform that provides APIs for sending SMS messages, making calls, and other communication services. Its Node.js SDK simplifies interaction with the Plivo API, allowing developers to easily integrate communication features into their applications.
Dotenv securely manages environment variables, storing sensitive information like Plivo API keys outside your source code. This enhances security by preventing accidental exposure of credentials in version control.
First, install Node.js and npm, create a project directory (mkdir plivo-sms-sender && cd $_), then initialize your project (npm init -y). Install Express, Plivo, and dotenv (npm install express plivo dotenv).
Your Plivo Auth ID and Auth Token are found on your Plivo Console dashboard after you log in to your Plivo account. Treat your Auth Token like a password, keeping it confidential.
PLIVO_SENDER_ID is the 'From' number or Alphanumeric Sender ID that recipients see. For US/Canada, it must be a Plivo phone number. Obtain one via the Plivo Console (Phone Numbers -> Buy Numbers). For other countries, consult Plivo documentation for Sender ID registration.
Node.js excels at building scalable, non-blocking servers, making it ideal for handling asynchronous communication like sending SMS. Express.js, a lightweight Node.js framework, simplifies building robust API endpoints for sending messages.
The article recommends creating an app.js file for your main application logic, a .env file for environment variables, and a .gitignore file to exclude node_modules and .env from version control.
Start your server with node app.js, and then use tools like curl or Postman to send POST requests to http://localhost:3000/send-sms. Include your destination number and message text in JSON format in the request body.
A 202 response indicates that Plivo has accepted your SMS request for processing but doesn't guarantee immediate delivery. Actual delivery status is confirmed later via webhooks (if configured) or by checking Plivo message logs.
Use try...catch blocks around your Plivo API calls to handle potential errors. The example code provides basic error handling and logging to console. Robust error handling should log detailed information, include correlation IDs, and potentially implement retries.
Retry mechanisms improve reliability by resending messages if transient errors occur (e.g., server-side errors or rate limiting). Implement retries with exponential backoff and avoid retrying on permanent errors like 400 Bad Request or 401 Unauthorized.
Crucial steps include robust input validation (using libraries like joi), implementing API key authentication for your endpoints, using rate limiting to prevent abuse, and ensuring HTTPS is used in production.
Common errors include 401 Unauthorized (bad credentials), 400 Bad Request (invalid input), 402 Payment Required, and message delivery failures. The troubleshooting section in the article provides further guidance.
Monitor your server resources (CPU, memory), response times, and error rates. Use the Plivo console to check message logs and costs. Utilize Plivo webhooks for real-time delivery updates, implement health checks, and centralized logging.
You'll build a Node.js application using the Express framework to send SMS messages via the Plivo API. This tutorial covers everything from initial project setup to deploying a functional API endpoint.
By the end, you'll have a simple but robust Express application capable of accepting API requests to send SMS messages, complete with basic error handling and considerations for production environments.
Project Overview and Goals
Goal: Create a backend service using Node.js and Express that exposes an API endpoint for sending SMS messages through Plivo.
Problem Solved: This service enables your applications to programmatically send SMS notifications, alerts, or messages without handling the complexities of SMS gateway protocols directly.
Technologies Used:
plivopackage on npm. Note: If you have legacy version 0.4.1 installed, uninstall it before installing the current version. The SDK is actively maintained at github.com/plivo/plivo-node..envfile intoprocess.env.System Architecture:
What Are the Prerequisites for Plivo SMS with Node.js?
Prerequisites:
How Do You Set Up a Node.js Express Project for Plivo?
1. Setting Up the Project
Initialize your Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
mkdir plivo-sms-sender cd plivo-sms-senderInitialize Node.js Project: Initialize the project using npm. The
-yflag accepts the default settings.This creates a
package.jsonfile.Install Dependencies: Install Express for the web server, the Plivo SDK for interacting with the API, and
dotenvfor managing environment variables securely.Create Project Structure: Create the main application file and a file for environment variables.
touch app.js .env .gitignoreYour basic structure should look like:
Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing dependencies and sensitive credentials.Why
.gitignore? It prevents accidentally publishing sensitive information (like API keys in.env) and unnecessary large directories (node_modules) to version control systems like Git.How Do You Configure Environment Variables for Plivo?
2. Environment Configuration
Store your sensitive Plivo credentials securely using environment variables.
Edit the
.envfile: Open the.envfile you created and add your Plivo credentials and sender information.Replace Placeholders:
YOUR_PLIVO_AUTH_IDandYOUR_PLIVO_AUTH_TOKENwith the actual values from your Plivo Console.+1XXXXXXXXXXwith your SMS-enabled Plivo phone number (in E.164 format) or your registered Alphanumeric Sender ID.Environment Variable Explanation:
PLIVO_AUTH_ID: Your unique Plivo account identifier. How to obtain: Log in to the Plivo Console – it's displayed prominently on the dashboard.PLIVO_AUTH_TOKEN: Your secret token for API authentication. How to obtain: Log in to the Plivo Console – it's displayed below the Auth ID. Treat this like a password.PLIVO_SENDER_ID: The identifier ("From" number or Alphanumeric ID) that will appear on the recipient's device. How to obtain/configure: For US/Canada, purchase an SMS-enabled number from Phone Numbers → Buy Numbers in the Plivo Console. For other regions, check Sender ID registration requirements with Plivo support or documentation.PORT: The network port your Express application will listen on.3000is a common default for development.Why use
.env? It keeps sensitive credentials out of your source code, making your application more secure and easier to configure for different environments (development, staging, production).How Do You Implement SMS Sending with Plivo SDK?
3. Implementing Core Functionality (Basic Send)
Initialize the Plivo client and create a function to send a single SMS.
Edit
app.js: Openapp.jsand add the following code to set up Express, load environment variables, initialize the Plivo client, and define your API endpoints.// app.js 'use strict'; // Enforces stricter parsing and error handling const express = require('express'); const plivo = require('plivo'); require('dotenv').config(); // Load environment variables from .env file // Validate essential environment variables if (!process.env.PLIVO_AUTH_ID || !process.env.PLIVO_AUTH_TOKEN || !process.env.PLIVO_SENDER_ID) { console.error("Error: Plivo credentials or sender ID missing in .env file."); console.error("Please ensure PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_SENDER_ID are set."); process.exit(1); // Exit if critical configuration is missing } const app = express(); app.use(express.json()); // Middleware to parse JSON request bodies const port = process.env.PORT || 3000; // Initialize Plivo client let client; try { client = new plivo.Client(process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN); console.log("Plivo client initialized successfully."); } catch (error) { console.error("Failed to initialize Plivo client:", error); process.exit(1); // Exit if client cannot be initialized } // --- API Endpoints --- /** * @route POST /send-sms * @description Sends a single SMS message. * @body {string} to - Destination phone number in E.164 format (e.g., +14155551234). * @body {string} text - The message content. Max 160 GSM characters or 70 Unicode characters per segment. */ app.post('/send-sms', async (req, res) => { const { to, text } = req.body; const src = process.env.PLIVO_SENDER_ID; // --- Input Validation --- if (!to || !text) { console.error("Validation Error: 'to' and 'text' fields are required."); return res.status(400).json({ error: "'to' and 'text' fields are required." }); } // Basic E.164 format check (starts with '+', followed by digits) // For production, consider more robust validation using libraries like 'joi' or 'express-validator'. if (!/^\+\d+$/.test(to)) { console.error(`Validation Error: Invalid 'to' number format: ${to}. Must be E.164 format.`); return res.status(400).json({ error: "Invalid 'to' number format. Must start with '+' followed by digits (E.164 format)." }); } // Basic text length check (optional, Plivo handles segmentation) if (text.length === 0) { console.error(`Validation Error: 'text' field cannot be empty.`); return res.status(400).json({ error: "'text' field cannot be empty." }); } console.log(`Attempting to send SMS from ${src} to ${to}`); try { const response = await client.messages.create( src, // Sender ID from .env to, // Destination number from request body text // Message text from request body ); console.log("Plivo API Response:", response); // Plivo's API usually returns a 202 Accepted on success // The actual delivery status comes via webhooks (if configured) res.status(202).json({ message: "SMS request accepted by Plivo.", message_uuid: response.messageUuid // Plivo's unique ID for the message request }); } catch (error) { // Log detailed error information internally console.error(`[Plivo API Error] Failed sending SMS to ${to}. Status: ${error.statusCode || 'N/A'}. Message: ${error.message}. Details:`, error); // Send a standardized error response to the client res.status(error.statusCode || 500).json({ error: "SMS sending failed.", // Optionally include a reference ID for correlation in production // error_reference: Date.now() // Simple example reference }); } }); /** * @route GET /health * @description Health check endpoint. */ app.get('/health', (req, res) => { // Basic health check: checks if the server is running // More advanced checks could verify DB connection, Plivo connectivity (with caution) etc. res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // --- Start the server --- // Check if the module is run directly if (require.main === module) { app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); } module.exports = app; // Export for testing2. Run the Server:
How Do You Build and Test the Plivo SMS API?
4. Building the API Layer
This section is now integrated into Section 3 where the
app.jscode, including the/send-smsand/healthendpoints, is presented.Testing the Endpoints:
Start the server:
node app.jsTest
/send-sms:curlor a tool like Postman.+1RECIPIENTNUMBERwith a real phone number (verified in your Plivo sandbox if using a trial account).curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "+1RECIPIENTNUMBER", "text": "Hello from Node.js and Plivo!" }'Expected Success Output (HTTP 202):
{ "message": "SMS request accepted by Plivo.", "message_uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] }Expected Validation Error (HTTP 400): (e.g., missing "to")
{ "error": "'to' and 'text' fields are required." }Expected Plivo Error (HTTP 500 or Plivo status): (e.g., bad credentials)
{ "error": "SMS sending failed." }Test
/health:Expected Output (HTTP 200):
{ "status": "UP", "timestamp": "YYYY-MM-DDTHH:mm:ss.sssZ" }What Are the Plivo Integration Details You Need to Know?
5. Integrating with Plivo (Details)
You've already initialized the client and used
messages.createinapp.js. Key points:new plivo.Client(process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN). Ensure.envvalues are correct. Find credentials on the Plivo Console Dashboard.src): Set viaPLIVO_SENDER_IDin.env. Must be a valid Plivo number (US/Canada) or potentially a registered Alphanumeric Sender ID. Manage numbers in the Phone Numbers section of the Plivo Console.PLIVO_AUTH_IDandPLIVO_AUTH_TOKEN. Use.envlocally and secure environment variables in production. Never commit credentials.How Do You Implement Error Handling for Plivo SMS?
6. Implementing Error Handling and Logging
Our
app.jsincludes basictry...catchblocks and improvedconsole.errorlogging.Note on Express 5.x: If using Express 5.x (released October 2024, requires Node.js 18+), async middleware can return rejected promises that are automatically caught by the router as errors, eliminating the need for manual try-catch blocks in many cases. This guide uses Express 4.x syntax for broader compatibility.
Retry Mechanisms (Conceptual Enhancement):
Plivo requests might fail transiently. Implementing retries can improve reliability. Note: The following is a conceptual example to illustrate the pattern. It is not integrated into the main
app.jscode provided in Section 3. Implementing this would require modifying the/send-smshandler to use this function instead of callingclient.messages.createdirectly.Conceptual Example Function:
async function sendSmsWithRetry(src, to, text, retries = 3, delay = 1000) { try { // Assume 'client' is the initialized Plivo client available in this scope return await client.messages.create(src, to, text); } catch (error) { // Only retry on specific error types (e.g., 5xx server errors, maybe 429 rate limit) // Avoid retrying permanent errors like 400 Bad Request or 401 Unauthorized. const shouldRetry = retries > 0 && (error.statusCode >= 500 || error.statusCode === 429); if (shouldRetry) { console.warn(`Retrying SMS to ${to}. Retries left: ${retries - 1}. Delay: ${delay}ms. Error: ${error.message}`); await new Promise(resolve => setTimeout(resolve, delay)); // Exponential backoff: double the delay for next retry return sendSmsWithRetry(src, to, text, retries - 1, delay * 2); } else { // Don't retry permanent errors or if retries exhausted console.error(`SMS to ${to} failed permanently or retries exhausted. Error: ${error.message}`); throw error; // Re-throw the error to be caught by the main handler } } } // To use this in the app.post('/send-sms', ...) handler, you would replace: // const response = await client.messages.create(src, to, text); // With: // const response = await sendSmsWithRetry(src, to, text);Why Retry? Handles temporary network or service issues. Exponential backoff prevents overwhelming the API during outages. Caution: Carefully choose which errors to retry.
Testing Error Scenarios:
.env.tonumber format (e.g., ""12345"").srcnumber in.env.What Security Features Should You Add to Your SMS API?
7. Adding Security Features
Protecting your API is crucial.
Input Validation and Sanitization:
app.jsincludes basic checks forto(presence, format) andtext(presence).joiorexpress-validatorto define schemas forreq.body, check data types, enforce length limits, and potentially sanitize inputs more rigorously. Example mention added inapp.jscomments.textlength if specific limits are desired beyond Plivo's segmentation.Authentication/Authorization for Your API (Conceptual Enhancement):
The
/send-smsendpoint in our baseapp.jsis currently open. This is insecure for production.Recommendation: Implement API Key Authentication (or other methods like JWT/OAuth).
Conceptual Example: The following shows how you could add API key middleware. This is not integrated into the main
app.jsexample but demonstrates the principle..env:INTERNAL_API_KEY=your_secret_key_here// ---- Conceptual API Key Middleware (Not applied in base app.js) ---- const API_KEY = process.env.INTERNAL_API_KEY; function authenticateApiKey(req, res, next) { if (!API_KEY) { console.error("INTERNAL_API_KEY not set. Cannot enforce API key authentication."); // Fail closed: If key isn't configured, deny access. return res.status(500).json({ error: 'Server configuration error' }); } const providedKey = req.headers['x-api-key']; if (!providedKey || providedKey !== API_KEY) { console.warn(`Authentication failed. Invalid or missing API key.`); // Use 403 Forbidden if the key is missing/wrong, 401 often implies possibility of authenticating return res.status(403).json({ error: 'Forbidden: Invalid API Key' }); } next(); // Key is valid, proceed } // ---- How to Apply (Example - Do not add this directly to base app.js unless implementing) ---- // To protect the /send-sms route, you would add this line *before* the route definition: // app.use('/send-sms', authenticateApiKey); // Or apply it globally (carefully): // app.use(authenticateApiKey); // Applies to all routes defined after this line // ---- End Conceptual Example ----X-API-Key: your_secret_key_herein their requests.Rate Limiting (Conceptual Enhancement):
express-rate-limit.app.jsexample.// ---- Conceptual Rate Limiting Setup (Not applied in base app.js) ---- // Place near the top of app.js const rateLimit = require('express-rate-limit'); const smsLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per `windowMs` message: { error: 'Too many SMS requests created from this IP, please try again after 15 minutes' }, standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // ---- How to Apply (Example - Do not add this directly to base app.js unless implementing) ---- // Apply the limiter to the SMS route: // app.use('/send-sms', smsLimiter); // ---- End Conceptual Example ----Why Rate Limit? Protects against DoS attacks and cost overruns.
HTTPS: Always use HTTPS in production (typically handled by load balancers, reverse proxies like Nginx, or hosting platforms) to encrypt data in transit.
How Do You Handle Special Cases in SMS Messaging?
8. Handling Special Cases (Briefly)
+countrycode...). Sender ID rules vary greatly by country (check Plivo docs/regulations).What Performance Optimizations Should You Consider?
9. Performance Optimizations (Considerations)
Asynchronous Operations: Node.js +
async/awaitis efficient.Connection Pooling (SDK): Plivo SDK likely handles this.
Caching: Not typically applicable for sending unique SMS.
Load Testing: Use tools (
k6,artillery,ab) to test/send-smsunder load. Monitor server resources (CPU, memory) and response times. Exampleabcommand:# Create payload.json: {""to"": ""+1..."", ""text"": ""Load test""} # Send 100 requests, 10 concurrently ab -n 100 -c 10 -p payload.json -T application/json http://localhost:3000/send-smsHow Do You Monitor Your Plivo SMS Application?
10. Monitoring and Observability (Considerations)
/healthendpoint (added in Section 3) provides a basic check. Monitoring services can use it.What Are Common Plivo SMS Issues and Solutions?
11. Troubleshooting and Caveats
401 Unauthorizedfrom Plivo: IncorrectPLIVO_AUTH_IDorPLIVO_AUTH_TOKENin.env.400 Bad Request(Invaliddst): Ensuretois valid E.164 format (+1...).400 Bad Request(Invalidsrc): EnsurePLIVO_SENDER_IDis a valid, SMS-enabled Plivo number or registered ID.402 Payment Required: Insufficient Plivo credits.Failed/Undelivered: Invalid destination, number blocked, carrier issues. Check Plivo logs. Trial accounts limited to verified numbers.ECONNREFUSED/Network Errors: Server can't reach Plivo API. Check connectivity, firewalls, Plivo status (status.plivo.com).plivoSDK updated (npm update plivo). Check github.com/plivo/plivo-node for releases.How Do You Deploy a Plivo SMS Application to Production?
12. Deployment and CI/CD
PLIVO_AUTH_ID,PLIVO_AUTH_TOKEN,PLIVO_SENDER_ID,PORT,INTERNAL_API_KEY(if used), etc. NEVER hardcode.NODE_ENV=production: Set this env var for performance.pm2, systemd, Docker, or platform's manager to keep the app running. (pm2 start app.js --name plivo-sms)main).npm ci).npm test). Fail pipeline on errors.How Do You Verify and Test Your Plivo Integration?
13. Verification and Testing
curl/Postman to/send-sms.202 Acceptedresponse andmessage_uuid./healthendpoint -> verify200 OKand status UP.to/textmissing/invalid format) -> verify400 Bad Request.403 Forbidden.429 Too Many Requests.Automated Testing (Examples - using Jest):
npm install --save-dev jest supertestapp.test.js):// app.test.js const request = require('supertest'); const app = require('./app'); // Import your express app // Mock the Plivo client to avoid actual API calls/costs during tests // Mock implementation simulates a successful API call by default const mockCreate = jest.fn().mockResolvedValue({ message: [""SMS request queued for delivery""], messageUuid: [""mock-uuid-"" + Date.now()], apiId: ""mock-api-id"" }); jest.mock('plivo', () => ({ Client: jest.fn().mockImplementation(() => ({ messages: { create: mockCreate // Use the mock function } })) })); // Load .env for consistency, but tests might override or rely on mocks require('dotenv').config(); describe('SMS API Endpoints', () => { // Clear mocks before each test to ensure isolation beforeEach(() => { mockCreate.mockClear(); // Reset to default success behavior if needed after failure tests mockCreate.mockResolvedValue({ message: [""SMS request queued for delivery""], messageUuid: [""mock-uuid-"" + Date.now()], apiId: ""mock-api-id"" }); }); // --- Test Suite for POST /send-sms --- describe('POST /send-sms', () => { const validPayload = { to: '+15551234567', text: 'Test message' }; it('should return 400 Bad Request if ""to"" is missing', async () => { const res = await request(app) .post('/send-sms') .send({ text: 'Missing to field' }); // Missing 'to' expect(res.statusCode).toEqual(400); expect(res.body).toHaveProperty('error', ""'to' and 'text' fields are required.""); expect(mockCreate).not.toHaveBeenCalled(); // Plivo should not be called }); it('should return 400 Bad Request if ""text"" is missing', async () => { const res = await request(app) .post('/send-sms') .send({ to: '+15551234567' }); // Missing 'text' expect(res.statusCode).toEqual(400); expect(res.body).toHaveProperty('error', ""'to' and 'text' fields are required.""); expect(mockCreate).not.toHaveBeenCalled(); }); it('should return 400 Bad Request for invalid ""to"" format', async () => { const res = await request(app) .post('/send-sms') .send({ to: '1234567890', text: 'Invalid number format' }); // Invalid 'to' expect(res.statusCode).toEqual(400); expect(res.body).toHaveProperty('error', expect.stringContaining(""Invalid 'to' number format"")); expect(mockCreate).not.toHaveBeenCalled(); }); it('should return 202 Accepted and message_uuid on valid request', async () => { const res = await request(app) .post('/send-sms') .send(validPayload); // Valid payload expect(res.statusCode).toEqual(202); expect(res.body).toHaveProperty('message', 'SMS request accepted by Plivo.'); expect(res.body).toHaveProperty('message_uuid'); expect(mockCreate).toHaveBeenCalledTimes(1); expect(mockCreate).toHaveBeenCalledWith( process.env.PLIVO_SENDER_ID, // Check if called with correct args validPayload.to, validPayload.text ); }); it('should return 500 if Plivo API call fails', async () => { // Simulate Plivo SDK throwing an error const plivoError = new Error(""Plivo simulated error""); plivoError.statusCode = 503; // Example Plivo error status mockCreate.mockRejectedValueOnce(plivoError); // Make the mock throw an error const res = await request(app) .post('/send-sms') .send(validPayload); expect(res.statusCode).toEqual(503); // Should reflect Plivo's error code if available expect(res.body).toHaveProperty('error', 'SMS sending failed.'); expect(mockCreate).toHaveBeenCalledTimes(1); // Ensure it was called }); }); // --- Test Suite for GET /health --- describe('GET /health', () => { it('should return 200 OK and status UP', async () => { const res = await request(app).get('/health'); expect(res.statusCode).toEqual(200); expect(res.body).toHaveProperty('status', 'UP'); expect(res.body).toHaveProperty('timestamp'); }); }); });Frequently Asked Questions About Plivo SMS with Node.js
What Node.js version is required for Plivo integration?
You need Node.js 18 or higher for modern Plivo implementations. As of January 2025, Node.js 22 is the current LTS version recommended for production applications. Node.js 18.x reaches end-of-life on April 30, 2025, so plan to upgrade to Node.js 20 or 22 for continued support and security updates.
What are the Plivo SMS rate limits?
Plivo assigns default rate limits at signup: 5 messages per second (MPS) for SMS and 0.25 MPS for MMS. The API supports up to 100 simultaneous requests by default. Messages exceeding these limits are queued, not dropped. You can increase capacity by distributing traffic across multiple Plivo numbers or contacting support for higher MPS allocations (additional fees apply).
How do you secure a Plivo SMS API endpoint?
Implement multiple security layers: use API key authentication via custom headers (e.g.,
X-API-Key), add rate limiting withexpress-rate-limitto prevent abuse, validate all input data using libraries likejoiorexpress-validator, always use HTTPS in production, and store credentials in environment variables never in code. For production, consider implementing JWT tokens or OAuth 2.0 for more robust authentication.What is E.164 phone number format for Plivo?
E.164 format is the international phone number standard required by Plivo: it starts with
+followed by the country code and subscriber number with no spaces or special characters. Examples:+14155551234(US),+442071234567(UK),+91981234567(India). All phone numbers in Plivo API calls must use this format for both source and destination numbers.Does Plivo support sending SMS internationally?
Yes, Plivo supports international SMS to 190+ countries, but sender ID requirements vary significantly by country. US and Canada require Plivo phone numbers purchased through the console. Other countries may accept Alphanumeric Sender IDs with registration. Check Plivo's country-specific documentation for sender ID requirements, pricing, and any regulatory restrictions before sending to international destinations.
How do you handle SMS delivery failures with Plivo?
Implement robust error handling: catch Plivo API errors in try-catch blocks, log detailed error information including status codes, implement retry logic with exponential backoff for transient failures (5xx errors, 429 rate limits), configure webhook endpoints to receive delivery status callbacks, store message status in a database for tracking, and monitor common errors like 401 (invalid credentials), 402 (insufficient credits), and 400 (invalid parameters).
What is the difference between Express 4.x and Express 5.x for Plivo?
Express 5.x (released October 2024) requires Node.js 18+ and includes built-in async error handling where rejected promises are automatically caught by the router, eliminating many manual try-catch blocks. Express 4.x requires explicit error handling in async routes. Both versions work with Plivo SDK, but Express 5.x provides cleaner async/await syntax and improved security through updated dependencies.
How much does it cost to send SMS with Plivo?
Plivo SMS pricing varies by destination country and typically ranges from $0.0035 to $0.10 per message segment. US SMS costs approximately $0.0079 per segment. Messages over 160 GSM characters or 70 Unicode characters are split into multiple segments, each billed separately. MMS messages cost more than SMS. Check your Plivo Console dashboard for real-time pricing for specific countries and monitor your usage to control costs.
Can you send SMS from a trial Plivo account?
Yes, but with limitations: trial accounts can only send SMS to phone numbers verified in your Plivo Console (Phone Numbers → Sandbox Numbers). Messages are prefixed with a trial account notice. You have limited free credits. To send to any number, upgrade to a paid account by adding funds to your Plivo account. Trial accounts are perfect for development and testing before production deployment.
How do you implement webhook callbacks for Plivo SMS delivery status?
Configure a webhook URL in your Plivo Console (Messaging → Applications) pointing to a publicly accessible endpoint in your application (e.g.,
/plivo/status). Create a route that accepts POST requests from Plivo containing delivery receipt data. Validate webhook signatures using HMAC SHA1 with your auth token to ensure requests come from Plivo. Parse the status (delivered, failed, undelivered) and update your database records accordingly. Use services like ngrok for local development testing.