Send SMS with Node.js and Vonage API: Complete Express Integration Guide
Learn how to build a production-ready Node.js SMS service using Express and Vonage API. Step-by-step tutorial with code examples, authentication, error handling, and deployment tips.
This guide provides a complete walkthrough for building a simple Node.js application using the Express framework to send SMS messages via the Vonage SMS API. You'll learn how to set up a project, integrate with Vonage, and deploy a production-ready SMS endpoint.
By the end of this tutorial, you'll have a functional Express API endpoint that accepts a recipient phone number and message, then uses the Vonage API to send SMS. This implementation works for backend services, internal tools, or applications where SMS notifications are triggered by server events.
Project Overview and Goals
Goal: Create a secure and reliable Node.js service that sends SMS messages programmatically using Vonage.
Problem Solved: This service provides a foundational building block for applications needing to send transactional SMS, notifications, alerts, or one-time passwords (OTPs).
Technologies Used:
Node.js: JavaScript runtime for building server-side applications with strong support for I/O-bound tasks like API interactions.
Express: Minimal Node.js web framework for setting up API endpoints and handling HTTP requests.
Vonage Server SDK for Node.js (@vonage/server-sdk): Official library for interacting with Vonage APIs.
dotenv: Module to load environment variables from a .env file, keeping sensitive credentials out of source code.
Enable ES Modules:
To use modern import syntax, open your package.json file and add "type": "module" at the top level:
JSON
{"name":"vonage-sms-sender","version":"1.0.0","description":"","main":"index.js","type":"module","scripts":{"start":"node index.js","test":"echo \"Error: no test specified\" && exit 1"},"keywords":[],"author":"","license":"ISC","dependencies":{"@vonage/server-sdk":"^3.12.1","dotenv":"^16.3.1","express":"^4.18.2"}}
Adding "type": "module" enables ES Module import/export syntax instead of CommonJS require.
Create Project Files:
Create the main application file and environment configuration file.
Bash
touch index.js .env .gitignore
Configure .gitignore:
Prevent sensitive information and unnecessary files from being committed to version control. Add these lines to your .gitignore file:
On the main dashboard page (or under "API settings"), find your API key and API secret.
Keep these values secure – they grant access to your Vonage account.
Configure Sender ID / Virtual Number:
Option A (Paid Accounts – Recommended for Production): Buy a Vonage virtual number. Navigate to "Numbers" > "Buy numbers" in the dashboard. Choose a number with SMS capabilities in your desired country. Using a dedicated number improves deliverability and enables two-way communication.
Option B (Free Trial / Testing): Use Test Numbers. Vonage free trial accounts can only send messages to phone numbers verified within the dashboard.
Enter the phone number you want to send test messages to (e.g., your personal mobile number) in E.164 format (e.g., +12015550123).
Vonage will send a verification code via SMS or voice call to that number. Enter the code to verify.
Important: While using a free trial, you must add recipient numbers here, otherwise you'll encounter a "Non-Whitelisted Destination" error when trying to send SMS.
Option C (Alphanumeric Sender ID): In some countries, you can use a custom text string (e.g., your brand name like "MyApp") as the sender ID instead of a phone number. Check Vonage documentation for country-specific regulations and support. This is often used for one-way notifications.
Set Environment Variables:
Open the .env file you created and add your Vonage credentials and sender/recipient information. Replace the placeholder values with your actual data.
.env
# .env
# Server Configuration
PORT=3000
# Vonage API Credentials
VONAGE_API_KEY=YOUR_API_KEY_HERE
VONAGE_API_SECRET=YOUR_API_SECRET_HERE
# Vonage Sender & Test Recipient
# Use either a purchased Vonage number, an approved Alphanumeric Sender ID,
# or keep it simple for testing (can often be text like 'VonageSMS')
VONAGE_SENDER_ID_OR_NUMBER=VonageTest
# The phone number added to your Vonage Test Numbers (E.164 format)
# Only needed if you want a default for quick testing in the code below.
# The API endpoint will accept the recipient dynamically.
TEST_RECIPIENT_NUMBER=+12015550123
PORT: The port your Express server will listen on.
VONAGE_API_KEY: Your API key from the dashboard.
VONAGE_API_SECRET: Your API secret from the dashboard.
VONAGE_SENDER_ID_OR_NUMBER: The "from" value for the SMS. Use your purchased number, approved alphanumeric ID, or a simple string like "VonageTest" (check deliverability for your region).
TEST_RECIPIENT_NUMBER: A placeholder for quick testing; the actual recipient will come from the API request. Make sure this number is verified in your Vonage dashboard's Test Numbers section if you're on a trial account.
3. Implementing the Express API Endpoint
Write the Node.js code to create the Express server and the /send endpoint.
JavaScript
// index.jsimport express from'express';
import dotenv from'dotenv';
import { Vonage } from'@vonage/server-sdk';
// Load environment variables from .env file
dotenv.config();
// --- Configuration ---constPORT = process.env.PORT || 3000;
constVONAGE_API_KEY = process.env.VONAGE_API_KEY;
constVONAGE_API_SECRET = process.env.VONAGE_API_SECRET;
constVONAGE_SENDER_ID = process.env.VONAGE_SENDER_ID_OR_NUMBER;
// Basic validation for environment variablesif (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_SENDER_ID) {
console.error(
'Error: Vonage API Key, Secret, or Sender ID is missing in .env file.'
);
console.error('Please check your environment variable configuration.');
process.exit(1); // Exit if critical configuration is missing
}
// --- Initialize Vonage Client ---const vonage = newVonage({
apiKey: VONAGE_API_KEY,
apiSecret: VONAGE_API_SECRET,
});
// --- Initialize Express App ---const app = express();
// --- Middleware ---// Enable parsing of JSON request bodies
app.use(express.json());
// Enable parsing of URL-encoded request bodies
app.use(express.urlencoded({ extended: true }));
// Simple logging middleware for incoming requests
app.use((req, res, next) => {
console.log(`[${newDate().toISOString()}] ${req.method}${req.url}`);
next(); // Pass control to the next middleware/route handler
});
// --- API Routes ---/**
* @route POST /send
* @description Sends an SMS message via Vonage.
* @access Public (consider adding auth in production)
* @requestBody { recipient: string, message: string }
* @response Success: { success: true, messageId: string }
* @response Error: { success: false, error: string }
*/
app.post('/send', async (req, res) => {
const { recipient, message } = req.body;
// --- Input Validation ---if (!recipient || !message) {
console.error('Validation Error: Missing recipient or message in request body');
return res.status(400).json({
success: false,
error: 'Bad Request: Both "recipient" and "message" are required in the request body.',
});
}
// Basic check for phone number format (improve as needed for production)// E.164 format is generally recommended (e.g., +12223334444)if (!/^\+?[1-9]\d{1,14}$/.test(recipient)) {
console.error(`Validation Error: Invalid recipient format: ${recipient}`);
return res.status(400).json({
success: false,
error: 'Bad Request: Invalid recipient phone number format. Use E.164 format (e.g., +12223334444).',
});
}
console.log(`Attempting to send SMS to ${recipient} from ${VONAGE_SENDER_ID}`);
// --- Send SMS using Vonage SDK ---try {
// Note: The modern Messages API (vonage.messages.send) is often preferred,// but vonage.sms.send is simpler for this basic use case.const responseData = await vonage.sms.send({
to: recipient,
from: VONAGE_SENDER_ID,
text: message,
});
// Check the status of the first message in the responseif (responseData.messages[0].status === '0') {
const messageId = responseData.messages[0]['message-id'];
console.log(`SMS sent successfully to ${recipient}. Message ID: ${messageId}`);
res.status(200).json({
success: true,
messageId: messageId,
});
} else {
const errorCode = responseData.messages[0].status;
const errorText = responseData.messages[0]['error-text'];
console.error(`SMS failed to send to ${recipient}. Status: ${errorCode} – Error: ${errorText}`);
// Determine appropriate status code based on Vonage error// For simplicity, using 500, but could map specific errors (e.g., 4xx for bad number format if not caught earlier)
res.status(500).json({
success: false,
error: `Failed to send SMS: ${errorText} (Code: ${errorCode})`,
});
}
} catch (error) {
console.error('Error sending SMS via Vonage:', error);
// Handle potential SDK errors or network issues
res.status(500).json({
success: false,
error: 'Internal Server Error: Could not process the SMS request.',
details: error.message, // Include details cautiously in dev, maybe omit in prod
});
}
});
// --- Health Check Endpoint ---
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: newDate().toISOString() });
});
// --- Error Handling Middleware (Basic) ---// Catches errors from synchronous code or errors passed via next(err)
app.use((err, req, res, next) => {
console.error('Unhandled Error:', err);
res.status(500).json({
success: false,
error: 'Internal Server Error',
message: err.message, // Be cautious exposing error messages in production
});
});
// --- Start Server ---
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
console.log(`API endpoint available at POST /send`);
console.log(`Using Vonage Sender ID/Number: ${VONAGE_SENDER_ID}`);
// Log only if TEST_RECIPIENT_NUMBER is set, useful for quick dev testingif (process.env.TEST_RECIPIENT_NUMBER) {
console.log(`Test recipient number set in .env: ${process.env.TEST_RECIPIENT_NUMBER}`);
}
});
Code Explanation:
Imports: Imports express, dotenv, and the Vonage SDK.
Vonage Initialization: Creates a vonage client instance using the API key and secret.
Express Setup: Initializes the Express application.
Middleware:
express.json() and express.urlencoded() parse incoming request bodies.
A custom middleware logs basic request information (method and URL).
/send Endpoint (POST):
Defined as an async function to use await for the Vonage API call.
Extracts recipient and message from req.body.
Input Validation: Checks if recipient and message are present. Returns a 400 Bad Request error if not. Includes a basic regex check for E.164 format.
Vonage Call: Uses vonage.sms.send() within a try...catch block.
Success: If responseData.messages[0].status is '0', the message was accepted by Vonage. Logs success and returns a 200 OK response with the message-id.
Vonage Error: If the status is not '0', logs the error details provided by Vonage and returns a 500 Internal Server Error (could be refined) with the Vonage error message.
SDK/Network Error: The catch block handles exceptions during the API call (e.g., network issues, invalid credentials leading to SDK errors) and returns a generic 500 error.
/health Endpoint (GET): A simple endpoint useful for monitoring to check if the service is running.
Error Handling Middleware: A basic catch-all error handler for unhandled errors.
Server Start: Starts the Express server, listening on the specified PORT.
4. Running and Testing the Application
Start the Server:
Open your terminal in the project directory and run:
Bash
npm start
You should see output indicating the server is running:
Code
Server is running on http://localhost:3000
API endpoint available at POST /send
Using Vonage Sender ID/Number: VonageTest
Test recipient number set in .env: +12015550123
Test with curl:
Open a new terminal window and use curl to send a POST request to your /send endpoint. Replace +1YOUR_TEST_NUMBER with the actual phone number you verified in the Vonage dashboard's Test Numbers section.
Bash
curl -X POST http://localhost:3000/send \
-H "Content-Type: application/json" \
-d '{
"recipient": "+1YOUR_TEST_NUMBER",
"message": "Hello from your Node.js Vonage App!"
}'
Test with Postman/Insomnia:
Create a new request.
Set the method to POST.
Set the URL to http://localhost:3000/send.
Go to the "Body" tab, select "raw", and choose "JSON" from the dropdown.
Enter the JSON payload:
JSON
{"recipient":"+1YOUR_TEST_NUMBER","message":"Hello from Postman/Insomnia via Node.js Vonage!"}
You should also receive the SMS on the recipient phone shortly after.
Validation Error (Missing Field):
JSON
// Status: 400 Bad Request{"success":false,"error":"Bad Request: Both \"recipient\" and \"message\" are required in the request body."}
Vonage Error (e.g., Non-Whitelisted Number on Trial):
JSON
// Status: 500 Internal Server Error (or potentially mapped 4xx){"success":false,"error":"Failed to send SMS: Non White-listed Destination – rejected (Code: 15)"// Example error}
Server Error (e.g., Invalid API Key):
JSON
// Status: 500 Internal Server Error{"success":false,"error":"Internal Server Error: Could not process the SMS request.","details":"Authentication failed"// Example SDK error message}
5. Error Handling and Logging
The current implementation includes basic error handling:
Input Validation: Checks for required fields (recipient, message) and basic format.
Vonage API Errors: Checks the status code in the Vonage response and logs/returns the error-text.
SDK/Network Errors: Uses a try...catch block around the vonage.sms.send call.
Basic Logging: Uses console.log and console.error.
Production Enhancements:
Structured Logging: Implement a dedicated logging library like winston or pino. This enables structured JSON logs, different log levels (debug, info, warn, error), and easier integration with log management systems (e.g., Datadog, Splunk, ELK stack).
Detailed Error Mapping: Map specific Vonage error codes (from responseData.messages[0].status) to appropriate HTTP status codes (4xx for client errors like invalid number, 5xx for server-side issues) and potentially retry logic.
Retry Mechanisms: For transient errors (e.g., network timeouts, temporary Vonage issues), implement a retry strategy with exponential backoff using libraries like async-retry. Be cautious not to retry errors indicating permanent failure (like invalid number).
Centralized Error Handling: Refine the final error handling middleware to provide consistent error responses without leaking sensitive stack traces in production.
6. Security Considerations
API Key Security:
NEVER commit API keys or secrets directly into source code.
Use environment variables (.env locally, platform-specific configuration in deployment).
Ensure .env is in your .gitignore.
Consider using secrets management solutions (like HashiCorp Vault, AWS Secrets Manager, Google Secret Manager) for production environments.
Input Validation & Sanitization:
The current validation is basic. Robustly validate phone numbers (e.g., using libraries like google-libphonenumber).
While Vonage handles SMS content, if you were displaying the message content elsewhere in your app, you would need to sanitize it (e.g., using express-validator or libraries like DOMPurify if rendering as HTML) to prevent Cross-Site Scripting (XSS). For sending via Vonage, validation of length and format is more relevant.
Rate Limiting: Protect your API endpoint from abuse. Implement rate limiting based on IP address or API keys (if you add authentication) using middleware like express-rate-limit.
Authentication/Authorization: The current endpoint is public. In a real application, you would protect it:
API Keys: Issue keys to legitimate clients and require them in headers (e.g., X-API-Key). Validate the key on the server.
JWT/OAuth: For user-driven actions, use standard authentication mechanisms.
HTTPS: Always run your application behind HTTPS in production (usually handled by load balancers or reverse proxies like Nginx).
7. Troubleshooting and Caveats
Non White-listed Destination - rejected (Error Code 15): This is the most common issue on free trial accounts. Solution: Ensure the recipient phone number has been added and verified under "Account" > "Test Numbers" in your Vonage Dashboard.
Authentication failed / 401 Unauthorized: Double-check that VONAGE_API_KEY and VONAGE_API_SECRET in your .env file are correct and that the .env file is being loaded properly (dotenv.config() is called before initializing Vonage). Ensure no extra spaces or characters were copied.
Invalid Sender ID: If using an alphanumeric sender ID, ensure it's registered and approved for the destination country. If using a number, ensure it's a valid Vonage number with SMS capabilities. Some networks might override alphanumeric IDs.
Incorrect Phone Number Format: Vonage generally expects E.164 format (+ followed by country code and number, e.g., +447700900000, +12125551234). Ensure the recipient number follows this format.
SDK Version Issues: Occasionally, breaking changes occur in SDKs. If you encounter unexpected errors after updating, check the SDK's changelog. Potential TypeScript issues might arise in specific version combinations, though less likely with this pure JavaScript setup.
Deliverability Issues: SMS delivery can be affected by carrier filtering, country regulations, and content. Check Vonage's documentation for best practices and country-specific guidelines.
Firewall/Network Issues: Ensure your server can make outbound HTTPS requests to Vonage's API endpoints (rest.nexmo.com, api.nexmo.com).
8. Deployment and CI/CD
Platform Choice: Deploy your Node.js application to platforms like Heroku, Vercel, AWS (EC2, Elastic Beanstalk, Fargate), Google Cloud (App Engine, Cloud Run), Azure App Service, or DigitalOcean App Platform.
Environment Variables: Configure your VONAGE_API_KEY, VONAGE_API_SECRET, and VONAGE_SENDER_ID_OR_NUMBER securely within your chosen hosting platform's environment variable settings. Do not deploy your .env file.
NODE_ENV=production: Set the NODE_ENV environment variable to production. This often enables performance optimizations in Express and other libraries.
Process Manager: Use a process manager like pm2 or rely on the platform's built-in mechanisms (e.g., Heroku Dynos, systemd) to keep your application running and handle restarts.
CI/CD: Set up a pipeline (e.g., using GitHub Actions, GitLab CI, Jenkins, CircleCI) to:
Lint and test your code automatically.
Build your application (if necessary, though often not required for simple Node.js apps).
Deploy automatically to staging/production environments upon successful builds/merges.
Database/State: This simple service is stateless. If you needed to store message logs, track status updates via webhooks, or manage user data, you would add a database (e.g., PostgreSQL, MongoDB, Redis).
9. Verification and Final Checks
Before considering the implementation complete:
Environment Setup: Can you successfully run npm install and npm start?
Configuration: Are VONAGE_API_KEY, VONAGE_API_SECRET, and VONAGE_SENDER_ID_OR_NUMBER correctly set in .env (or deployment environment)?
Test Number Verification: If using a trial account, is the recipient number verified in the Vonage Dashboard?
Health Check: Does GET /health return a 200 OK response with { "status": "UP", ... }?
Successful SMS: Can you successfully send an SMS using curl or Postman to a valid (and potentially whitelisted) recipient?
Does the API return a 200 OK with { "success": true, "messageId": "..." }?
Do you receive the SMS on the recipient's phone?
Validation Error Handling: Does sending a request without recipient or message return a 400 Bad Request with an appropriate error message?
Vonage Error Simulation (if possible): Can you trigger a known Vonage error (e.g., sending to a non-whitelisted number on trial) and verify the API returns a non-200 status (e.g., 500) with the correct error message from Vonage?
Security: Is .env included in .gitignore? (Crucial!)
Logging: Do requests and errors appear in the console logs during testing?
Final Thoughts
You have successfully built a basic but functional Node.js service to send SMS messages using Express and the Vonage API. This provides a solid foundation for integrating SMS capabilities into various applications. Remember to enhance security, error handling, and logging for production environments.
Consider exploring Vonage's other features like receiving SMS (using webhooks), number insight, voice calls, or two-factor authentication (Verify API) to further enhance your communication workflows.
Frequently asked questions
Set up a Node.js project with Express and the Vonage Server SDK. Create an API endpoint that accepts recipient and message details, then uses the SDK to send the SMS via the Vonage API. Ensure you have Vonage API credentials and a sender ID configured.
The Vonage SMS API enables sending text messages programmatically from your applications. It's used for transactional messages, notifications, alerts, two-factor authentication, and more by integrating with the API via their SDK.
Express simplifies creating API endpoints and handling HTTP requests, which streamlines the integration with the Vonage SMS API in a Node.js application. It provides a structured way to build your SMS service.
Obtain your API key and secret from the Vonage API Dashboard. Store them securely, preferably in environment variables (.env file locally), to avoid exposing them in your code.
A dedicated virtual number is recommended, especially for production, as it enhances deliverability and enables two-way communication. Obtain one from your Vonage Dashboard under 'Numbers'.
Yes, in certain countries, you can use a text string (like your brand name) as the sender ID. Consult Vonage's documentation for regional regulations and availability as this is country-specific.
This usually occurs with free trial accounts. Verify that you've added and confirmed the recipient phone number in the 'Test Numbers' section of your Vonage Dashboard. This is required for trial usage.
The dotenv module loads environment variables from a .env file into process.env. This helps manage credentials and configuration securely without directly embedding them in your code.
Check several potential issues: incorrect API credentials, unverified recipient number (for trial accounts), invalid sender ID, incorrect phone number format, or network connectivity issues.
It's best to implement rate limiting in production to prevent abuse and control costs. Middleware like express-rate-limit can help enforce limits based on IP address or API key.
Implement structured logging (Winston or Pino), map Vonage error codes to HTTP status codes, add retry mechanisms for transient errors, and centralize error handling middleware.
Secure API keys using environment variables or dedicated secrets management. Validate and sanitize input data, implement rate limiting, add authentication/authorization, and ensure HTTPS in production.
Deploy to platforms like Heroku, AWS, or Google Cloud, configuring environment variables securely. Use process managers and set NODE_ENV=production. Set up CI/CD for automated deployment and testing.
Use tools like curl or Postman to send POST requests to your /send endpoint with recipient and message in JSON format. Check the responses for success or specific error messages and ensure you receive the SMS.
This guide provides a complete walkthrough for building a simple Node.js application using the Express framework to send SMS messages via the Vonage SMS API. You'll learn how to set up a project, integrate with Vonage, and deploy a production-ready SMS endpoint.
By the end of this tutorial, you'll have a functional Express API endpoint that accepts a recipient phone number and message, then uses the Vonage API to send SMS. This implementation works for backend services, internal tools, or applications where SMS notifications are triggered by server events.
Project Overview and Goals
Goal: Create a secure and reliable Node.js service that sends SMS messages programmatically using Vonage.
Problem Solved: This service provides a foundational building block for applications needing to send transactional SMS, notifications, alerts, or one-time passwords (OTPs).
Technologies Used:
@vonage/server-sdk): Official library for interacting with Vonage APIs.dotenv: Module to load environment variables from a.envfile, keeping sensitive credentials out of source code.System Architecture:
Prerequisites:
node -vandnpm -v.curl, Postman, Insomnia).1. Setting up the project
Initialize your Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
mkdir vonage-sms-sender cd vonage-sms-senderInitialize npm: Initialize the project using npm. The
-yflag accepts the default settings.This creates a
package.jsonfile.Install Dependencies: Install Express, the Vonage Server SDK, and
dotenv.express: The web framework.@vonage/server-sdk: The official Vonage SDK.dotenv: For managing environment variables.Enable ES Modules: To use modern
importsyntax, open yourpackage.jsonfile and add"type": "module"at the top level:{ "name": "vonage-sms-sender", "version": "1.0.0", "description": "", "main": "index.js", "type": "module", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@vonage/server-sdk": "^3.12.1", "dotenv": "^16.3.1", "express": "^4.18.2" } }Adding
"type": "module"enables ES Moduleimport/exportsyntax instead of CommonJSrequire.Create Project Files: Create the main application file and environment configuration file.
touch index.js .env .gitignoreConfigure
.gitignore: Prevent sensitive information and unnecessary files from being committed to version control. Add these lines to your.gitignorefile:2. Integrating with Vonage
Retrieve your Vonage API credentials and configure your sending number or test recipient.
Get Vonage API Credentials:
Configure Sender ID / Virtual Number:
+12015550123).Set Environment Variables: Open the
.envfile you created and add your Vonage credentials and sender/recipient information. Replace the placeholder values with your actual data.PORT: The port your Express server will listen on.VONAGE_API_KEY: Your API key from the dashboard.VONAGE_API_SECRET: Your API secret from the dashboard.VONAGE_SENDER_ID_OR_NUMBER: The "from" value for the SMS. Use your purchased number, approved alphanumeric ID, or a simple string like "VonageTest" (check deliverability for your region).TEST_RECIPIENT_NUMBER: A placeholder for quick testing; the actual recipient will come from the API request. Make sure this number is verified in your Vonage dashboard's Test Numbers section if you're on a trial account.3. Implementing the Express API Endpoint
Write the Node.js code to create the Express server and the
/sendendpoint.// index.js import express from 'express'; import dotenv from 'dotenv'; import { Vonage } from '@vonage/server-sdk'; // Load environment variables from .env file dotenv.config(); // --- Configuration --- const PORT = process.env.PORT || 3000; const VONAGE_API_KEY = process.env.VONAGE_API_KEY; const VONAGE_API_SECRET = process.env.VONAGE_API_SECRET; const VONAGE_SENDER_ID = process.env.VONAGE_SENDER_ID_OR_NUMBER; // Basic validation for environment variables if (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_SENDER_ID) { console.error( 'Error: Vonage API Key, Secret, or Sender ID is missing in .env file.' ); console.error('Please check your environment variable configuration.'); process.exit(1); // Exit if critical configuration is missing } // --- Initialize Vonage Client --- const vonage = new Vonage({ apiKey: VONAGE_API_KEY, apiSecret: VONAGE_API_SECRET, }); // --- Initialize Express App --- const app = express(); // --- Middleware --- // Enable parsing of JSON request bodies app.use(express.json()); // Enable parsing of URL-encoded request bodies app.use(express.urlencoded({ extended: true })); // Simple logging middleware for incoming requests app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); // Pass control to the next middleware/route handler }); // --- API Routes --- /** * @route POST /send * @description Sends an SMS message via Vonage. * @access Public (consider adding auth in production) * @requestBody { recipient: string, message: string } * @response Success: { success: true, messageId: string } * @response Error: { success: false, error: string } */ app.post('/send', async (req, res) => { const { recipient, message } = req.body; // --- Input Validation --- if (!recipient || !message) { console.error('Validation Error: Missing recipient or message in request body'); return res.status(400).json({ success: false, error: 'Bad Request: Both "recipient" and "message" are required in the request body.', }); } // Basic check for phone number format (improve as needed for production) // E.164 format is generally recommended (e.g., +12223334444) if (!/^\+?[1-9]\d{1,14}$/.test(recipient)) { console.error(`Validation Error: Invalid recipient format: ${recipient}`); return res.status(400).json({ success: false, error: 'Bad Request: Invalid recipient phone number format. Use E.164 format (e.g., +12223334444).', }); } console.log(`Attempting to send SMS to ${recipient} from ${VONAGE_SENDER_ID}`); // --- Send SMS using Vonage SDK --- try { // Note: The modern Messages API (vonage.messages.send) is often preferred, // but vonage.sms.send is simpler for this basic use case. const responseData = await vonage.sms.send({ to: recipient, from: VONAGE_SENDER_ID, text: message, }); // Check the status of the first message in the response if (responseData.messages[0].status === '0') { const messageId = responseData.messages[0]['message-id']; console.log(`SMS sent successfully to ${recipient}. Message ID: ${messageId}`); res.status(200).json({ success: true, messageId: messageId, }); } else { const errorCode = responseData.messages[0].status; const errorText = responseData.messages[0]['error-text']; console.error(`SMS failed to send to ${recipient}. Status: ${errorCode} – Error: ${errorText}`); // Determine appropriate status code based on Vonage error // For simplicity, using 500, but could map specific errors (e.g., 4xx for bad number format if not caught earlier) res.status(500).json({ success: false, error: `Failed to send SMS: ${errorText} (Code: ${errorCode})`, }); } } catch (error) { console.error('Error sending SMS via Vonage:', error); // Handle potential SDK errors or network issues res.status(500).json({ success: false, error: 'Internal Server Error: Could not process the SMS request.', details: error.message, // Include details cautiously in dev, maybe omit in prod }); } }); // --- Health Check Endpoint --- app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // --- Error Handling Middleware (Basic) --- // Catches errors from synchronous code or errors passed via next(err) app.use((err, req, res, next) => { console.error('Unhandled Error:', err); res.status(500).json({ success: false, error: 'Internal Server Error', message: err.message, // Be cautious exposing error messages in production }); }); // --- Start Server --- app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); console.log(`API endpoint available at POST /send`); console.log(`Using Vonage Sender ID/Number: ${VONAGE_SENDER_ID}`); // Log only if TEST_RECIPIENT_NUMBER is set, useful for quick dev testing if (process.env.TEST_RECIPIENT_NUMBER) { console.log(`Test recipient number set in .env: ${process.env.TEST_RECIPIENT_NUMBER}`); } });Code Explanation:
express,dotenv, and theVonageSDK.dotenv.config()loads variables from.env. Basic checks ensure critical Vonage credentials exist.vonageclient instance using the API key and secret.express.json()andexpress.urlencoded()parse incoming request bodies./sendEndpoint (POST):asyncfunction to useawaitfor the Vonage API call.recipientandmessagefromreq.body.recipientandmessageare present. Returns a 400 Bad Request error if not. Includes a basic regex check for E.164 format.vonage.sms.send()within atry...catchblock.responseData.messages[0].statusis'0', the message was accepted by Vonage. Logs success and returns a 200 OK response with themessage-id.'0', logs the error details provided by Vonage and returns a 500 Internal Server Error (could be refined) with the Vonage error message.catchblock handles exceptions during the API call (e.g., network issues, invalid credentials leading to SDK errors) and returns a generic 500 error./healthEndpoint (GET): A simple endpoint useful for monitoring to check if the service is running.PORT.4. Running and Testing the Application
Start the Server: Open your terminal in the project directory and run:
You should see output indicating the server is running:
Test with
curl: Open a new terminal window and usecurlto send a POST request to your/sendendpoint. Replace+1YOUR_TEST_NUMBERwith the actual phone number you verified in the Vonage dashboard's Test Numbers section.curl -X POST http://localhost:3000/send \ -H "Content-Type: application/json" \ -d '{ "recipient": "+1YOUR_TEST_NUMBER", "message": "Hello from your Node.js Vonage App!" }'Test with Postman/Insomnia:
POST.http://localhost:3000/send.{ "recipient": "+1YOUR_TEST_NUMBER", "message": "Hello from Postman/Insomnia via Node.js Vonage!" }Expected Responses:
Success:
{ "success": true, "messageId": "some-unique-message-id-from-vonage" }You should also receive the SMS on the recipient phone shortly after.
Validation Error (Missing Field):
// Status: 400 Bad Request { "success": false, "error": "Bad Request: Both \"recipient\" and \"message\" are required in the request body." }Vonage Error (e.g., Non-Whitelisted Number on Trial):
// Status: 500 Internal Server Error (or potentially mapped 4xx) { "success": false, "error": "Failed to send SMS: Non White-listed Destination – rejected (Code: 15)" // Example error }Server Error (e.g., Invalid API Key):
// Status: 500 Internal Server Error { "success": false, "error": "Internal Server Error: Could not process the SMS request.", "details": "Authentication failed" // Example SDK error message }5. Error Handling and Logging
The current implementation includes basic error handling:
recipient,message) and basic format.statuscode in the Vonage response and logs/returns theerror-text.try...catchblock around thevonage.sms.sendcall.console.logandconsole.error.Production Enhancements:
winstonorpino. This enables structured JSON logs, different log levels (debug, info, warn, error), and easier integration with log management systems (e.g., Datadog, Splunk, ELK stack).responseData.messages[0].status) to appropriate HTTP status codes (4xx for client errors like invalid number, 5xx for server-side issues) and potentially retry logic.async-retry. Be cautious not to retry errors indicating permanent failure (like invalid number).6. Security Considerations
.envlocally, platform-specific configuration in deployment)..envis in your.gitignore.google-libphonenumber).messagecontent elsewhere in your app, you would need to sanitize it (e.g., usingexpress-validatoror libraries likeDOMPurifyif rendering as HTML) to prevent Cross-Site Scripting (XSS). For sending via Vonage, validation of length and format is more relevant.express-rate-limit.X-API-Key). Validate the key on the server.7. Troubleshooting and Caveats
Non White-listed Destination - rejected(Error Code 15): This is the most common issue on free trial accounts. Solution: Ensure the recipient phone number has been added and verified under "Account" > "Test Numbers" in your Vonage Dashboard.Authentication failed/ 401 Unauthorized: Double-check thatVONAGE_API_KEYandVONAGE_API_SECRETin your.envfile are correct and that the.envfile is being loaded properly (dotenv.config()is called before initializing Vonage). Ensure no extra spaces or characters were copied.+followed by country code and number, e.g.,+447700900000,+12125551234). Ensure therecipientnumber follows this format.rest.nexmo.com,api.nexmo.com).8. Deployment and CI/CD
VONAGE_API_KEY,VONAGE_API_SECRET, andVONAGE_SENDER_ID_OR_NUMBERsecurely within your chosen hosting platform's environment variable settings. Do not deploy your.envfile.NODE_ENV=production: Set theNODE_ENVenvironment variable toproduction. This often enables performance optimizations in Express and other libraries.pm2or rely on the platform's built-in mechanisms (e.g., Heroku Dynos, systemd) to keep your application running and handle restarts.9. Verification and Final Checks
Before considering the implementation complete:
npm installandnpm start?VONAGE_API_KEY,VONAGE_API_SECRET, andVONAGE_SENDER_ID_OR_NUMBERcorrectly set in.env(or deployment environment)?GET /healthreturn a 200 OK response with{ "status": "UP", ... }?curlor Postman to a valid (and potentially whitelisted) recipient?200 OKwith{ "success": true, "messageId": "..." }?recipientormessagereturn a400 Bad Requestwith an appropriate error message?.envincluded in.gitignore? (Crucial!)Final Thoughts
You have successfully built a basic but functional Node.js service to send SMS messages using Express and the Vonage API. This provides a solid foundation for integrating SMS capabilities into various applications. Remember to enhance security, error handling, and logging for production environments.
Consider exploring Vonage's other features like receiving SMS (using webhooks), number insight, voice calls, or two-factor authentication (Verify API) to further enhance your communication workflows.