This guide provides a complete walkthrough for setting up a Node.js application using the Express framework to send SMS messages via the Plivo API. We'll cover everything from initial project setup and configuration to sending messages and handling potential issues, ensuring you can build a robust SMS sending feature.
By the end of this guide, you'll have a functional Express API endpoint capable of sending SMS messages programmatically, along with the knowledge to integrate this capability into larger applications.
Project Overview and Goals
Goal: To build a simple Node.js Express API that accepts a destination phone number and a message text, then uses the Plivo API to send an SMS message.
Problem Solved: Enables applications to programmatically send SMS notifications, alerts, verification codes, or other messages without manual intervention.
Technologies Used:
- Node.js: A JavaScript runtime environment ideal for building scalable network applications. Its event-driven, non-blocking I/O model is well-suited for API development.
- Express.js: A minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications, simplifying API creation.
- Plivo Node.js SDK: A library provided by Plivo to easily interact with their communication APIs from Node.js applications.
- Plivo API: The third-party service used for actual SMS delivery.
- dotenv: A module to load environment variables from a
.env
file intoprocess.env
, keeping sensitive credentials out of source code.
System Architecture:
+-------------+ +------------------------+ +-------------+ +-----------------+
| Client |------>| Node.js/Express Server |------>| Plivo SDK |------>| Plivo API/Infra |
| (e.g. curl, | | (API Endpoint) | | (Node Module) | | (Sends SMS) |
| Postman) | +------------------------+ +-------------+ +-----------------+
+-------------+
| POST /send-sms
| { ""to"": ""..."", ""text"": ""..."" }
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. (Download from nodejs.org)
- A Plivo Account: Sign up at plivo.com. A trial account is sufficient to start.
- A Plivo Phone Number: You need an SMS-enabled Plivo phone number to send messages, especially to the US and Canada. You can purchase one from the Plivo console (
Phone Numbers
>Buy Numbers
). Alternatively, for some regions, you might register an Alphanumeric Sender ID. - Plivo Auth ID and Auth Token: Found on the dashboard of your Plivo console after logging in.
- Basic understanding of JavaScript and Node.js.
- Familiarity with REST APIs and terminal/command line usage.
Final Outcome: A running Express server with a /send-sms
endpoint that successfully sends an SMS via Plivo when called with valid credentials and parameters.
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal or command prompt and create a new directory for your project, then navigate into it.
mkdir plivo-sms-sender cd plivo-sms-sender
-
Initialize Node.js Project: This command creates a
package.json
file, which tracks your project's metadata and dependencies.npm init -y
(The
-y
flag accepts the default settings) -
Install Dependencies: We need Express for the web server, the Plivo Node.js SDK to interact with the API, and
dotenv
for managing environment variables.npm install express plivo dotenv
-
Set Up Project Structure: Create a basic structure for clarity.
mkdir src touch src/server.js touch .env touch .gitignore
src/server.js
: This will contain our Express application code..env
: This file will store our sensitive Plivo credentials. Never commit this file to version control..gitignore
: Specifies intentionally untracked files that Git should ignore.
-
Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.node_modules/ .env *.log
-
Configure Environment Variables (
.env
): Open the.env
file and add your Plivo credentials and sender ID/number.- Purpose:
PLIVO_AUTH_ID
: Your specific Plivo Account Auth ID for API authentication.PLIVO_AUTH_TOKEN
: Your specific Plivo Account Auth Token for API authentication.PLIVO_SENDER_ID
: The Plivo phone number (in E.164 format, e.g.,+14155551212
) or approved Alphanumeric Sender ID that will appear as the message source.
- How to Obtain:
- Log in to your Plivo Console.
- Your
Auth ID
andAuth Token
are displayed prominently on the dashboard/overview page. - To get a
PLIVO_SENDER_ID
(phone number): Navigate toPhone Numbers
->Buy Numbers
to purchase an SMS-enabled number if you don't have one. If you have one, go toPhone Numbers
->Your Numbers
to see it. Ensure it's in E.164 format. For Alphanumeric Sender IDs (availability varies by country), check Plivo's documentation or support for registration requirements.
- Format:
# Plivo Credentials - KEEP THESE SECRET PLIVO_AUTH_ID=YOUR_AUTH_ID_HERE PLIVO_AUTH_TOKEN=YOUR_AUTH_TOKEN_HERE # Plivo Sender Number or Alphanumeric ID # Use E.164 format for numbers (e.g., +12025551234) PLIVO_SENDER_ID=YOUR_PLIVO_NUMBER_OR_SENDER_ID
Replace the placeholder values with your actual credentials.
- Purpose:
2. Implementing Core Functionality (Sending SMS)
The core logic involves initializing the Plivo client and using it to send a message. We'll wrap this in our Express server setup.
Edit src/server.js
:
// src/server.js
// 1. Import Dependencies
require('dotenv').config(); // Load environment variables from .env file
const express = require('express');
const plivo = require('plivo');
// 2. Initialize Express App
const app = express();
app.use(express.json()); // Middleware to parse JSON request bodies
// 3. Check for Essential Environment Variables
const { PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, PLIVO_SENDER_ID } = process.env;
if (!PLIVO_AUTH_ID || !PLIVO_AUTH_TOKEN || !PLIVO_SENDER_ID) {
console.error(
'Error: Missing Plivo credentials or Sender ID 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
}
// 4. Initialize Plivo Client
// The Plivo SDK automatically uses the environment variables PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN
// if they are set, but we initialize explicitly for clarity and potential future flexibility.
const client = new plivo.Client(PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN);
// 5. Define the SMS Sending Logic (will be used in the API endpoint)
async function sendSmsMessage(destinationNumber, messageText) {
console.log(`Attempting to send SMS to ${destinationNumber}`);
try {
const response = await client.messages.create(
PLIVO_SENDER_ID, // src: Sender ID or Plivo number
destinationNumber, // dst: Destination number in E.164 format
messageText // text: The content of the SMS message
);
console.log('Plivo API Response:', response);
// The response contains information like the message UUID
// which is useful for tracking message status later.
return { success: true, data: response };
} catch (error) {
console.error('Error sending SMS via Plivo:', error);
// Plivo errors often have more details in error.response or error.message
// It's good practice to log the full error for debugging.
return { success: false, error: error.message || 'Unknown error sending SMS' };
}
}
// 6. Basic Health Check Endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'OK', timestamp: new Date().toISOString() });
});
// 7. Define the API Endpoint (Details in next section)
// 8. Start the Server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Plivo Sender ID configured: ${PLIVO_SENDER_ID}`);
});
// Placeholder for API endpoint - we'll add it in the next step
module.exports = { app, sendSmsMessage }; // Export for potential testing
Explanation:
- We import
dotenv
first to load variables before they're needed. - We initialize Express and use
express.json()
middleware to handle incoming JSON payloads. - We explicitly check if the required Plivo credentials are loaded from
.env
. The application exits if they are missing, preventing runtime errors later. - We create an instance of the Plivo client using the Auth ID and Token.
sendSmsMessage
is anasync
function encapsulating the Plivo API call. It takes the destination number and text as arguments.client.messages.create()
is the core SDK method.- It requires
src
(sender),dst
(destination), andtext
(message). - We wrap the call in a
try...catch
block for basic error handling (more on this later). - It returns an object indicating success or failure, along with the Plivo response or error message.
- A simple
/health
endpoint is good practice for monitoring. - Placeholder for the API endpoint definition.
- The server starts listening on a configurable port (defaulting to 3000).
3. Building the API Layer
Now, let's create the specific endpoint that clients will call to send an SMS.
Add the following code to src/server.js
just before the // 8. Start the Server
section:
// src/server.js
// ... (keep existing code from steps 1-6) ...
// 7. Define the API Endpoint
app.post('/send-sms', async (req, res) => {
// Basic Input Validation
const { to, text } = req.body;
if (!to || !text) {
return res.status(400).json({ error: ""Missing required fields: 'to' and 'text'."" });
}
// Simple validation for E.164 format (starts with +, followed by digits)
// More robust validation might be needed in production.
if (!/^\+[1-9]\d{1,14}$/.test(to)) {
return res.status(400).json({ error: ""Invalid 'to' phone number format. Use E.164 format (e.g., +12025551234)."" });
}
if (typeof text !== 'string' || text.trim().length === 0) {
return res.status(400).json({ error: ""Invalid 'text' field. Must be a non-empty string."" });
}
// Call the SMS sending logic
const result = await sendSmsMessage(to, text);
// Respond to the client
if (result.success) {
res.status(200).json({
message: 'SMS sent successfully.',
details: result.data // Includes message_uuid etc.
});
} else {
// Avoid exposing detailed internal errors to the client in production.
// Log the detailed error server-side (already done in sendSmsMessage).
res.status(500).json({
error: 'Failed to send SMS.',
// Optionally include a generic error identifier for correlation
// errorId: generateSomeErrorId()
});
}
});
// ... (keep existing code from step 8) ...
Explanation:
- We define a
POST
route/send-sms
. - Input Validation: We check if
to
andtext
are present in the request body (req.body
). We also perform a basic regular expression check to ensure theto
number looks like the E.164 format required by Plivo and thattext
is a non-empty string. This is crucial for security and preventing errors. - We call our
sendSmsMessage
function with the validated inputs. - Response Handling:
- If
sendSmsMessage
returnssuccess: true
, we send a200 OK
response with the details from Plivo (like themessage_uuid
). - If it returns
success: false
, we send a generic500 Internal Server Error
. We avoid sending the rawerror
message back to the client for security reasons but ensure it's logged server-side within thesendSmsMessage
function.
- If
Testing the Endpoint:
-
Start the Server:
node src/server.js
You should see ""Server running on port 3000"" and your configured Sender ID.
-
Use
curl
or Postman:-
Using
curl
(replace placeholders):curl -X POST http://localhost:3000/send-sms \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""+1_DESTINATION_PHONE_NUMBER"", ""text"": ""Hello from my Node.js Plivo app! Test 123."" }'
(Replace
+1_DESTINATION_PHONE_NUMBER
with a valid phone number in E.164 format. If using a Plivo Trial account, this number must be verified in your Plivo console underPhone Numbers
>Sandbox Numbers
.) -
Using Postman:
- Set the request type to
POST
. - Enter the URL:
http://localhost:3000/send-sms
- Go to the
Body
tab, selectraw
, and chooseJSON
from the dropdown. - Enter the JSON payload:
{ ""to"": ""+1_DESTINATION_PHONE_NUMBER"", ""text"": ""Hello from my Postman Plivo test!"" }
- Click
Send
.
- Set the request type to
-
Expected Responses:
-
Success (200 OK):
{ ""message"": ""SMS sent successfully."", ""details"": { ""message"": ""message(s) queued"", ""messageUuid"": [""xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx""], ""apiId"": ""xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"" } }
You should also receive the SMS on the destination phone shortly after. Check the server console logs for
Plivo API Response:
. -
Validation Error (400 Bad Request):
{ ""error"": ""Invalid 'to' phone number format. Use E.164 format (e.g., +12025551234)."" }
Or a similar error for missing fields.
-
Server Error (500 Internal Server Error):
{ ""error"": ""Failed to send SMS."" }
Check your server console logs for the detailed error message from the
catch
block insendSmsMessage
. This could be due to invalid Plivo credentials, insufficient account balance, an invalid sender ID, network issues, or Plivo API errors.
4. Integrating with Plivo (Configuration Recap)
We've already integrated the Plivo SDK and handled the core configuration, but let's reiterate the key points for clarity:
- Credentials:
PLIVO_AUTH_ID
andPLIVO_AUTH_TOKEN
are mandatory for authentication. Obtain them from your Plivo Console Dashboard. - Sender ID:
PLIVO_SENDER_ID
is the 'from' number or ID.- US/Canada: Must be a Plivo phone number (long code or Toll-Free) enabled for SMS. Purchase from
Phone Numbers
>Buy Numbers
. - Other Regions: Can often be a Plivo number or a pre-approved Alphanumeric Sender ID (e.g., ""MyApp""). Rules vary significantly by country (see Plivo's documentation or the ""Caveats"" section below regarding India).
- Format: Always use E.164 format for phone numbers (e.g.,
+14155551212
).
- US/Canada: Must be a Plivo phone number (long code or Toll-Free) enabled for SMS. Purchase from
- Secure Storage: Use a
.env
file (and ensure it's in.gitignore
) to store these sensitive details, accessed viaprocess.env
using thedotenv
package. Never hardcode credentials directly in your source code. - Trial Account Limitations: If using a trial account, you can typically only send SMS to phone numbers that you have explicitly verified in the Plivo console (
Phone Numbers
>Sandbox Numbers
).
(No new code needed here, this section is a reinforcement of configuration best practices.)
5. Error Handling, Logging, and Retry Mechanisms
Our current error handling is basic. Production applications require more robustness.
Error Handling Strategy:
- Catch Specific Errors: The Plivo SDK might throw specific error types or include error codes in the response. Check the Plivo API documentation for common error codes (e.g., authentication failure, invalid destination number, insufficient funds). Handle known, recoverable errors differently if possible.
- Consistent Logging: Log errors with sufficient context (timestamp, request ID if applicable, error details, stack trace).
- User-Facing Errors: Don't expose internal details. Provide generic error messages to the client but log specifics server-side.
- Monitoring: Integrate with an error tracking service (like Sentry, Datadog) to aggregate and alert on errors.
Logging:
console.log
and console.error
are fine for development, but for production, use a dedicated logging library like winston
or pino
. These offer:
- Log Levels: (debug, info, warn, error)
- Structured Logging: (JSON format for easier parsing by log analysis tools)
- Transports: Output logs to files, databases, or external services.
Example using console
(enhancement):
// Enhance logging in sendSmsMessage (src/server.js)
async function sendSmsMessage(destinationNumber, messageText) {
const logContext = { // Add context for easier debugging
destination: destinationNumber,
sender: PLIVO_SENDER_ID,
timestamp: new Date().toISOString()
};
console.info('Attempting to send SMS', logContext); // Use info level
try {
const response = await client.messages.create(
PLIVO_SENDER_ID,
destinationNumber,
messageText
);
console.info('Plivo API Success Response:', { ...logContext, response });
return { success: true, data: response };
} catch (error) {
// Log the full error object for detailed debugging
console.error('Error sending SMS via Plivo', { ...logContext, error: error.stack || error });
return { success: false, error: error.message || 'Unknown error sending SMS' };
}
}
Retry Mechanisms:
SMS sending is usually asynchronous on Plivo's side (the API confirms queuing, not delivery). Retrying the API call itself should typically only be done for transient network errors (e.g., connection timeouts) or specific Plivo errors indicating a temporary issue (e.g., rate limiting 429 Too Many Requests
).
- Strategy: Implement exponential backoff for retries. Wait longer between successive attempts.
- Caution: Avoid retrying on definitive failures (e.g., invalid credentials, invalid number) as this will just waste resources and potentially incur costs. Do not retry just because the API call succeeded but the message wasn't delivered instantly – delivery status is handled separately (often via webhooks, which is beyond this basic guide).
Simple conceptual retry (not fully implemented):
// Conceptual retry logic - requires a more robust implementation
async function sendSmsWithRetry(destinationNumber, messageText, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
attempt++;
console.log(`Sending SMS attempt ${attempt}/${maxRetries}...`);
try {
const response = await client.messages.create(PLIVO_SENDER_ID_ destinationNumber_ messageText);
console.log('Plivo API Success:'_ response);
return { success: true_ data: response };
} catch (error) {
console.error(`Attempt ${attempt} failed:`_ error.message);
// Check if the error is potentially retryable (e.g._ network issue_ 5xx from Plivo_ 429 rate limit)
// Note: Plivo error structure might vary; inspect actual errors.
// 'statusCode' might not be directly on the error object.
const statusCode = error.statusCode || (error.response && error.response.status);
const isRetryable = (statusCode >= 500 || statusCode === 429);
if (isRetryable && attempt < maxRetries) {
const delay = Math.pow(2_ attempt) * 100; // Exponential backoff (e.g._ 200ms_ 400ms_ 800ms)
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
console.error('Non-retryable error or max retries reached.');
return { success: false, error: error.message || 'Failed after retries' };
}
}
}
// This line should ideally not be reached if logic is correct
return { success: false, error: 'Max retries reached' };
}
// In the API endpoint, you would call sendSmsWithRetry instead of sendSmsMessage:
// const result = await sendSmsWithRetry(to, text);
(Note: This retry logic is basic. Production systems often use libraries like async-retry
or integrate with job queues for more complex scenarios.)
6. Creating a Database Schema and Data Layer (Conceptual)
While not strictly required for just sending an SMS, a real application would likely log SMS attempts and statuses to a database.
Conceptual Schema (e.g., PostgreSQL):
CREATE TABLE sms_logs (
id SERIAL PRIMARY KEY, -- Unique identifier for the log entry
recipient_number VARCHAR(20) NOT NULL, -- Destination phone number (E.164)
sender_id VARCHAR(20) NOT NULL, -- Plivo sender number/ID used
message_body TEXT NOT NULL, -- Content of the SMS
plivo_message_uuid VARCHAR(50) UNIQUE, -- Plivo's unique ID for the message (if successfully queued)
status VARCHAR(20) NOT NULL DEFAULT 'initiated', -- e.g., initiated, queued, failed_to_queue, sent, delivered, failed
error_message TEXT, -- Details if status is failed_to_queue
attempted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- When the API call was made
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- When the status was last updated (e.g., via webhook)
);
-- Index for querying by Plivo UUID (useful for webhook updates)
CREATE INDEX idx_sms_logs_plivo_uuid ON sms_logs(plivo_message_uuid);
-- Index for querying by recipient
CREATE INDEX idx_sms_logs_recipient ON sms_logs(recipient_number);
Data Layer Integration:
You would typically use an ORM (Object-Relational Mapper) like Prisma or Sequelize in your Node.js application to interact with the database.
- Before API Call: Create a record in
sms_logs
with status'initiated'
. - After Successful API Call: Update the record with the
plivo_message_uuid
and set status to'queued'
. - After Failed API Call: Update the record with status
'failed_to_queue'
and populateerror_message
. - Webhooks (Advanced): Configure Plivo webhooks to receive delivery status updates (sent, delivered, failed) and update the corresponding record in
sms_logs
using theplivo_message_uuid
.
(Implementation details for ORMs and webhooks are beyond this basic guide but crucial for production tracking.)
7. Adding Security Features
Security is paramount when dealing with APIs and external services.
-
Input Validation (Implemented): We added basic checks for
to
andtext
formats in the API endpoint. Use libraries likejoi
orexpress-validator
for more complex validation schemas in larger applications. Sanitize inputs to prevent injection attacks if user-provided content is used directly in sensitive contexts (though less critical for the SMStext
itself unless it's processed further). -
Rate Limiting: Protect your API from abuse and control costs. Use middleware like
express-rate-limit
.npm install express-rate-limit
// In src/server.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: 'Too many SMS requests 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 }); // Apply the rate limiting middleware *before* the main logic app.post('/send-sms', smsLimiter, async (req, res) => { // ... existing endpoint logic ... });
-
API Key/Authentication: For internal or partner use, protect the
/send-sms
endpoint itself. Implement API key checking, JWT authentication, or other mechanisms so only authorized clients can trigger SMS sends. -
Secrets Management (Implemented): Use
.env
and.gitignore
to keep Plivo credentials secure. In production, use managed secret stores (like AWS Secrets Manager, Google Secret Manager, HashiCorp Vault). -
HTTPS: Always run your Express server behind a reverse proxy (like Nginx or Caddy) configured with TLS/SSL certificates (e.g., using Let's Encrypt) to encrypt traffic. Do not run Node.js directly exposed on port 80/443 in production.
-
Dependency Security: Regularly audit dependencies for known vulnerabilities using
npm audit
and update them.
8. Handling Special Cases
Real-world SMS sending involves nuances:
- Character Encoding (GSM vs. Unicode):
- Standard SMS uses the 7-bit GSM 03.38 character set (160 characters per segment).
- Including characters outside this set (like many emojis or non-Latin characters) forces the message into Unicode (UCS-2) encoding, reducing the segment length to 70 characters.
- Plivo handles this automatically, but be aware that Unicode messages cost the same per segment but fit less text, potentially increasing the total cost for long messages. See Plivo's Encoding Guide.
- Multipart Messages (Concatenation):
- Messages exceeding the single segment limit (160 GSM or 70 Unicode) are split into multiple parts but should be reassembled (concatenated) on the recipient's device.
- Plivo handles the splitting and includes necessary headers (UDH) for concatenation automatically.
- The
client.messages.create
call doesn't change; just provide the long text. You'll be billed per segment sent.
- E.164 Format (Implemented): Destination numbers (
dst
) must be in E.164 format (e.g.,+14155551212
,+442071838750
). Ensure your application validates or converts numbers to this format before calling the API. - Sender ID Regulations (Country Specific):
- India: Sending SMS to India often requires registering an Alphanumeric Sender ID with operators. Using a standard phone number as the
src
might result in it being replaced by a generic shortcode (likeVM-PLVS
). Contact Plivo support or check their country-specific guidelines. - Other Countries: Regulations vary widely regarding allowed Sender ID types (Numeric, Alphanumeric) and registration requirements. Always consult Plivo's documentation for your target countries.
- India: Sending SMS to India often requires registering an Alphanumeric Sender ID with operators. Using a standard phone number as the
- Opt-Out Handling: Regulations (like TCPA in the US) require honoring opt-out requests (e.g., replying STOP). While receiving replies is beyond this basic sending guide, a production system needs mechanisms (like Plivo webhooks for incoming messages) to manage opt-out lists and prevent sending to users who have opted out.
9. Implementing Performance Optimizations
For basic single SMS sends, performance optimization is minimal. If sending bulk messages:
- Asynchronous Operations: Node.js is inherently asynchronous. Our use of
async/await
is correct. - Bulk Sending:
- Plivo's Method: Plivo's API supports sending to multiple destination numbers in a single API call by separating them with
<
in thedst
field (e.g._+14155551212<+14155551213
). This is the most efficient way via Plivo.// Example for Plivo's bulk method const destinations = ['+14155551212'_ '+14155551213']; const destinationString = destinations.join('<'); // ... call client.messages.create(PLIVO_SENDER_ID_ destinationString_ text) ...
- Parallel Calls (
Promise.all
): If you need to send different messages or cannot use the<
method_ send individual API calls in parallel usingPromise.all
. This is less efficient network-wise than Plivo's bulk method but faster than sending sequentially.// Example using Promise.all const messagesToSend = [ { to: '+14155551212'_ text: 'Message 1' }_ { to: '+14155551213'_ text: 'Message 2' } ]; const sendPromises = messagesToSend.map(msg => sendSmsMessage(msg.to, msg.text) // Assuming sendSmsMessage handles one message ); const results = await Promise.all(sendPromises); // Process results array (contains success/failure for each)
- Plivo's Method: Plivo's API supports sending to multiple destination numbers in a single API call by separating them with
- Connection Pooling: The Plivo SDK likely handles HTTP connection management, but ensure keep-alive is enabled if configuring HTTP agents manually (usually not needed with the SDK).
- Resource Limits: Monitor Node.js process CPU and memory usage under load. Use tools like
pm2
in production to manage Node.js processes.
10. Adding Monitoring, Observability, and Analytics
For production visibility:
- Health Checks (Implemented): The basic
/health
endpoint allows load balancers or monitoring systems (like UptimeRobot, Pingdom) to check if the service is running. - Performance Metrics:
- Use libraries like
prom-client
to expose application-level metrics (e.g., number of SMS sent, API call latency, error rates) in Prometheus format. - Monitor Node.js runtime metrics (event loop lag, heap usage, CPU usage) using tools like
node-clinic
or APM solutions.
- Use libraries like
- Error Tracking (Mentioned): Integrate services like Sentry, Bugsnag, or Datadog APM. They capture unhandled exceptions and log errors with rich context, providing alerts and dashboards.
# Conceptual Sentry Integration - Install Dependencies npm install @sentry/node @sentry/tracing
// Conceptual Sentry Integration // In server.js (early initialization) // const Sentry = require('@sentry/node'); // const Tracing = require('@sentry/tracing'); // Correct import if using tracing // Sentry.init({ dsn: ""YOUR_SENTRY_DSN"", tracesSampleRate: 1.0, integrations: [new Tracing.Integrations.Express()] }); // Configure Express integration // // The request handler must be the first middleware on the app // app.use(Sentry.Handlers.requestHandler()); // // TracingHandler creates transactions for each request // app.use(Sentry.Handlers.tracingHandler()); // // ... your routes ... // // The error handler must be before any other error middleware and after all controllers // app.use(Sentry.Handlers.errorHandler()); // // Optional fallthrough error handler // app.use(function onError(err, req, res, next) { // res.statusCode = 500; // res.end(res.sentry + ""\n""); // });
- Logging Analysis (Mentioned): Ship structured logs (JSON) to a centralized logging platform (like Elasticsearch/Logstash/Kibana (ELK), Datadog Logs, Splunk). Create dashboards to visualize SMS volume, error rates, and search logs for specific issues using
plivo_message_uuid
or recipient numbers. - Plivo Console/Analytics: Utilize Plivo's built-in console logs and analytics features to track message statuses, delivery rates, and costs.
11. Troubleshooting and Caveats
Common issues and things to watch out for:
- Invalid Credentials: (
401 Unauthorized
error from Plivo) Double-checkPLIVO_AUTH_ID
andPLIVO_AUTH_TOKEN
in your.env
file. Ensure they are copied correctly from the Plivo console and have no extra spaces. - Trial Account Restrictions:
- Unverified Destination Number: (
Error: Cannot send SMS to unverified number on a trial account
or similar) Verify the destination number inPhone Numbers
>Sandbox Numbers
on the Plivo console. - Limited Sender ID: Trial accounts might restrict the
src
number you can use. Often, you must use a Plivo number obtained during signup.
- Unverified Destination Number: (
- Insufficient Funds: (
402 Payment Required
or similar) Check your Plivo account balance. - Invalid
dst
Number: (400 Bad Request
with error message about the number) Ensure the number is in valid E.164 format (+
followed by country code and number, no spaces or dashes). - Invalid
src
Number/Sender ID: (400 Bad Request
with error message aboutsrc
) Ensure the sender ID is a valid, SMS-enabled Plivo number you own or a registered Alphanumeric ID approved for the destination country. Check country-specific regulations. - Rate Limits: (
429 Too Many Requests
) You are sending requests faster than allowed by Plivo or your rate limiter. Implement backoff or reduce sending frequency. - Network Issues:
ECONNREFUSED
,ETIMEDOUT
. Check network connectivity from your server to Plivo's API endpoints (api.plivo.com
). Firewalls might block outgoing connections. - Message Encoding Issues: If messages arrive garbled, check if non-GSM characters were intended and ensure systems handle Unicode correctly.
- Delivery Delays: SMS delivery times can vary based on carrier networks. The Plivo API response confirms queuing, not final delivery. Use webhooks for delivery status.
- Country-Specific Blocking/Filtering: Some countries or carriers may filter messages based on content or sender ID. Test thoroughly for target regions.