code examples
code examples
Building SMS Interactions with Node.js, Express, and Vonage
A step-by-step guide to creating a Node.js application using Express and the Vonage Messages API for sending SMS, receiving messages, and handling delivery receipts via webhooks.
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.envfile 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:2pxPrerequisites:
- 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.
ngrokinstalled 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-app2. Initialize Node.js Project:
Initialize the project using npm (or yarn). This creates a package.json file.
npm init -y3. 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 dotenv4. Create Project Files:
Create the main files for our application logic and environment configuration.
touch index.js server.js .env .gitignoreindex.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
*.log6. 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=3000Explanation of Configuration:
- Storing credentials (
API Key,API Secret,Application ID,Private Key Path) in environment variables (.envfile locally, system environment variables in production) is crucial for security. It avoids hardcoding sensitive data directly into the source code. - The
dotenvlibrary enables loading these variables intoprocess.envautomatically during development. VONAGE_NUMBERis the virtual number purchased from Vonage that will send and receive messages.RECIPIENT_NUMBERis the target phone number for sending test messages (use your own mobile number). Ensure it includes the country code (e.g.,14155552671).PORTdefines 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.envfile. Crucial to do this first.- SDK Initialization: We create a
Vonageclient instance. For the Messages API, authentication primarily relies on theapplicationIdandprivateKey(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
sendmethod returns a Promise. We useasync/awaitfor cleaner handling and include atry...catchblock for error management. - Error Logging: The
catchblock logs errors, including specific Vonage API error details if available inerr.response.data. - Execution: The
if (require.main === module)block ensures thesendSmsfunction is called only whenindex.jsis run directly (e.g.,node index.js), not when imported as a module. - Export: We export
sendSmsso 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.jsExpress 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: trueallows 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
POSTrequests 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
POSTrequests 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
ngrokto expose the local port (ngrok http 3000). - Configuring the
ngrokURL 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
.envfile forVONAGE_API_KEYandVONAGE_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
.envfile 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.keyfile in the root directory of your Node.js project (e.g., alongsidepackage.json). Ensure it's listed in your.gitignorefile. - Update the
VONAGE_PRIVATE_KEY_PATHin your.envfile to./private.key.
- Security: Save this
- Note the Application ID displayed on the page. Copy this value and paste it into your
.envfile 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
ngrokto expose port 3000 (or the port your server is running on):bashngrok http 3000 ngrokwill 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
ngrokURL into the Inbound URL field and append/webhooks/inbound. (e.g.,https://random-string.ngrok-free.app/webhooks/inbound). - Paste the HTTPS
ngrokURL 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.sendcalls intry...catchblocks. Inspect the caughterrobject. Vonage SDK errors often containerr.response.datawith 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.bodyfor/inboundand/statuswebhooks. If essential data is missing, log an error but still return200 OKto 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 OKto 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.logandconsole.errorare sufficient. Setdebug: truein 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.,
infoin production,debugin development). - Log Correlation: Include unique identifiers (like the
message_uuidor 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/inboundor/webhooks/statusendpoints do not return a200 OKresponse 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-retryorp-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.
Frequently Asked Questions
How to send SMS messages with Node.js?
Use the Vonage Messages API with the Node.js Server SDK. After setting up a Vonage application and obtaining necessary credentials, the `vonage.messages.send()` function handles sending, specifying the recipient, sender, message type, content, and channel as 'sms'.
What is the Vonage Messages API used for?
The Vonage Messages API is a multi-channel communication platform enabling messaging across various channels like SMS, MMS, WhatsApp, and more. This tutorial focuses on its SMS capabilities for sending and receiving text messages.
Why use Express.js with Vonage Messages API?
Express.js simplifies building robust webhooks to receive incoming SMS messages and delivery receipts (DLRs) from Vonage. Its middleware handles request parsing and routing, making webhook management efficient.
When should I use the Vonage Messages API sandbox?
Use the sandbox URL (`MESSAGES_SANDBOX_URL`) during development and testing to avoid sending real SMS messages and incurring charges. Switch to the live API for production deployments.
Can I receive SMS messages to my Node.js app?
Yes, create a webhook endpoint (e.g., `/webhooks/inbound`) in your Express app. Configure this URL in your Vonage application settings. Vonage will forward incoming messages to this endpoint.
How to handle inbound SMS messages in Express?
In your `/webhooks/inbound` route handler, parse the incoming request body (`req.body`) which contains the sender's number (`msisdn`), message content (`text`), and other metadata. Always respond with `res.status(200).end()` to acknowledge receipt.
What are delivery receipts (DLRs) with Vonage?
DLRs provide status updates on the messages you've sent via Vonage. Your `/webhooks/status` endpoint receives these updates, indicating whether a message was 'delivered', 'failed', or is in another state.
How to set up ngrok with Vonage webhooks?
After starting your local Express server, run `ngrok http <your_port>` (e.g., `ngrok http 3000`). Use the generated HTTPS ngrok URL as the base for your webhook URLs in the Vonage application settings, appending the specific paths like `/webhooks/inbound`.
What is the @vonage/server-sdk?
The official Vonage Server SDK for Node.js facilitates interaction with Vonage APIs. It handles authentication, simplifies API calls, and provides helper functions for common tasks.
Why does Vonage require a 200 OK response for webhooks?
A 200 OK response confirms to Vonage that your application has received the webhook. Without it, Vonage assumes a failure and will retry sending the webhook, potentially causing duplicate processing.
How to secure Vonage API credentials in Node.js?
Store sensitive credentials (API key, secret, application ID, private key path) in environment variables (`.env` file locally) and avoid hardcoding them directly in your code. Load these variables using the `dotenv` library.
What is the Vonage application ID used for?
The Vonage application ID uniquely identifies your application within the Vonage platform and is used, along with the private key, for authenticating API requests, especially for the Messages API.
When to use API Key/Secret vs Application ID/Private Key?
The Messages API primarily uses the Application ID and Private Key for authentication. API Key and Secret might be used for other Vonage APIs or account management operations not related to sending/receiving messages.
How to test Vonage webhook endpoints locally?
Use `ngrok` to expose your local server or tools like Postman or `curl` to simulate webhook requests to your local endpoints (e.g., `http://localhost:3000/webhooks/inbound`) with appropriate test data.