code examples
code examples
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.
Send SMS with Node.js and Vonage API: Complete Express Integration Guide
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.envfile, keeping sensitive credentials out of source code.
System Architecture:
+-------------+ +-----------------------+ +-----------------+ +-------------+
| Client | ----> | Node.js/Express API | ----> | Vonage API | ----> | User's Phone|
| (e.g., curl,| | (Your Application) | | (SMS Gateway) | | (Recipient) |
| Postman) | | - POST /send endpoint | | | | |
+-------------+ | - Vonage SDK | | | | |
| - Reads .env | | | | |
+-----------------------+ +-----------------+ +-------------+Prerequisites:
- Node.js and npm (or yarn) installed. Check with
node -vandnpm -v. - A Vonage API account. Sign up here if you don't have one. You get free credit for testing.
- A text editor or IDE (e.g., VS Code).
- A tool for making HTTP requests (e.g.,
curl, Postman, Insomnia). - A test phone number to receive messages.
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.
bashmkdir vonage-sms-sender cd vonage-sms-sender -
Initialize npm: Initialize the project using npm. The
-yflag accepts the default settings.bashnpm init -yThis creates a
package.jsonfile. -
Install Dependencies: Install Express, the Vonage Server SDK, and
dotenv.bashnpm install express @vonage/server-sdk dotenv --saveexpress: 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: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 Moduleimport/exportsyntax instead of CommonJSrequire. -
Create Project Files: Create the main application file and environment configuration file.
bashtouch index.js .env .gitignore -
Configure
.gitignore: Prevent sensitive information and unnecessary files from being committed to version control. Add these lines to your.gitignorefile:text# .gitignore # Dependencies node_modules/ # Environment variables .env # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # Optional Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw?
2. Integrating with Vonage
Retrieve your Vonage API credentials and configure your sending number or test recipient.
-
Get Vonage API Credentials:
- Log in to your Vonage API Dashboard.
- 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.
- Go to your Vonage Dashboard.
- Navigate to "Account" > "Test Numbers".
- Click "Add test number".
- 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
.envfile you created and add your Vonage credentials and sender/recipient information. Replace the placeholder values with your actual data.dotenv# .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=+12015550123PORT: 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.
// 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:
- Imports: Imports
express,dotenv, and theVonageSDK. - Configuration Loading:
dotenv.config()loads variables from.env. Basic checks ensure critical Vonage credentials exist. - Vonage Initialization: Creates a
vonageclient instance using the API key and secret. - Express Setup: Initializes the Express application.
- Middleware:
express.json()andexpress.urlencoded()parse incoming request bodies.- A custom middleware logs basic request information (method and URL).
/sendEndpoint (POST):- Defined as an
asyncfunction to useawaitfor the Vonage API call. - Extracts
recipientandmessagefromreq.body. - Input Validation: Checks if
recipientandmessageare 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 atry...catchblock.- Success: If
responseData.messages[0].statusis'0', the message was accepted by Vonage. Logs success and returns a 200 OK response with themessage-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
catchblock handles exceptions during the API call (e.g., network issues, invalid credentials leading to SDK errors) and returns a generic 500 error.
- Success: If
- Defined as an
/healthEndpoint (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:
bashnpm startYou should see output indicating the server is running:
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 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.bashcurl -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!" } - Send the request.
Expected Responses:
-
Success:
json{ "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):
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
statuscode in the Vonage response and logs/returns theerror-text. - SDK/Network Errors: Uses a
try...catchblock around thevonage.sms.sendcall. - Basic Logging: Uses
console.logandconsole.error.
Production Enhancements:
- Structured Logging: Implement a dedicated logging library like
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). - 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 (
.envlocally, platform-specific configuration in deployment). - Ensure
.envis 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
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.
- The current validation is basic. Robustly validate phone numbers (e.g., using libraries like
- 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.
- API Keys: Issue keys to legitimate clients and require them in headers (e.g.,
- 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 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.- 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 therecipientnumber 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, 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.- Process Manager: Use a process manager like
pm2or 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 installandnpm start? - Configuration: Are
VONAGE_API_KEY,VONAGE_API_SECRET, andVONAGE_SENDER_ID_OR_NUMBERcorrectly 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 /healthreturn a 200 OK response with{ "status": "UP", ... }? - Successful SMS: Can you successfully send an SMS using
curlor Postman to a valid (and potentially whitelisted) recipient?- Does the API return a
200 OKwith{ "success": true, "messageId": "..." }? - Do you receive the SMS on the recipient's phone?
- Does the API return a
- Validation Error Handling: Does sending a request without
recipientormessagereturn a400 Bad Requestwith 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
.envincluded 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
How to send SMS with Node.js and Express
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.
What is Vonage SMS API used for
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.
Why use Express framework with Node.js for SMS
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.
How to set up Vonage API credentials
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.
When to use a virtual number for sending SMS
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'.
Can I use an alphanumeric sender ID with Vonage
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.
How to fix 'Non-Whitelisted Destination' error
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.
What is dotenv used for in Node.js project
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.
Why does my Vonage SMS fail to send
Check several potential issues: incorrect API credentials, unverified recipient number (for trial accounts), invalid sender ID, incorrect phone number format, or network connectivity issues.
When should I implement rate limiting for my SMS API
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.
How to improve error handling in Node.js SMS app
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.
What are security best practices for Vonage API integration
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.
How to deploy my Node.js SMS application
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.
How to test my Vonage SMS API endpoint
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.