This guide provides a step-by-step walkthrough for building a Node.js application using the Express framework to send and receive SMS messages via the Vonage Messages API. We will cover project setup, sending messages, handling incoming messages and delivery receipts via webhooks, configuration, error handling, security considerations, and deployment.
By the end of this tutorial, you will have a functional application capable of programmatic SMS communication, forming the backbone for SMS marketing campaigns, notifications, or interactive services. We will use Node.js for its asynchronous capabilities, Express for its robust web framework features, and the Vonage Messages API for its versatile multi-channel communication support, focusing specifically on SMS.
Project Overview and Goals
What We'll Build:
A Node.js application that can:
- Send SMS messages programmatically using the Vonage Messages API.
- Receive incoming SMS messages sent to a Vonage virtual number via an Express webhook.
- Receive message delivery status updates (Delivery Receipts - DLRs) via an Express webhook.
Problem Solved:
This application provides the core infrastructure needed to integrate SMS capabilities into larger systems. It enables businesses to automate sending messages for marketing, alerts, or two-factor authentication, and to process replies or status updates programmatically.
Technologies Used:
- Node.js: A JavaScript runtime environment ideal for building scalable, asynchronous network applications.
- Express: A minimal and flexible Node.js web application framework providing robust features for web and mobile applications.
- Vonage Messages API: A powerful API enabling communication across multiple channels (SMS, MMS, WhatsApp, etc.). We will focus on its SMS capabilities.
@vonage/server-sdk
: The official Vonage Node.js SDK for interacting with the Vonage APIs.ngrok
: A tool to expose local development servers to the internet, essential for testing webhooks.dotenv
: A module to load environment variables from a.env
file intoprocess.env
.
System Architecture:
graph LR
subgraph Your Application
direction LR
A[Node.js/Express App] -->|Sends SMS via SDK| B(Vonage SDK);
C(Webhook Endpoint: /webhooks/inbound) <-. Receives Inbound SMS .- D(Vonage Platform);
E(Webhook Endpoint: /webhooks/status) <-. Receives DLRs .- D;
end
subgraph External
direction LR
B -->|API Call (HTTPS)| D;
D -->|Sends SMS| F([End User Phone]);
F -->|Sends SMS| D;
end
style Your Application fill:#f9f,stroke:#333,stroke-width:2px
style External fill:#ccf,stroke:#333,stroke-width:2px
Prerequisites:
- A Vonage API account (Sign up free).
- Node.js and npm (or yarn) installed (Download Node.js).
- A Vonage virtual phone number capable of sending/receiving SMS.
ngrok
installed and authenticated (Download ngrok).- Basic understanding of JavaScript, Node.js, and REST APIs.
- (Optional) Vonage CLI installed globally:
npm install -g @vonage/cli
.
1. Setting up the Project
This section details setting up the Node.js project structure, installing dependencies, and configuring the environment.
1. Create Project Directory:
Open your terminal and create a new directory for your project, then navigate into it.
mkdir vonage-sms-app
cd vonage-sms-app
2. Initialize Node.js Project:
Initialize the project using npm (or yarn). This creates a package.json
file.
npm init -y
3. Install Dependencies:
Install the necessary libraries: Express for the web server, the Vonage Server SDK to interact with the API, and dotenv
for managing environment variables.
npm install express @vonage/server-sdk dotenv
4. Create Project Files:
Create the main files for our application logic and environment configuration.
touch index.js server.js .env .gitignore
index.js
: Will contain the code for sending SMS messages.server.js
: Will contain the Express server code for receiving SMS messages and status updates via webhooks..env
: Will store sensitive credentials and configuration variables..gitignore
: Will specify files and directories that Git should ignore.
5. Configure .gitignore
:
Open .gitignore
and add the following lines to prevent committing sensitive information and unnecessary files:
node_modules/
.env
private.key
*.log
6. Set up Environment Variables (.env
):
Open the .env
file and add the following placeholders. We will populate these values later.
# Vonage API Credentials (Find in Vonage Dashboard -> API Settings)
VONAGE_API_KEY=YOUR_API_KEY
VONAGE_API_SECRET=YOUR_API_SECRET
# Vonage Application Credentials (Generated when creating a Vonage Application)
VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID
VONAGE_PRIVATE_KEY_PATH=./private.key
# Vonage Number (Purchase in Vonage Dashboard -> Numbers)
VONAGE_NUMBER=YOUR_VONAGE_NUMBER
# Recipient Number (For testing sending SMS)
RECIPIENT_NUMBER=RECIPIENT_PHONE_NUMBER_WITH_COUNTRY_CODE
# Server Port
PORT=3000
Explanation of Configuration:
- Storing credentials (
API Key
,API Secret
,Application ID
,Private Key Path
) in environment variables (.env
file locally, system environment variables in production) is crucial for security. It avoids hardcoding sensitive data directly into the source code. - The
dotenv
library enables loading these variables intoprocess.env
automatically during development. VONAGE_NUMBER
is the virtual number purchased from Vonage that will send and receive messages.RECIPIENT_NUMBER
is the target phone number for sending test messages (use your own mobile number). Ensure it includes the country code (e.g.,14155552671
).PORT
defines the port our Express server will listen on.
2. Implementing Core Functionality - Sending SMS
Let's implement the logic to send an SMS message using the Vonage Node.js SDK.
1. Edit index.js
:
Open index.js
and add the following code:
// index.js
require('dotenv').config(); // Load environment variables from .env file
const { Vonage } = require('@vonage/server-sdk');
const { MESSAGES_SANDBOX_URL } = require('@vonage/server-sdk'); // Use sandbox for testing if needed
// --- Configuration ---
const vonageApiKey = process.env.VONAGE_API_KEY;
const vonageApiSecret = process.env.VONAGE_API_SECRET;
const vonageAppId = process.env.VONAGE_APPLICATION_ID;
const privateKeyPath = process.env.VONAGE_PRIVATE_KEY_PATH;
const vonageNumber = process.env.VONAGE_NUMBER;
const recipientNumber = process.env.RECIPIENT_NUMBER;
// --- Input Validation (Basic Example) ---
if (!vonageAppId || !privateKeyPath || !vonageNumber || !recipientNumber) {
console.error('Error: Missing required environment variables for sending SMS.');
console.error('Please check your .env file and ensure VONAGE_APPLICATION_ID, VONAGE_PRIVATE_KEY_PATH, VONAGE_NUMBER, and RECIPIENT_NUMBER are set.');
process.exit(1); // Exit if configuration is missing
}
// --- Initialize Vonage Client ---
// The Messages API primarily uses Application ID and Private Key for authentication
const vonage = new Vonage({
applicationId: vonageAppId,
privateKey: privateKeyPath,
// Optional: Use the sandbox endpoint for testing without sending real SMS
// apiHost: MESSAGES_SANDBOX_URL
}, { debug: false }); // Set debug: true for detailed SDK logging
// --- Define Send Function ---
async function sendSms(to, from, text) {
console.log(`Attempting to send SMS from ${from} to ${to}: ""${text}""`);
try {
const resp = await vonage.messages.send({
message_type: ""text"",
text: text,
to: to,
from: from,
channel: ""sms""
});
console.log('SMS submitted successfully!');
console.log('Message UUID:', resp.message_uuid);
return resp; // Return the response object
} catch (err) {
console.error('Error sending SMS:');
// Log specific Vonage error details if available
if (err.response && err.response.data) {
console.error('Vonage Error:', JSON.stringify(err.response.data, null, 2));
} else {
console.error(err);
}
// Re-throw the error or handle it as needed
throw err;
}
}
// --- Execute Send Function ---
// Ensure this part only runs when the script is executed directly
if (require.main === module) {
const messageText = `Hello from Vonage via Node.js! Sent on ${new Date().toLocaleTimeString()}`;
sendSms(recipientNumber, vonageNumber, messageText)
.then(() => console.log('sendSms function completed.'))
.catch(() => console.error('sendSms function failed.'));
}
// Export the function if you want to use it as a module elsewhere
module.exports = { sendSms };
Explanation:
require('dotenv').config();
: Loads variables from the.env
file. Crucial to do this first.- SDK Initialization: We create a
Vonage
client instance. For the Messages API, authentication primarily relies on theapplicationId
andprivateKey
(obtained in the Vonage Application setup step later). API Key/Secret might be used for other Vonage APIs or account management tasks. vonage.messages.send({...})
: This is the core function call.message_type: ""text""
: Specifies a standard text message.text
: The content of the SMS.to
: The recipient's phone number (from.env
).from
: Your Vonage virtual number (from.env
).channel: ""sms""
: Explicitly uses the SMS channel of the Messages API.
- Asynchronous Handling: The
send
method returns a Promise. We useasync/await
for cleaner handling and include atry...catch
block for error management. - Error Logging: The
catch
block logs errors, including specific Vonage API error details if available inerr.response.data
. - Execution: The
if (require.main === module)
block ensures thesendSms
function is called only whenindex.js
is run directly (e.g.,node index.js
), not when imported as a module. - Export: We export
sendSms
so it could potentially be reused in other parts of a larger application (e.g., triggered by an API call inserver.js
). - Note: This guide separates sending logic (
index.js
) from receiving logic (server.js
) for clarity. In a more complex application, you might integrate the sending functionality into API routes within the mainserver.js
Express application.
Alternative Approaches:
- Callbacks: The SDK methods also support traditional Node.js callbacks if you prefer that style over Promises/
async/await
. - SMS API vs Messages API: Vonage has an older SMS API. The Messages API is generally preferred for new development due to its multi-channel support and richer features, even if only using SMS initially. Ensure your Vonage account settings use the Messages API as default (covered later).
3. Building the API Layer - Receiving SMS and Status Updates
Now, let's set up the Express server to handle incoming webhooks from Vonage.
1. Edit server.js
:
Open server.js
and add the following code:
// server.js
require('dotenv').config(); // Load environment variables
const express = require('express');
const { json, urlencoded } = express; // Use built-in parsers
const app = express();
const port = process.env.PORT || 3000; // Use port from .env or default to 3000
// --- Middleware ---
// Parse JSON request bodies
app.use(json());
// Parse URL-encoded request bodies (needed for some webhook formats)
app.use(urlencoded({ extended: true }));
// --- Basic Logging Middleware ---
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
// Log body for POST requests (be cautious with sensitive data in production logs)
if (req.method === 'POST' && req.body) {
console.log('Request Body:', JSON.stringify(req.body, null, 2));
}
next(); // Pass control to the next middleware/route handler
});
// --- Webhook Endpoints ---
// 1. Inbound SMS Webhook
// Vonage sends incoming SMS messages to this endpoint
app.post('/webhooks/inbound', (req, res) => {
console.log('--- Inbound SMS Received ---');
const { msisdn, to, text, messageId, 'message-timestamp': timestamp } = req.body;
if (!msisdn || !to || !text) {
console.error('Invalid inbound message data received:', req.body);
// Respond with error, but still 200 OK for Vonage not to retry unnecessarily for format errors
return res.status(200).send('Invalid data format');
}
console.log(`From: ${msisdn}`); // Sender's number
console.log(`To: ${to}`); // Your Vonage number
console.log(`Text: ${text}`);
console.log(`Message ID: ${messageId}`);
console.log(`Timestamp: ${timestamp}`);
// --- TODO: Add your business logic here ---
// - Store the message in a database
// - Check for keywords (e.g., STOP, HELP)
// - Trigger replies or other actions
// --- Acknowledge Receipt to Vonage ---
// Vonage requires a 200 OK response to confirm receipt.
// Failure to respond or non-200 status will cause Vonage to retry.
res.status(200).end();
});
// 2. Message Status (Delivery Receipt - DLR) Webhook
// Vonage sends status updates about outbound messages to this endpoint
app.post('/webhooks/status', (req, res) => {
console.log('--- Message Status Received (DLR) ---');
const { message_uuid, status, timestamp, to, from, error } = req.body;
console.log(`Message UUID: ${message_uuid}`);
console.log(`Status: ${status}`); // e.g., delivered, failed, accepted, rejected
console.log(`Timestamp: ${timestamp}`);
console.log(`To: ${to}`);
console.log(`From: ${from}`);
if (error) {
console.error(`Error Code: ${error.code}`);
console.error(`Error Reason: ${error.reason}`);
}
// --- TODO: Add your business logic here ---
// - Update message status in your database using message_uuid
// - Analyze delivery rates
// - Trigger alerts for failed messages
// --- Acknowledge Receipt to Vonage ---
res.status(200).end();
});
// --- Health Check Endpoint ---
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
// --- Start Server ---
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
console.log(`Webhook endpoints expected at:`);
console.log(` Inbound SMS: POST /webhooks/inbound`);
console.log(` Status (DLR): POST /webhooks/status`);
console.log(`Make sure ngrok forwards to this port.`);
});
// Export app for potential testing
module.exports = app;
Explanation:
- Middleware:
express.json()
: Parses incoming requests with JSON payloads (common for modern webhooks).express.urlencoded()
: Parses incoming requests with URL-encoded payloads (sometimes used by older systems or form submissions).extended: true
allows for rich objects and arrays.- Logging Middleware: A simple custom middleware logs every incoming request's method and URL. It also logs the body of POST requests (useful for debugging webhooks). Caution: In production, be mindful of logging sensitive data from request bodies.
/webhooks/inbound
:- Handles
POST
requests to this path. Vonage sends data about incoming SMS messages here. - Extracts key information from
req.body
(sendermsisdn
, recipientto
,text
, etc.). - Includes a placeholder
// TODO:
section where you would add logic to process the message (database storage, keyword checks, auto-replies). - Crucially, sends a
res.status(200).end()
response. Vonage requires this acknowledgment. Without it, Vonage assumes the webhook failed and will retry sending the message, leading to duplicate processing.
- Handles
/webhooks/status
:- Handles
POST
requests for Delivery Receipts (DLRs). Vonage sends updates on the status of messages you sent. - Extracts key information like the
message_uuid
(which matches the UUID returned when you sent the message),status
(delivered
,failed
,accepted
, etc.), and any error details. - Includes a
// TODO:
section for logic like updating your database records based on the delivery status. - Also sends
res.status(200).end()
to acknowledge receipt.
- Handles
/health
: A simple GET endpoint often used by monitoring systems or load balancers to check if the application is running.app.listen
: Starts the Express server on the configured port.
Testing Webhooks:
Since these endpoints need to be publicly accessible for Vonage to reach them, we'll use ngrok
during development. Testing involves:
- Running the Express server (
node server.js
). - Running
ngrok
to expose the local port (ngrok http 3000
). - Configuring the
ngrok
URL in the Vonage dashboard (next section). - Sending an SMS to your Vonage number (tests
/inbound
). - Sending an SMS from your application (
node index.js
) and observing the status update (tests/status
).
You can use tools like Postman or curl
to manually send simulated webhook data to your local server before setting up ngrok
, helping test the endpoint logic in isolation.
Example curl
command for /webhooks/inbound
:
curl -X POST http://localhost:3000/webhooks/inbound \
-H ""Content-Type: application/json"" \
-d '{
""msisdn"": ""14155551234"",
""to"": ""12125559876"",
""messageId"": ""abc-def-ghi-jkl"",
""text"": ""Hello from curl test!"",
""type"": ""text"",
""keyword"": ""HELLO"",
""message-timestamp"": ""2025-04-20T12:00:00Z""
}'
Example curl
command for /webhooks/status
:
curl -X POST http://localhost:3000/webhooks/status \
-H ""Content-Type: application/json"" \
-d '{
""message_uuid"": ""xyz-789-lmo-456"",
""status"": ""delivered"",
""timestamp"": ""2025-04-20T12:05:00Z"",
""to"": ""14155551234"",
""from"": ""12125559876"",
""usage"": { ""currency"": ""USD"", ""price"": ""0.0075""},
""client_ref"": ""my-campaign-123""
}'
# Example for a failed message
# curl -X POST http://localhost:3000/webhooks/status \
# -H ""Content-Type: application/json"" \
# -d '{
# ""message_uuid"": ""xyz-789-lmo-457"",
# ""status"": ""failed"",
# ""timestamp"": ""2025-04-20T12:06:00Z"",
# ""to"": ""14155551111"",
# ""from"": ""12125559876"",
# ""error"": { ""code"": 4, ""reason"": ""Invalid Destination Address""}
# }'
4. Integrating with Vonage Service
This section covers configuring your Vonage account and application to work with the code we've written.
1. Obtain API Key and Secret:
- Log in to your Vonage API Dashboard.
- On the main dashboard page (""Getting started""), you'll find your API key and API secret.
- Copy these values and paste them into your
.env
file forVONAGE_API_KEY
andVONAGE_API_SECRET
.
2. Set Default SMS API to ""Messages API"":
- Navigate to Settings in the left-hand menu.
- Scroll down to the API settings section.
- Under Default SMS Setting, ensure Messages API is selected. If not, select it and click Save changes. This ensures consistency between the API used for sending and the format of webhooks received.
3. Purchase a Vonage Number:
- Navigate to Numbers -> Buy numbers.
- Search for numbers based on country, features (SMS, Voice), and type (Mobile, Landline, Toll-free).
- Choose a number that supports SMS and purchase it.
- Note the full number (including country code). Paste this into your
.env
file forVONAGE_NUMBER
.
4. Create a Vonage Application:
This application links your code (via API credentials) and your Vonage number to the webhook URLs.
- Navigate to Your applications -> Create a new application.
- Give your application a descriptive Name (e.g., ""NodeJS SMS Campaign App"").
- Click Generate public and private key. This will automatically download a file named
private.key
.- Security: Save this
private.key
file in the root directory of your Node.js project (e.g., alongsidepackage.json
). Ensure it's listed in your.gitignore
file. - Update the
VONAGE_PRIVATE_KEY_PATH
in your.env
file to./private.key
.
- Security: Save this
- Note the Application ID displayed on the page. Copy this value and paste it into your
.env
file forVONAGE_APPLICATION_ID
. - Enable the Messages capability by toggling the switch next to it. This reveals fields for Inbound URL and Status URL.
- Configure Webhook URLs (Using ngrok):
- Open a new terminal window.
- Navigate to your project directory (
cd vonage-sms-app
). - Start your Express server:
node server.js
. - Open another new terminal window.
- Start
ngrok
to expose port 3000 (or the port your server is running on):ngrok http 3000
ngrok
will display forwarding URLs. Copy the HTTPS forwarding URL (e.g.,https://random-string.ngrok-free.app
).- Go back to the Vonage Application configuration page.
- Paste the HTTPS
ngrok
URL into the Inbound URL field and append/webhooks/inbound
. (e.g.,https://random-string.ngrok-free.app/webhooks/inbound
). - Paste the HTTPS
ngrok
URL into the Status URL field and append/webhooks/status
. (e.g.,https://random-string.ngrok-free.app/webhooks/status
).
- Scroll down to the Link virtual numbers section.
- Find the Vonage number you purchased earlier and click the Link button next to it.
- Finally, click Save changes at the bottom of the page.
Summary of .env
values and where to find them:
Variable | Purpose | How to Obtain |
---|---|---|
VONAGE_API_KEY | Account-level authentication (legacy/other APIs) | Vonage Dashboard -> Getting started |
VONAGE_API_SECRET | Account-level authentication (legacy/other APIs) | Vonage Dashboard -> Getting started |
VONAGE_APPLICATION_ID | Identifies your specific Vonage Application | Vonage Dashboard -> Your applications -> (Select your app) -> Application ID |
VONAGE_PRIVATE_KEY_PATH | Path to the private key for application auth | Set to ./private.key (after downloading during app creation) |
VONAGE_NUMBER | The Vonage virtual number to use | Vonage Dashboard -> Numbers -> Your numbers |
RECIPIENT_NUMBER | Test destination number | Your own mobile number (include country code) |
PORT | Local server port | Set to 3000 (or your preference, ensure ngrok matches) |
5. Implementing Error Handling, Logging, and Retry Mechanisms
Robust applications require solid error handling and logging.
Error Handling Strategy:
- SDK Errors (
index.js
): Wrapvonage.messages.send
calls intry...catch
blocks. Inspect the caughterr
object. Vonage SDK errors often containerr.response.data
with specific API error details (code, reason). Log these details. Decide whether to retry based on the error type (e.g., temporary network issues vs. invalid number). - Webhook Input Validation (
server.js
): Check for the presence of expected fields inreq.body
for/inbound
and/status
webhooks. If essential data is missing, log an error but still return200 OK
to Vonage – the issue is with the data payload, not the webhook receipt itself. Retrying won't fix malformed data. - Webhook Processing Errors (
server.js
): If an error occurs during your business logic (e.g., database connection fails while trying to save an incoming message), log the error comprehensively. You must still return200 OK
to Vonage. Implement your own retry mechanism or dead-letter queue for your internal processing if necessary. Vonage's retry is only for confirming receipt of the webhook. - Unexpected Server Errors (
server.js
): Implement a global Express error handler to catch unhandled exceptions and prevent stack traces from leaking.
Logging:
- Development:
console.log
andconsole.error
are sufficient. Setdebug: true
in the Vonage SDK options (new Vonage({...}, { debug: true })
) for verbose SDK logging. - Production: Use a dedicated logging library like Winston or Pino.
- Structured Logging: Log in JSON format for easier parsing by log aggregation tools (e.g., ELK Stack, Datadog, Splunk).
- Log Levels: Use appropriate levels (debug, info, warn, error, fatal). Configure the minimum log level based on the environment (e.g.,
info
in production,debug
in development). - Log Correlation: Include unique identifiers (like the
message_uuid
or a request ID) in related log entries to trace requests across services.
Example (Adding Winston to server.js
):
npm install winston
// server.js (Top section)
const winston = require('winston');
// --- Configure Winston Logger ---
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info', // Default to info level
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }), // Log stack traces
winston.format.json() // Log in JSON format
),
transports: [
// In production, you might write to files:
// new winston.transports.File({ filename: 'error.log', level: 'error' }),
// new winston.transports.File({ filename: 'combined.log' }),
// In development, log to the console
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(), // Add colors
winston.format.simple() // Simple format for console
)
})
],
exceptionHandlers: [ // Catch unhandled exceptions
// new winston.transports.File({ filename: 'exceptions.log' })
new winston.transports.Console({ // Also log exceptions to console
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
],
rejectionHandlers: [ // Catch unhandled promise rejections
// new winston.transports.File({ filename: 'rejections.log' })
new winston.transports.Console({ // Also log rejections to console
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
// --- Replace console.log/error with logger ---
// Example in /webhooks/inbound:
app.post('/webhooks/inbound', (req, res) => {
logger.info('--- Inbound SMS Received ---', { body: req.body }); // Include context
const { msisdn, to, text, messageId, 'message-timestamp': timestamp } = req.body; // Ensure variables are defined here
if (!msisdn || !to || !text) {
logger.error('Invalid inbound message data received', { body: req.body });
return res.status(200).send('Invalid data format');
}
logger.info('Processing inbound SMS', { from: msisdn, to: to, messageId: messageId });
// ... rest of the logic
res.status(200).end();
});
// Example in index.js sendSms function (assuming logger is exported or passed):
// (This requires refactoring index.js or sharing the logger instance)
// async function sendSms(to, from, text, logger) { // Pass logger instance
// logger.info(`Attempting to send SMS`, { to, from }); // Removed text from log for brevity/privacy
// try {
// const vonage = new Vonage(...) // Ensure vonage is initialized
// const resp = await vonage.messages.send({ /* ... params ... */ });
// logger.info('SMS submitted successfully', { message_uuid: resp.message_uuid });
// return resp;
// } catch (err) {
// logger.error('Error sending SMS', {
// error: err.message,
// stack: err.stack,
// vonage_response: err.response?.data // Safely access nested property
// });
// throw err;
// }
// }
Retry Mechanisms:
- Webhook Delivery (Vonage -> Your App): Vonage handles retries automatically if your
/webhooks/inbound
or/webhooks/status
endpoints do not return a200 OK
response within a reasonable timeframe (usually a few seconds). They use an exponential backoff strategy. This is why acknowledging receipt quickly withres.status(200).end()
is critical, even if your internal processing fails later. - SMS Sending (Your App -> Vonage): If sending an SMS fails due to potentially temporary issues (e.g., network timeout, Vonage API temporary unavailability - 5xx errors), you might implement application-level retries.
- Use libraries like
async-retry
orp-retry
. - Implement exponential backoff to avoid overwhelming the API.
- Only retry on specific error codes indicating transient failures. Do not retry on permanent errors like ""Invalid Credentials"" (401) or ""Invalid Number Format"" (often 400).
- Use libraries like
Example (Basic retry logic in index.js
- conceptual):
// index.js - Conceptual Retry (using pseudo-code logic)
// Assumes logger is available (e.g., passed as argument or imported)
const MAX_RETRIES = 3;
const INITIAL_DELAY_MS = 1000;
async function sendSmsWithRetry(to, from, text, logger) { // Pass logger
let retries = 0;
while (retries < MAX_RETRIES) {
try {
logger.info(`Attempt ${retries + 1} to send SMS`_ { to_ from });
// Assuming 'vonage' client is initialized and available in scope
const resp = await vonage.messages.send({
message_type: ""text""_
text: text_
to: to_
from: from_
channel: ""sms""
});
logger.info('SMS submitted successfully'_ { message_uuid: resp.message_uuid });
return resp; // Success! Exit loop.
} catch (err) {
logger.error(`Attempt ${retries + 1} failed`_ { error: err.message_ vonage_response: err.response?.data });
// Check if the error is retryable (e.g._ 5xx server errors_ network issues)
// This logic needs refinement based on specific Vonage error codes
const isRetryable = err.response?.status >= 500 || ['ETIMEDOUT', 'ECONNRESET', 'EPIPE'].includes(err.code);
if (isRetryable && retries < MAX_RETRIES - 1) {
retries++;
const delay = INITIAL_DELAY_MS * Math.pow(2_ retries - 1); // Exponential backoff
logger.warn(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay)); // Wait
} else {
logger.error('Non-retryable error or max retries reached. Giving up.');
throw err; // Re-throw the error for upstream handling
}
}
}
}
// Call the retry function instead of the basic one
// Make sure to pass the logger instance
// sendSmsWithRetry(recipientNumber, vonageNumber, messageText, logger) ...
6. Creating a Database Schema and Data Layer (Conceptual)
While this guide focuses on the core Vonage integration, a real-world application requires persistent storage.
Why a Database?
- Store contact lists or campaign recipients.
- Log sent and received messages for history and auditing.
- Track the delivery status (
message_uuid
,status
) of outbound messages. - Manage opt-out lists (essential for compliance).
- Store campaign details and results.