How to Send SMS with Node.js and Vonage API: Complete Tutorial
Learn how to build a Node.js REST API to send SMS messages using the Vonage SMS API. Complete guide with Express setup, code examples, error handling, and deployment.
This guide provides a complete walkthrough for building a Node.js and Express application that sends SMS messages using the Vonage SMS API. You'll learn everything from project setup to deployment and troubleshooting.
By the end, you'll have a functional REST API endpoint that accepts a phone number and message text, then uses the Vonage API to programmatically send SMS messages. This serves as a foundation for applications requiring SMS notifications, two-factor authentication, user verification, or other communication features.
Project Overview and Goals
What You're Building:
A minimal REST API built with Node.js and Express. This API exposes a single endpoint (POST /send-sms) that takes a recipient phone number and message body, and uses the Vonage Node.js SDK to send the SMS via the Vonage SMS API. This guide focuses on core functionality; production deployments require additional security, error handling, and scalability enhancements (discussed later).
Problem Solved:
Provides a straightforward, server-side mechanism to programmatically send SMS messages, abstracting direct Vonage API interaction into a simple API call.
Technologies Used:
Node.js: JavaScript runtime for server-side code
Express: Minimal Node.js web application framework for creating API endpoints
Vonage Node.js SDK (@vonage/server-sdk): Simplifies Vonage API interaction, handling authentication and request formatting (GitHub repo)
Vonage SMS API: Underlying service that dispatches SMS messages globally (General Availability status)
dotenv: Module to load environment variables from a .env file into process.env, keeping credentials secure
System Architecture:
An HTTP client (like curl or a web application) sends a POST request to the /send-sms endpoint of your Node.js/Express server. The server uses the Vonage SDK, configured with API credentials, to request the Vonage SMS API. Vonage then sends the SMS to the recipient's phone.
Prerequisites:
Node.js and npm: Install from nodejs.org. Requires Node.js v18.11.0+ for built-in watch mode.
Vonage API Account: Sign up for free at Vonage API Dashboard. You'll receive free credit for testing.
Trial Account Limitation: New Vonage accounts start on trial. You must whitelist recipient phone numbers in the Vonage Dashboard before sending SMS during trial. See the Troubleshooting section for details.
Vonage API Key and Secret: Found on your Vonage API Dashboard main page after signup
Vonage Virtual Number: Rent a Vonage phone number capable of sending SMS from the Dashboard under "Numbers" > "Buy numbers". Trial accounts may have destination limitations.
(Optional) HTTP Client: Tools like curl or Postman for testing
Expected Outcome:
A running Node.js server with a /send-sms endpoint. Sending a POST request with valid to and text parameters results in an SMS sent via Vonage.
1. Setting Up Your Node.js SMS Project
Initialize your Node.js project and install dependencies.
Create Project Directory:
Bash
mkdir vonage-sms-guide
cd vonage-sms-guide
Initialize npm:
Bash
npm init -y
This creates package.json to manage dependencies and scripts.
Install Dependencies:
Bash
npm install express @vonage/server-sdk dotenv
Create Project Files:
Bash
touch index.js .env .gitignore
Configure .gitignore:
Add these lines to prevent committing dependencies and sensitive credentials:
Text
# .gitignore
node_modules/
.env
Configure Environment Variables (.env):
Replace placeholder values with your actual credentials from the Vonage Dashboard:
.env
# .env
# Found on your Vonage API Dashboard
VONAGE_API_KEY=YOUR_API_KEY
VONAGE_API_SECRET=YOUR_API_SECRET
# Your Vonage virtual number capable of sending SMS
VONAGE_VIRTUAL_NUMBER=YOUR_VONAGE_NUMBER
# Port for the Express server
PORT=3000
VONAGE_API_KEY / VONAGE_API_SECRET: SDK uses these to authenticate requests
VONAGE_VIRTUAL_NUMBER: The "From" number appearing on recipient's phone (must be associated with your account)
PORT: Local port for your Express server
Project Structure:
Code
vonage-sms-guide/
├── node_modules/
├── .env # API keys and secrets (DO NOT COMMIT)
├── .gitignore # Untracked files
├── index.js # Main application code
├── package-lock.json # Exact dependency versions
└── package.json # Project metadata and dependencies
2. Implementing Core SMS Functionality
Write the code in index.js to set up the Express server, initialize the Vonage SDK, and create the SMS sending function.
JavaScript
// index.js'use strict';
// 1. Import necessary modulesrequire('dotenv').config(); // Load environment variables from .envconst express = require('express');
const { Vonage } = require('@vonage/server-sdk');
// 2. Initialize Vonage SDK// API Key and Secret are loaded from environment variablesconst vonage = newVonage({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET
});
// 3. Initialize Express appconst app = express();
app.use(express.json()); // Parse JSON request bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded data// 4. Define the SMS sending functionasyncfunctionsendSms(recipient, message) {
constfrom = process.env.VONAGE_VIRTUAL_NUMBER;
const to = recipient;
const text = message;
try {
// Uses Vonage's SMS API (General Availability)// Reference: https://developer.vonage.com/en/messaging/sms/overviewconst responseData = await vonage.sms.send({ to, from, text });
console.log(`Message sent successfully to ${to}. Message ID: ${responseData.messages[0]['message-id']}`);
// Status '0' indicates success per Vonage API specif (responseData.messages[0]['status'] === '0') {
return { success: true, data: responseData };
} else {
const errorText = responseData.messages[0]['error-text'];
console.error(`Message failed with error: ${errorText}`);
return { success: false, message: `Message failed: ${errorText}` };
}
} catch (err) {
// Handle network issues or SDK errorsconsole.error("Error sending SMS:", err);
return { success: false, message: 'Failed to send SMS due to an internal error.' };
}
}
// 5. Define the API endpoint (Covered in next section)// ... Endpoint code will go here ...// 6. Start the serverconst port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port} on ${newDate().toLocaleString()}`);
console.log(`Using Vonage Number: ${process.env.VONAGE_VIRTUAL_NUMBER}`);
// Check for API credentialsif (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET || !process.env.VONAGE_VIRTUAL_NUMBER) {
console.warn("***********************************************************************");
console.warn("WARNING: Vonage API credentials or virtual number missing in .env file!");
console.warn("SMS sending will likely fail. Check your .env configuration.");
console.warn("***********************************************************************");
}
});
Explanation:
Imports: Load dotenv for credentials, express for the server, and the Vonage class from the SDK
Vonage Initialization: Create a Vonage instance with API key and secret from process.env. The SDK handles authentication using these credentials (SDK documentation)
Express Initialization: Create Express app and apply middleware to parse request bodies
sendSms Function:
async function encapsulating SMS logic
Retrieves from number from environment variables
Uses vonage.sms.send() with to, from, and text parameters
Checks the status property in responseData.messages[0]['status']. Status '0' indicates success according to Vonage API documentation
Handles both successful sends and API-level errors (invalid numbers, insufficient funds, etc.)
Includes try...catch for network errors or SDK exceptions
Returns object with success: true/false and relevant data or error message
Server Start: Retrieves PORT from .env (defaults to 3000), starts Express server, logs confirmation and warnings if credentials are missing
3. Building the REST API Endpoint
Add the API endpoint to index.js before the app.listen call (where the "// 5. Define the API endpoint" comment is).
JavaScript
// index.js (Add before app.listen)// 5. Define the API endpoint
app.post('/send-sms', async (req, res) => {
// Basic Input Validationconst { to, text } = req.body;
if (!to || !text) {
console.warn('Received invalid request: Missing "to" or "text" field.');
return res.status(400).json({
success: false,
message: 'Missing required fields: "to" (recipient phone number) and "text" (message body).'
});
}
// E.164 format check (international phone number standard)const phoneRegex = /^\+[1-9]\d{1,14}$/;
if (!phoneRegex.test(to)) {
console.warn(`Received invalid phone number format: ${to}`);
return res.status(400).json({
success: false,
message: 'Invalid phone number format. Use E.164 format (e.g., +14155552671).'
});
// Note: Production validation should use libraries like google-libphonenumber// for comprehensive country-specific rules
}
console.log(`Received request to send SMS to: ${to}`);
// Call the SMS sending functionconst result = awaitsendSms(to, text);
// Send response based on resultif (result.success) {
res.status(200).json({
success: true,
message: 'SMS sent successfully.', // Final delivery is asynchronousdetails: result.data// Include Vonage response details
});
} else {
// 500 for internal errors; could map specific Vonage errors to 400/502
res.status(500).json({
success: false,
message: result.message
});
}
});
Explanation:
Route Definition:app.post('/send-sms', ...) listens for HTTP POST requests on /send-sms
Input Validation:
Extracts to (recipient) and text (message) from req.body
Checks both fields are present; returns 400 Bad Request if missing
Regex validates E.164 phone number format (international standard). Production apps should use robust libraries like google-libphonenumber
Call sendSms: Invokes the function with validated parameters
Response Handling:
On success (200 OK): Returns success message and Vonage response data
On failure (500 Internal Server Error): Returns error message. More refined implementations might map specific Vonage errors to different HTTP status codes
4. Vonage API Integration Setup
Integration occurs during setup and initialization:
Credentials: Stored securely in .env (VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_VIRTUAL_NUMBER)
SDK Initialization:Vonage SDK instance created with these credentials in index.js
API Call:vonage.sms.send() handles communication with Vonage API, including authentication headers
Dashboard Configuration (Critical for Trial Accounts):
Navigate to "Numbers" > "Your numbers" to confirm you have a virtual number capable of sending SMS
Trial accounts: You must add and verify recipient phone numbers. Look for profile/settings or "Test Numbers" section. Sending to unverified numbers fails. Upgrade by adding payment details to remove restrictions (common beginner issue)
API Settings: Check "API settings" in dashboard. The "Default SMS Setting" should be appropriate. This guide uses the SMS API (vonage.sms.send). For receiving messages, this setting changes webhook formats
5. Error Handling, Logging, and Retry Logic
Error Handling:
sendSms function uses try...catch for network or SDK-level errors
Inspects Vonage API response (responseData.messages[0].status and error-text) to detect API-specific errors (invalid number, insufficient balance)
API endpoint returns appropriate HTTP status codes (400 for bad input, 500 for server/API errors, 200 for success)
Logging:
Basic console.log, console.warn, and console.error provide visibility
Production recommendation: Use robust logging libraries (Winston or Pino) for structured JSON logs to files or logging services. Log: request receipt, validation failures, Vonage API calls, success/error responses, caught exceptions
Retry Mechanisms:
This guide doesn't implement automatic retries
Production recommendation: For transient network errors or rate-limiting (status code 1), implement retry strategy with exponential backoff using libraries like async-retry. Wrap vonage.sms.send() in a retry loop with progressive waits. Don't retry permanent errors (invalid API key, invalid recipient)
6. Database Schema
Not applicable for this simple SMS API. For tracking sent messages, storing templates, or managing users, add a database (PostgreSQL, MongoDB) and data layer (ORM like Prisma or Sequelize).
7. Security Best Practices for SMS APIs
Secrets Management: Use .env and .gitignore. In production, use environment variables from deployment platform or dedicated secrets manager
Input Validation: Basic validation for to and text presence and format included. Use libraries like joi or express-validator for complex validation in production
Rate Limiting: Not implemented. Use middleware like express-rate-limit to prevent API abuse (limit requests per IP)
Authentication/Authorization: Endpoint is currently open. In production, protect with API keys, JWT tokens, or session authentication
8. Handling International SMS and Special Cases
International Numbers: E.164 format (+ followed by country code and number) is standard. Vonage routes based on this. Ensure your account/number is enabled for destination countries
Character Limits & Encoding:
Standard SMS: 160 GSM-7 characters or 70 UCS-2 (Unicode) characters per message (Vonage encoding guide)
Longer messages split into concatenated SMS (charged per segment)
Characters from GSM extended table require 2 bytes each
Special characters (Chinese, Japanese, Korean, Arabic, Cyrillic) force UCS-2 encoding, reducing limit to 70 characters
Vonage accepts up to 3,200 characters, but best practice is max 6 SMS parts
Delivery Reports (DLRs): Not covered in this guide. Configure webhook URL in Vonage settings to receive delivery status updates (delivered, failed, rejected). Requires adding another endpoint to your Express app for callbacks
9. Performance Optimization for High-Volume SMS
Not critical for single SMS messages. For high volumes, consider:
Asynchronous Processing: Push SMS tasks to background job queue (e.g., BullMQ with Redis) and return immediate 202 Accepted response. Separate worker process handles sending
SDK Client Reuse: The vonage client is initialized once and reused (already efficient)
10. Monitoring and Analytics for SMS Delivery
Logging: Use structured logging (see Section 5)
Health Checks: Add a /health endpoint returning 200 OK for load balancers or monitoring systems
Metrics: Track request counts, error rates, and latency for /send-sms using monitoring tools (Prometheus/Grafana, Datadog). Track Vonage API call latency and success/error rates
Error Tracking: Use services like Sentry or Bugsnag to capture and aggregate runtime errors
11. Common Issues and Troubleshooting
Error: 401 Unauthorized (Authentication failed)
Cause: Incorrect VONAGE_API_KEY or VONAGE_API_SECRET in .env
Solution: Verify credentials in .env against Vonage Dashboard. Ensure require('dotenv').config(); is called early. Check for extra spaces or characters
Error: Invalid 'From' number
Cause:VONAGE_VIRTUAL_NUMBER is incorrect, not owned by your account, or not SMS-capable
Solution: Verify number in Dashboard under "Numbers" > "Your numbers". Ensure correct E.164 format
Cause: Trial account sending to non-whitelisted phone number
Solution: Add recipient to "Test Numbers" list in Dashboard, or upgrade account by adding billing details
Error: Missing Mandatory Field (Status code 4 or 5)
Cause: Missing or invalid to, from, or text parameter
Solution: Check sendSms function and /send-sms endpoint logic. Review logs for validation errors
SMS Not Received, but API shows Success (status: '0')
Causes: Carrier filtering, incorrect recipient number (valid format but wrong number), network issues, destination country restrictions
Solutions:
Verify recipient number is correct and active
Check Dashboard logs ("Logs" > "SMS") for detailed delivery status
Test with different number or carrier
Verify account allows sending to specific country
Set up DLR webhooks for detailed delivery status
Caveat: Environment Variables Not Loading
Cause:require('dotenv').config(); not called, called too late, or .env file in wrong location
Solution: Ensure require('dotenv').config(); is first line in index.js. Verify .env location (project root where node runs)
Caveat: Port Conflict (EADDRINUSE)
Cause: Another application using the port
Solution: Stop other application or change PORT in .env
12. Production Deployment and CI/CD
Deployment Platforms: Deploy to Heroku, Render, AWS Elastic Beanstalk, Google Cloud Run, or VPS
Environment Variables:Never include .env in deployment or Git commits. Configure variables (VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_VIRTUAL_NUMBER, PORT, NODE_ENV=production) in deployment platform's settings
Note: dev script requires Node.js v18.11.0+. For broader compatibility, use nodemon index.js (requires npm install --save-dev nodemon).
Most platforms automatically run npm start
CI/CD Pipeline (GitHub Actions example):
Push code to Git repository (GitHub, GitLab)
Set repository secrets for Vonage credentials and deployment keys
Installs dependencies (npm ci – preferred in CI for using package-lock.json)
(Optional) Runs tests (npm test)
Deploys to platform using CLI or GitHub Actions (e.g., actions/heroku-deploy, google-github-actions/deploy-cloudrun)
Passes environment variables securely during deployment
Rollback: Most platforms offer mechanisms to redeploy previous versions (e.g., Heroku's heroku rollback, AWS CodeDeploy strategies)
13. Testing Your SMS API
Start the Server Locally:
Ensure .env is correctly populated:
Bash
node index.js
You should see "Server listening..." in the console.
Manual Testing (using curl):
Open a new terminal. Replace +14155550100 with valid test number (whitelisted for trial accounts):
Bash
curl -X POST http://localhost:3000/send-sms \
-H "Content-Type: application/json" \
-d '{
"to": "+14155550100",
"text": "Hello from your Node.js Vonage App!"
}'
Check Expected Output:
Terminal running curl:
Success:
JSON
{"success":true,"message":"SMS sent successfully.","details":{"message-count":"1","messages":[{"to":"14155550100","message-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","status":"0","remaining-balance":"x.xxxx","message-price":"x.xxxx","network":"xxxxx"}]}}
Terminal running Node.js server: Check console logs for "Received request...", "Message sent successfully...", or error details
Recipient Phone: Should receive the SMS
Vonage Dashboard: Check "Logs" > "SMS" for message record and status
Automated Testing:
Unit Tests: Use Jest or Mocha/Chai to test sendSms in isolation. Mock @vonage/server-sdk to avoid actual API calls. Test different inputs and scenarios based on mocked responses
Integration Tests: Use supertest to make HTTP requests to Express app. Test /send-sms endpoint, validating inputs, responses, and status codes
Frequently Asked Questions
How do I send SMS messages from Node.js?
To send SMS from Node.js, install the Vonage SDK (npm install @vonage/server-sdk), initialize it with your API credentials, and call vonage.sms.send() with the recipient number, sender number, and message text. This guide provides a complete implementation with Express.
What is the best SMS API for Node.js?
Vonage SMS API (formerly Nexmo) is one of the most popular choices for Node.js due to its comprehensive SDK, reliable delivery, and competitive pricing. Other options include Twilio, AWS SNS, and MessageBird. The choice depends on your requirements for features, pricing, and geographic coverage.
How much does it cost to send SMS with Vonage?
Vonage SMS pricing varies by destination country. Most messages cost between $0.0045-0.02 USD per SMS segment. New accounts receive free credit for testing. Check the Vonage Pricing page for specific rates.
What is E.164 phone number format?
E.164 is the international phone number format required by most SMS APIs. It consists of a plus sign (+) followed by the country code and phone number, with no spaces or special characters (e.g., +14155552671 for a US number).
How many characters can I send in an SMS?
Standard SMS supports 160 GSM-7 characters or 70 Unicode characters per message. Longer messages are automatically split into concatenated SMS segments, with each part counting as a separate billable message. Characters from the GSM extended table use 2 bytes each.
Can I send SMS to international numbers?
Yes, Vonage supports SMS delivery to over 200 countries. Ensure your phone numbers use E.164 format with the correct country code. Note that trial accounts may have restrictions on international destinations until you add billing details.
How do I handle SMS delivery failures?
Implement comprehensive error handling by checking the status code in Vonage's response. Status '0' indicates success, while other codes indicate specific errors (invalid number, insufficient balance, etc.). Set up delivery receipt webhooks for real-time delivery confirmation.
What's the difference between Vonage SMS API and Messages API?
The SMS API (used in this guide) is optimized for simple text messaging and is in General Availability. The Messages API is a newer, more feature-rich platform supporting SMS, MMS, WhatsApp, Facebook Messenger, and Viber through a unified interface.
Next Steps
After completing this tutorial, consider these enhancements:
Add delivery receipts by configuring webhook endpoints to track message delivery status
Implement message templates for common use cases like verification codes or notifications
Add rate limiting to prevent abuse and manage API costs
Integrate with a database to log sent messages and track delivery metrics
Explore the Messages API for multi-channel communication (SMS, WhatsApp, etc.)
Set up monitoring with tools like Datadog or New Relic for production reliability
This guide provides a solid foundation for sending SMS messages with Node.js, Express, and Vonage. Adapt error handling, security, logging, and deployment strategies to fit your production requirements.
Frequently asked questions
Use the Vonage SMS API with the Node.js SDK and Express. Create an API endpoint that accepts recipient number and message text, then uses the SDK to send the SMS via Vonage.
The Vonage Node.js SDK (@vonage/server-sdk) simplifies interaction with the Vonage APIs. It handles authentication and request formatting, making it easier to send SMS messages from your Node.js application.
For trial accounts, Vonage requires whitelisting recipient numbers in the dashboard for security and to prevent abuse. This restriction is lifted once the account is upgraded with billing details.
While this guide uses the simpler vonage.sms.send() method, Vonage's Messages API offers more advanced features. Consider it for richer messaging needs beyond basic SMS.
Yes, use the E.164 format (+[country code][number]). Ensure your Vonage account and number are enabled for the destination countries and that you comply with any applicable regulations.
Initialize a Node project with npm init, install express, @vonage/server-sdk, and dotenv. Create index.js, .env, and .gitignore. Add API keys and virtual number to .env and implement the SMS sending logic.
The .env file stores sensitive information like your Vonage API key, secret, and virtual number. It's crucial for security and should never be committed to version control.
Use try...catch blocks for network/SDK errors. Check the Vonage API response status for success ('0') or specific error codes. Implement robust logging for easier debugging and monitoring in production.
Start your server locally with node index.js. Use a tool like curl or Postman to send POST requests to your /send-sms endpoint with test data, ensuring whitelisted recipient numbers in trial accounts. Check responses and logs to verify successful SMS delivery.
In production, use retry mechanisms with exponential backoff for handling transient errors like network issues or rate limiting. Avoid retrying permanent errors like invalid API keys.
Use a secrets manager for API keys, implement robust input validation, rate limiting, and appropriate authentication/authorization for the /send-sms endpoint.
Standard SMS messages are limited to 160 characters (GSM-7 encoding). Longer messages may be split, incurring costs for multiple segments. Special characters might force UCS-2, further reducing per-segment limits.
Configure a webhook URL in your Vonage Dashboard settings to receive delivery reports. This will notify your application when a message is delivered, failed, or rejected.
Double-check your VONAGE_API_KEY and VONAGE_API_SECRET in the .env file and your Vonage Dashboard. Ensure the .env file is loaded correctly early in your application.
Several reasons include carrier issues, incorrect recipient number, temporary network problems, or country-specific restrictions. Verify the recipient number, consult Vonage Dashboard logs, and consider setting up delivery receipts.
This guide provides a complete walkthrough for building a Node.js and Express application that sends SMS messages using the Vonage SMS API. You'll learn everything from project setup to deployment and troubleshooting.
By the end, you'll have a functional REST API endpoint that accepts a phone number and message text, then uses the Vonage API to programmatically send SMS messages. This serves as a foundation for applications requiring SMS notifications, two-factor authentication, user verification, or other communication features.
Project Overview and Goals
What You're Building:
A minimal REST API built with Node.js and Express. This API exposes a single endpoint (
POST /send-sms) that takes a recipient phone number and message body, and uses the Vonage Node.js SDK to send the SMS via the Vonage SMS API. This guide focuses on core functionality; production deployments require additional security, error handling, and scalability enhancements (discussed later).Problem Solved:
Provides a straightforward, server-side mechanism to programmatically send SMS messages, abstracting direct Vonage API interaction into a simple API call.
Technologies Used:
@vonage/server-sdk): Simplifies Vonage API interaction, handling authentication and request formatting (GitHub repo).envfile intoprocess.env, keeping credentials secureSystem Architecture:
An HTTP client (like
curlor a web application) sends a POST request to the/send-smsendpoint of your Node.js/Express server. The server uses the Vonage SDK, configured with API credentials, to request the Vonage SMS API. Vonage then sends the SMS to the recipient's phone.Prerequisites:
curlor Postman for testingExpected Outcome:
A running Node.js server with a
/send-smsendpoint. Sending a POST request with validtoandtextparameters results in an SMS sent via Vonage.1. Setting Up Your Node.js SMS Project
Initialize your Node.js project and install dependencies.
Create Project Directory:
mkdir vonage-sms-guide cd vonage-sms-guideInitialize npm:
This creates
package.jsonto manage dependencies and scripts.Install Dependencies:
Create Project Files:
touch index.js .env .gitignoreConfigure
.gitignore: Add these lines to prevent committing dependencies and sensitive credentials:Configure Environment Variables (
.env): Replace placeholder values with your actual credentials from the Vonage Dashboard:VONAGE_API_KEY/VONAGE_API_SECRET: SDK uses these to authenticate requestsVONAGE_VIRTUAL_NUMBER: The "From" number appearing on recipient's phone (must be associated with your account)PORT: Local port for your Express serverProject Structure:
2. Implementing Core SMS Functionality
Write the code in
index.jsto set up the Express server, initialize the Vonage SDK, and create the SMS sending function.// index.js 'use strict'; // 1. Import necessary modules require('dotenv').config(); // Load environment variables from .env const express = require('express'); const { Vonage } = require('@vonage/server-sdk'); // 2. Initialize Vonage SDK // API Key and Secret are loaded from environment variables const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET }); // 3. Initialize Express app const app = express(); app.use(express.json()); // Parse JSON request bodies app.use(express.urlencoded({ extended: true })); // Parse URL-encoded data // 4. Define the SMS sending function async function sendSms(recipient, message) { const from = process.env.VONAGE_VIRTUAL_NUMBER; const to = recipient; const text = message; try { // Uses Vonage's SMS API (General Availability) // Reference: https://developer.vonage.com/en/messaging/sms/overview const responseData = await vonage.sms.send({ to, from, text }); console.log(`Message sent successfully to ${to}. Message ID: ${responseData.messages[0]['message-id']}`); // Status '0' indicates success per Vonage API spec if (responseData.messages[0]['status'] === '0') { return { success: true, data: responseData }; } else { const errorText = responseData.messages[0]['error-text']; console.error(`Message failed with error: ${errorText}`); return { success: false, message: `Message failed: ${errorText}` }; } } catch (err) { // Handle network issues or SDK errors console.error("Error sending SMS:", err); return { success: false, message: 'Failed to send SMS due to an internal error.' }; } } // 5. Define the API endpoint (Covered in next section) // ... Endpoint code will go here ... // 6. Start the server const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server listening at http://localhost:${port} on ${new Date().toLocaleString()}`); console.log(`Using Vonage Number: ${process.env.VONAGE_VIRTUAL_NUMBER}`); // Check for API credentials if (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET || !process.env.VONAGE_VIRTUAL_NUMBER) { console.warn("***********************************************************************"); console.warn("WARNING: Vonage API credentials or virtual number missing in .env file!"); console.warn("SMS sending will likely fail. Check your .env configuration."); console.warn("***********************************************************************"); } });Explanation:
dotenvfor credentials,expressfor the server, and theVonageclass from the SDKVonageinstance with API key and secret fromprocess.env. The SDK handles authentication using these credentials (SDK documentation)sendSmsFunction:asyncfunction encapsulating SMS logicfromnumber from environment variablesvonage.sms.send()withto,from, andtextparametersstatusproperty inresponseData.messages[0]['status']. Status'0'indicates success according to Vonage API documentationtry...catchfor network errors or SDK exceptionssuccess: true/falseand relevant data or error messagePORTfrom.env(defaults to 3000), starts Express server, logs confirmation and warnings if credentials are missing3. Building the REST API Endpoint
Add the API endpoint to
index.jsbefore theapp.listencall (where the "// 5. Define the API endpoint" comment is).// index.js (Add before app.listen) // 5. Define the API endpoint app.post('/send-sms', async (req, res) => { // Basic Input Validation const { to, text } = req.body; if (!to || !text) { console.warn('Received invalid request: Missing "to" or "text" field.'); return res.status(400).json({ success: false, message: 'Missing required fields: "to" (recipient phone number) and "text" (message body).' }); } // E.164 format check (international phone number standard) const phoneRegex = /^\+[1-9]\d{1,14}$/; if (!phoneRegex.test(to)) { console.warn(`Received invalid phone number format: ${to}`); return res.status(400).json({ success: false, message: 'Invalid phone number format. Use E.164 format (e.g., +14155552671).' }); // Note: Production validation should use libraries like google-libphonenumber // for comprehensive country-specific rules } console.log(`Received request to send SMS to: ${to}`); // Call the SMS sending function const result = await sendSms(to, text); // Send response based on result if (result.success) { res.status(200).json({ success: true, message: 'SMS sent successfully.', // Final delivery is asynchronous details: result.data // Include Vonage response details }); } else { // 500 for internal errors; could map specific Vonage errors to 400/502 res.status(500).json({ success: false, message: result.message }); } });Explanation:
app.post('/send-sms', ...)listens for HTTP POST requests on/send-smsto(recipient) andtext(message) fromreq.body400 Bad Requestif missinggoogle-libphonenumbersendSms: Invokes the function with validated parameters200 OK): Returns success message and Vonage response data500 Internal Server Error): Returns error message. More refined implementations might map specific Vonage errors to different HTTP status codes4. Vonage API Integration Setup
Integration occurs during setup and initialization:
.env(VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_VIRTUAL_NUMBER)VonageSDK instance created with these credentials inindex.jsvonage.sms.send()handles communication with Vonage API, including authentication headersvonage.sms.send). For receiving messages, this setting changes webhook formats5. Error Handling, Logging, and Retry Logic
sendSmsfunction usestry...catchfor network or SDK-level errorsresponseData.messages[0].statusanderror-text) to detect API-specific errors (invalid number, insufficient balance)console.log,console.warn, andconsole.errorprovide visibility1), implement retry strategy with exponential backoff using libraries likeasync-retry. Wrapvonage.sms.send()in a retry loop with progressive waits. Don't retry permanent errors (invalid API key, invalid recipient)6. Database Schema
Not applicable for this simple SMS API. For tracking sent messages, storing templates, or managing users, add a database (PostgreSQL, MongoDB) and data layer (ORM like Prisma or Sequelize).
7. Security Best Practices for SMS APIs
.envand.gitignore. In production, use environment variables from deployment platform or dedicated secrets managertoandtextpresence and format included. Use libraries likejoiorexpress-validatorfor complex validation in productionexpress-rate-limitto prevent API abuse (limit requests per IP)8. Handling International SMS and Special Cases
+followed by country code and number) is standard. Vonage routes based on this. Ensure your account/number is enabled for destination countriesdelivered,failed,rejected). Requires adding another endpoint to your Express app for callbacks9. Performance Optimization for High-Volume SMS
Not critical for single SMS messages. For high volumes, consider:
202 Acceptedresponse. Separate worker process handles sendingvonageclient is initialized once and reused (already efficient)10. Monitoring and Analytics for SMS Delivery
/healthendpoint returning200 OKfor load balancers or monitoring systems/send-smsusing monitoring tools (Prometheus/Grafana, Datadog). Track Vonage API call latency and success/error rates11. Common Issues and Troubleshooting
Error: 401 Unauthorized (
Authentication failed)VONAGE_API_KEYorVONAGE_API_SECRETin.env.envagainst Vonage Dashboard. Ensurerequire('dotenv').config();is called early. Check for extra spaces or charactersError:
Invalid 'From' numberVONAGE_VIRTUAL_NUMBERis incorrect, not owned by your account, or not SMS-capableError:
Non-Whitelisted Destination(Status code15)Error:
Missing Mandatory Field(Status code4or5)to,from, ortextparametersendSmsfunction and/send-smsendpoint logic. Review logs for validation errorsSMS Not Received, but API shows Success (
status: '0')Caveat: Environment Variables Not Loading
require('dotenv').config();not called, called too late, or.envfile in wrong locationrequire('dotenv').config();is first line inindex.js. Verify.envlocation (project root wherenoderuns)Caveat: Port Conflict (
EADDRINUSE)PORTin.env12. Production Deployment and CI/CD
Deployment Platforms: Deploy to Heroku, Render, AWS Elastic Beanstalk, Google Cloud Run, or VPS
Environment Variables: Never include
.envin deployment or Git commits. Configure variables (VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_VIRTUAL_NUMBER,PORT,NODE_ENV=production) in deployment platform's settingspackage.jsonScripts:{ "scripts": { "start": "node index.js", "dev": "node --watch index.js" } }Note:
devscript requires Node.js v18.11.0+. For broader compatibility, usenodemon index.js(requiresnpm install --save-dev nodemon). Most platforms automatically runnpm startCI/CD Pipeline (GitHub Actions example):
.github/workflows/deploy.yml) that:npm ci– preferred in CI for usingpackage-lock.json)npm test)actions/heroku-deploy,google-github-actions/deploy-cloudrun)Rollback: Most platforms offer mechanisms to redeploy previous versions (e.g., Heroku's
heroku rollback, AWS CodeDeploy strategies)13. Testing Your SMS API
Start the Server Locally: Ensure
.envis correctly populated:You should see "Server listening..." in the console.
Manual Testing (using
curl): Open a new terminal. Replace+14155550100with valid test number (whitelisted for trial accounts):curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "+14155550100", "text": "Hello from your Node.js Vonage App!" }'Check Expected Output:
curl:{ "success": true, "message": "SMS sent successfully.", "details": { "message-count": "1", "messages": [ { "to": "14155550100", "message-id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "status": "0", "remaining-balance": "x.xxxx", "message-price": "x.xxxx", "network": "xxxxx" } ] } }{ "success": false, "message": "Missing required fields: \"to\" (recipient phone number) and \"text\" (message body)." }{ "success": false, "message": "Message failed: Invalid Sender Address - rejected" }Automated Testing:
sendSmsin isolation. Mock@vonage/server-sdkto avoid actual API calls. Test different inputs and scenarios based on mocked responsessupertestto make HTTP requests to Express app. Test/send-smsendpoint, validating inputs, responses, and status codesFrequently Asked Questions
How do I send SMS messages from Node.js?
To send SMS from Node.js, install the Vonage SDK (
npm install @vonage/server-sdk), initialize it with your API credentials, and callvonage.sms.send()with the recipient number, sender number, and message text. This guide provides a complete implementation with Express.What is the best SMS API for Node.js?
Vonage SMS API (formerly Nexmo) is one of the most popular choices for Node.js due to its comprehensive SDK, reliable delivery, and competitive pricing. Other options include Twilio, AWS SNS, and MessageBird. The choice depends on your requirements for features, pricing, and geographic coverage.
How much does it cost to send SMS with Vonage?
Vonage SMS pricing varies by destination country. Most messages cost between $0.0045-0.02 USD per SMS segment. New accounts receive free credit for testing. Check the Vonage Pricing page for specific rates.
What is E.164 phone number format?
E.164 is the international phone number format required by most SMS APIs. It consists of a plus sign (+) followed by the country code and phone number, with no spaces or special characters (e.g., +14155552671 for a US number).
How many characters can I send in an SMS?
Standard SMS supports 160 GSM-7 characters or 70 Unicode characters per message. Longer messages are automatically split into concatenated SMS segments, with each part counting as a separate billable message. Characters from the GSM extended table use 2 bytes each.
Can I send SMS to international numbers?
Yes, Vonage supports SMS delivery to over 200 countries. Ensure your phone numbers use E.164 format with the correct country code. Note that trial accounts may have restrictions on international destinations until you add billing details.
How do I handle SMS delivery failures?
Implement comprehensive error handling by checking the status code in Vonage's response. Status '0' indicates success, while other codes indicate specific errors (invalid number, insufficient balance, etc.). Set up delivery receipt webhooks for real-time delivery confirmation.
What's the difference between Vonage SMS API and Messages API?
The SMS API (used in this guide) is optimized for simple text messaging and is in General Availability. The Messages API is a newer, more feature-rich platform supporting SMS, MMS, WhatsApp, Facebook Messenger, and Viber through a unified interface.
Next Steps
After completing this tutorial, consider these enhancements:
References
This guide provides a solid foundation for sending SMS messages with Node.js, Express, and Vonage. Adapt error handling, security, logging, and deployment strategies to fit your production requirements.