How to Send SMS with Node.js and Vonage API: Complete Express Tutorial
Step-by-step tutorial to send SMS messages using Node.js, Express, and Vonage SMS API. Complete guide with code examples, authentication setup, error handling, and production deployment tips.
Learn how to send SMS messages programmatically using Node.js, Express, and the Vonage SMS API. This comprehensive tutorial covers everything from initial setup to production deployment, including authentication, error handling, and best practices for SMS integration.
By the end of this guide, you'll have a fully functional Express API endpoint that sends SMS messages to any phone number worldwide using the Vonage Node.js SDK. Perfect for building SMS notifications, alerts, two-factor authentication, or customer communication features in your Node.js applications.
Project Overview and Goals
Goal: Create a REST API endpoint using Node.js and Express that sends SMS messages to specified recipients via the Vonage SMS API.
Problem Solved: Send SMS messages programmatically from your applications, automating communication workflows.
Technologies Used:
Node.js: JavaScript runtime environment for server-side applications.
Express: Minimal, flexible Node.js web framework for creating API endpoints.
Vonage Node.js SDK: Library provided by Vonage to simplify SMS API interaction.
dotenv: Module to load environment variables from a .env file, keeping sensitive credentials secure.
Prerequisites:
Node.js and npm (or yarn): Install from nodejs.org.
Vonage API Account: Sign up for free at Vonage API Dashboard. New accounts receive free testing credit.
A Text Editor or IDE: VS Code, Sublime Text, or WebStorm.
API Testing Tool:curl, Postman, or Insomnia.
1. Setting up the Node.js Project
Create your project directory and set up dependencies and configuration for sending SMS with Node.js.
Project Structure
Code
vonage-sms-guide/
├── lib/
│ └── sms.js # SMS sending logic
├── index.js # Express server & API endpoint
├── .env # Environment variables (not in version control)
├── .gitignore # Files to exclude from Git
└── package.json # Project dependencies
Setup Steps
Create Project Directory:
Open your terminal and create a new directory for your project.
Bash
mkdir vonage-sms-guide
cd vonage-sms-guide
Initialize Node.js Project:
Initialize the project using npm. The -y flag accepts default settings.
Bash
npm init -y
This creates a package.json file.
Install Dependencies:
Install Express for the web server, the Vonage Server SDK for API interaction, and dotenv for environment variables.
Bash
npm install express @vonage/server-sdk dotenv
Enable ES Modules:
Open your package.json file and add "type": "module" to use modern import/export syntax.
JSON
{"name":"vonage-sms-guide","version":"1.0.0","description":"","main":"index.js","scripts":{"start":"node index.js","test":"echo \"Error: no test specified\" && exit 1"},"keywords":[],"author":"","license":"ISC","dependencies":{"@vonage/server-sdk":"^3.13.0","dotenv":"^16.4.5","express":"^4.19.2"},"type":"module"}
(Note: The versions shown are examples. Your package.json will reflect the specific versions installed by npm install.)
Create Environment File (.env):
Create a file named .env in the root of your project directory. This file stores your sensitive API credentials and configuration. Never commit this file to version control.
.env
# .env
# Server Configuration
PORT=3000
# Vonage API Credentials
VONAGE_API_KEY=YOUR_API_KEY
VONAGE_API_SECRET=YOUR_API_SECRET
# Vonage Sender Information
# Use either a purchased Vonage number (in E.164 format, e.g., +14155552671)
# or a registered Alphanumeric Sender ID (e.g., "MyApp")
VONAGE_VIRTUAL_NUMBER=YOUR_VONAGE_NUMBER_OR_SENDER_ID
Populate the VONAGE_ values in the next section.
Create .gitignore File:
Prevent accidentally committing sensitive files and unnecessary directories by creating a .gitignore file in the root of your project:
Locate your API key and API secret on the main dashboard.
Copy these values.
Open your .env file and replace YOUR_API_KEY and YOUR_API_SECRET with your actual credentials.
.env
# .env (Example – use your actual credentials)
PORT=3000
VONAGE_API_KEY=a1b2c3d4
VONAGE_API_SECRET=e5f6g7h8i9j0k1l2
VONAGE_VIRTUAL_NUMBER=YOUR_VONAGE_NUMBER_OR_SENDER_ID
Configure Sender Number/ID:
Vonage requires a "from" number or identifier for your SMS messages. Choose one option:
Vonage Virtual Number: Purchase a virtual phone number through the dashboard (Numbers → Buy numbers). These numbers support two-way messaging. Use E.164 format (e.g., +14155552671).
Alphanumeric Sender ID: In supported countries, register a custom sender ID (like your brand name, e.g., "MyApp"). This supports one-way messaging only. Consult Vonage documentation for registration requirements.
Update VONAGE_VIRTUAL_NUMBER in your .env file with your chosen number or Sender ID.
.env
# .env (Example using a US number)
PORT=3000
VONAGE_API_KEY=a1b2c3d4
VONAGE_API_SECRET=e5f6g7h8i9j0k1l2
VONAGE_VIRTUAL_NUMBER=+14155552671
Trial Account Limitation (Important):
Free trial accounts can only send SMS messages to verified phone numbers within the dashboard.
To add and verify test numbers:
Navigate to your Vonage Dashboard.
Find the "Getting started" section or "Test Numbers"/"Sandbox" area.
Add the phone number(s) you want to test.
Enter the verification code Vonage sends via SMS or voice call.
Sending to unverified numbers on trial accounts returns a "Non-Whitelisted Destination" error. Add payment details to upgrade and send messages globally.
3. Implementing the SMS Sending Logic
Write the code that handles interaction with the Vonage SDK to send SMS messages programmatically.
SMS Sending Flow
Code
1. Validate environment variables → 2. Initialize Vonage client →
3. Validate input parameters → 4. Send SMS via API →
5. Check response status → 6. Return success or throw error
Create the file lib/sms.js:
JavaScript
// lib/sms.jsimport { Vonage } from'@vonage/server-sdk';
import dotenv from'dotenv';
// Load environment variables from .env file
dotenv.config();
// Validate essential environment variablesif (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET || !process.env.VONAGE_VIRTUAL_NUMBER) {
console.error("Error: Missing Vonage API credentials or sender number in .env file.");
process.exit(1); // Exit if critical config is missing
}
// Initialize Vonage client using API Key and Secret// Note: This guide uses the legacy SMS API authentication method (API Key/Secret)// via `vonage.sms.send()` for simplicity. Vonage recommends the// newer Messages API (using Application ID/Private Key and `vonage.messages.send()`)// for new applications as it supports more channels and features.const vonage = newVonage({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET
});
const sender = process.env.VONAGE_VIRTUAL_NUMBER;
/**
* Sends an SMS message using the Vonage SMS API.
* @param {string} recipient - The recipient's phone number in E.164 format (e.g., +14155552671).
* @param {string} message - The text message content.
* @returns {Promise<object>} A promise that resolves with the Vonage API response data on success.
* @throws {Error} Throws an error if the SMS sending fails.
*/exportconstsendSms = async (recipient, message) => {
console.log(`Attempting to send SMS from ${sender} to ${recipient}`);
// Input validation (basic)if (!recipient || !message) {
thrownewError("Recipient phone number and message text are required.");
}
// Basic E.164 format check (can be more robust)if (!/^\+[1-9]\d{1,14}$/.test(recipient)) {
console.warn(`Recipient number ${recipient} might not be in valid E.164 format.`);
// Depending on requirements, you might throw an error here instead.// throw new Error("Recipient phone number must be in E.164 format (e.g., +14155552671).");
}
// Use the vonage.sms.send method (part of the older SMS API interface within the SDK)returnnewPromise((resolve, reject) => {
vonage.sms.send(sender, recipient, message, (err, responseData) => {
if (err) {
console.error("Vonage API Error:", err);
// Provide a more specific error message if possiblereject(newError(`Failed to send SMS via Vonage: ${err.message || err}`));
} else {
// Check the status of the first (and likely only) message in the responseif (responseData.messages[0]['status'] === "0") {
console.log("SMS submitted successfully:", responseData.messages[0]);
resolve(responseData); // Resolve with the full response data
} else {
const errorCode = responseData.messages[0]['status'];
const errorText = responseData.messages[0]['error-text'];
console.error(`Vonage Message Failed – Code: ${errorCode}, Error: ${errorText}`);
// Reject with a specific error message including details from Vonagereject(newError(`Vonage error ${errorCode}: ${errorText}`));
}
}
});
});
};
Code Breakdown:
Imports: Import Vonage from the SDK and dotenv to load environment variables.
Configuration Loading:dotenv.config() loads variables from .env. Basic checks ensure critical variables are present.
Vonage Initialization: Create a Vonage instance using apiKey and apiSecret from environment variables. The comment clarifies this uses the legacy SMS API authentication and mentions the newer Messages API as the recommended alternative.
sendSms Function:
Takes recipient and message as arguments.
Includes basic logging and input validation (checking presence and a simple E.164 format regex).
Wraps the vonage.sms.send(...) call in a Promise for easier async/await handling in the API layer.
vonage.sms.send takes sender, recipient, message text, and a callback function.
Callback Handling:
Checks for network/request errors (err).
If no request error, checks responseData.messages[0]['status']. Status "0" indicates success.
If status is not "0", logs the error code and text from Vonage and rejects the promise with a descriptive error.
On success, logs details and resolves the promise with responseData.
4. Building the Express API Endpoint
Create the Express server and REST API endpoint that uses the sendSms function to send text messages.
Create the file index.js in the project root:
JavaScript
// index.jsimport express from'express';
import dotenv from'dotenv';
import { sendSms } from'./lib/sms.js'; // Import our SMS sending function// Load environment variables
dotenv.config();
const app = express();
constPORT = process.env.PORT || 3000; // Use port from .env or default to 3000// Middleware to parse JSON request bodies
app.use(express.json());
// Middleware to parse URL-encoded request bodies
app.use(express.urlencoded({ extended: true }));
// Simple Root Route for Health Check/Info
app.get('/', (req, res) => {
res.status(200).json({
message: "Vonage SMS API Guide Server is running.",
sendEndpoint: "/send-sms (POST)",
requiredBody: { to: "+14155552671", text: "Your message content" }
});
});
/**
* API Endpoint: POST /send-sms
* Sends an SMS message via Vonage.
* Request Body: JSON { "to": "recipient_number", "text": "message_content" }
* - "to": Recipient phone number in E.164 format (e.g., +14155552671)
* - "text": The message body
* Responses:
* - 200 OK: { success: true, data: { message-count: '1', messages: […] } }
* - 400 Bad Request: { success: false, message: "Validation error details" }
* - 500 Internal Server Error: { success: false, message: "Error details from Vonage or server" }
*/
app.post('/send-sms', async (req, res) => {
const { to, text } = req.body;
// Input Validationif (!to || !text) {
return res.status(400).json({
success: false,
message: "Missing required fields: 'to' (recipient phone number) and 'text' (message content) are required in the request body."
});
}
// Optional: More specific validation (e.g., length checks, format) could go here// Using the basic E.164 check inside sendSms for now.console.log(`Received request to send SMS to: ${to}`);
try {
// Call the SMS Sending Logicconst result = awaitsendSms(to, text); // Uses the function from lib/sms.jsconsole.log("Vonage API Response:", result);
// Send Success Response
res.status(200).json({
success: true,
message: "SMS submitted successfully to Vonage.",
data: result // Include the full response from Vonage
});
} catch (error) {
// Handle Errorsconsole.error("Error in /send-sms endpoint:", error);
// Default error message and statuslet statusCode = 500;
let errorMessage = "Failed to send SMS due to an internal server error.";
// Check if it's a Vonage-specific error message from our lib/sms.js rejection// Note: Relying on string matching can be brittle; see Section 6 for improvements.if (error.message.startsWith("Vonage error") || error.message.includes("Failed to send SMS via Vonage")) {
errorMessage = error.message;
// Potentially adjust status code based on Vonage error (e.g., 400 for bad number format if validation was stricter)// For simplicity, keeping 500 for most Vonage backend errors unless clearly client-input related.// The "Non-Whitelisted Destination" error is common on trial accounts.if (error.message.includes("Non-Whitelisted Destination")) {
errorMessage = "Failed to send SMS: The recipient number is not whitelisted for this trial account. Verify the number in the Vonage dashboard.";
statusCode = 400; // Consider this a client-side setup issue
} elseif (error.message.includes("Invalid Credentials")) {
errorMessage = "Failed to send SMS: Invalid Vonage API Key or Secret. Check your .env file.";
statusCode = 500; // Server config issue
}
} elseif (error.message.includes("Recipient phone number and message text are required")) {
// Error from our basic validation within sendSms
statusCode = 400;
errorMessage = error.message;
}
res.status(statusCode).json({
success: false,
message: errorMessage
});
}
});
// Start the server
app.listen(PORT, () => {
console.log(`Server listening at http://localhost:${PORT}`);
console.log(`API Endpoint available at POST http://localhost:${PORT}/send-sms`);
});
Code Breakdown:
Imports: Import express and our sendSms function.
Initialization: Load dotenv, create the Express app, and define the PORT.
Middleware: Use express.json() and express.urlencoded() to parse incoming JSON and form data in request bodies.
Root Route (/): A simple GET endpoint to confirm the server is running.
/send-sms Endpoint (POST):
Defined using app.post.
Extracts to and text from req.body.
Input Validation: Performs a basic check to ensure to and text are provided. Returns a 400 Bad Request if not.
Call sendSms: Calls the imported function within a try...catch block to handle potential errors (both network errors and Vonage API errors returned via promise rejection).
Success Response: If sendSms resolves successfully, sends a 200 OK response containing the success status and the data received from the Vonage API.
Error Handling: If sendSms rejects, the catch block executes. It logs the error and sends an appropriate error response (usually 500 Internal Server Error, but checks for specific known error messages like validation failures or whitelisting issues to potentially return a 400 Bad Request).
Server Start:app.listen starts the server on the specified port.
5. Testing Your SMS Endpoint
Run the Node.js application and test the SMS sending functionality.
Verify .env Configuration: Verify your .env file contains the correct VONAGE_API_KEY, VONAGE_API_SECRET, and VONAGE_VIRTUAL_NUMBER. Confirm the recipient phone number is whitelisted if using a trial account.
Start the Server:
Open your terminal in the project root directory and run:
Bash
npm start
Or directly:
Bash
node index.js
Expected output:
Code
Server listening at http://localhost:3000
API Endpoint available at POST http://localhost:3000/send-sms
Test with curl:
Open a new terminal window. Replace [REPLACE_WITH_YOUR_VERIFIED_NUMBER] with your verified recipient phone number (in E.164 format, e.g., +14155552671) and adjust the message text.
Bash
curl -X POST http://localhost:3000/send-sms \
-H "Content-Type: application/json" \
-d '{
"to": "[REPLACE_WITH_YOUR_VERIFIED_NUMBER]",
"text": "Hello from Node.js and Vonage!"
}'
Expected Success Response:
If the request succeeds, you should see output in the terminal where curl was run similar to this (the message-id, remaining-balance, and message-price will vary):
JSON
{"success":true,"message":"SMS submitted successfully to Vonage.","data":{"message-count":"1","messages":[{"to":"1xxxxxxxxxx","message-id":"a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6","status":"0","remaining-balance":"1.85000000","message-price":"0.00650000","network":"12345"}]}}
You should receive the SMS on the recipient phone shortly after. The terminal running the Node.js server will show log messages.
Example Error Response (Missing Field):
If you omit the text field:
{"success":false,"message":"Missing required fields: 'to' (recipient phone number) and 'text' (message content) are required in the request body."}
Example Error Response (Non-Whitelisted Number on Trial):
JSON
{"success":false,"message":"Failed to send SMS: The recipient number is not whitelisted for this trial account. Verify the number in the Vonage dashboard."}
6. Error Handling and Logging
Current Implementation
The lib/sms.js function checks the status code from the Vonage response and rejects the promise with a specific error message if it's not "0".
The index.js endpoint uses a try...catch block to handle promise rejections from sendSms.
Basic input validation checks for required fields (to, text) in index.js.
Specific error messages for common issues like missing fields, whitelisting problems, and invalid credentials are provided in the API response based on string matching of error messages.
console.log and console.error are used for logging basic information and errors to the terminal.
Production Enhancements
Implement these patterns to improve reliability and observability:
Structured Logging: Use a dedicated logging library like Pino or Winston for structured JSON logs, which are easier to parse and analyze. Include request IDs for tracing.
Centralized Error Handling: Implement Express error-handling middleware for a more consistent way to catch and format errors across different routes.
Detailed Vonage Errors: Consult the Vonage SMS API reference for a complete list of status codes (error-text) and handle critical ones more explicitly if needed (e.g., insufficient funds, barring).
Custom Error Classes: Replace string matching (error.message.includes(...)) with custom error classes thrown from lib/sms.js or checking specific error codes provided by the Vonage SDK (if available) for more reliable error type detection in the catch block.
Retry Mechanisms: For transient network errors or specific Vonage error codes indicating temporary issues, implement a retry strategy with exponential backoff using libraries like async-retry. Avoid retrying errors caused by invalid input.
7. Security Considerations
Security Checklist
Security Layer
Implementation
Priority
API Credentials
Store in .env locally and use secure configuration management in production. Never commit to version control.
Critical
Input Validation
Use robust validation libraries like joi or express-validator to strictly validate phone number format and message content.
You've successfully built a Node.js and Express application that sends SMS messages programmatically using the Vonage SMS API with API Key/Secret authentication. This tutorial covered project setup, credential management, SMS sending logic with the Vonage Node.js SDK, API endpoint creation, testing, error handling, security, and deployment considerations.
Extend this foundation by:
Implementing webhook endpoints to receive incoming SMS messages or delivery receipts.
Adding more robust validation and error handling.
Integrating a database to log sent messages.
Building a user interface to trigger sends.
Exploring other Vonage APIs (Voice, Verify, Video) or migrating to the Messages API for multi-channel support.
Frequently Asked Questions (FAQ)
How much does it cost to send SMS with Vonage?
Vonage pricing varies by destination country. New accounts receive free credit for testing. Check the Vonage Pricing page for specific rates. SMS typically costs $0.0075–$0.05 per message depending on the destination.
Can I send SMS to any phone number with a Vonage trial account?
No. Trial accounts can only send SMS to verified phone numbers added in the Vonage Dashboard. Verify test numbers before sending. Upgrade to a paid account by adding payment details to send messages globally without restrictions.
What is E.164 phone number format?
E.164 is the international phone number format required by Vonage. It includes a plus sign (+), country code, and phone number without spaces or special characters. Example: +14155552671 for a US number.
How do I fix "Non-Whitelisted Destination" errors?
Add and verify the recipient number in your Vonage Dashboard under "Test Numbers" or upgrade your account to a paid plan.
What's the difference between Vonage SMS API and Messages API?
The SMS API uses API Key/Secret authentication and supports SMS only. The Messages API uses Application ID/Private Key authentication and supports multiple channels (SMS, WhatsApp, Viber, Facebook Messenger). Vonage recommends the Messages API for new applications.
Can I receive SMS messages with this setup?
This tutorial covers sending SMS only. To receive messages, configure webhook URLs in your Vonage Dashboard to receive inbound message notifications. Implement webhook endpoints in Express to handle incoming requests.
How do I handle SMS delivery receipts?
Configure delivery receipt webhooks in the Vonage Dashboard. Create an Express endpoint to receive delivery status updates (delivered, failed, expired). Use the message ID to track delivery status for each sent SMS.
Is the Vonage Node.js SDK production-ready?
Yes. The @vonage/server-sdk package is officially maintained by Vonage and suitable for production. Implement proper error handling, rate limiting, and monitoring for production deployments.
Use the Vonage Node.js SDK and Express to create an API endpoint. This endpoint accepts a phone number and message, then uses the SDK to send the SMS via the Vonage API. The SDK simplifies interaction with Vonage's services, providing methods to send messages, handle responses, and manage errors, while Express manages routing and server logic. The guide walks through the complete process from setting up the project to testing and deployment considerations.
The Vonage Node.js SDK is a software library that simplifies using Vonage APIs in Node.js applications. It handles the complexities of API requests and responses, allowing you to send SMS messages, make voice calls, and more, with just a few lines of code. The article shows an example using vonage.sms.send() from the legacy SMS API and mentions that vonage.messages.send() (from the Messages API) is generally preferred for new projects.
The API Key and Secret act as your credentials, authenticating your application with the Vonage API. They ensure only authorized applications can access the SMS API. These should be stored in a .env file, locally.
An Alphanumeric Sender ID, like your brand name, can be used instead of a phone number for sending one-way SMS messages. This guide recommends checking Vonage's documentation for registration requirements for the Alphanumeric Sender ID and provides example configurations using VONAGE_VIRTUAL_NUMBER in the .env file.
Trial accounts can only send SMS to verified numbers. Add and verify your test numbers via the Vonage dashboard. Attempting to send to unverified numbers will result in an error. It is required to upgrade your account to send globally without verification/whitelisting.
Start by creating a project directory and initializing it with npm. Install necessary packages like Express, @vonage/server-sdk, and dotenv. Create a .env file to store your Vonage API credentials, ensuring you never commit this file to version control. The guide provides step-by-step instructions including example package.json setups.
The .env file stores your sensitive API credentials (like your Vonage API Key and Secret) and other environment-specific configuration. The dotenv package loads these variables into process.env. This is crucial for keeping your secrets out of your codebase and version control.
The example code includes a try-catch block and checks for errors returned by the Vonage API. For production systems, consider using structured logging, custom error classes, retry mechanisms, and centralized error handling. This is essential to ensure your application remains resilient.
Express simplifies creating the API endpoint needed to interact with the Vonage SMS API. It handles routing, middleware (like JSON parsing), and server management, letting you focus on the SMS logic. The article uses examples such as / and /send-sms endpoints.
The tutorial provides example curl commands for testing. You can also use tools like Postman or Insomnia to make POST requests to the /send-sms endpoint. You'll need a valid, verified recipient number and message content in the request body. Example responses are shown for success and common errors.
E.164 is an international standard for phone number formatting. It ensures numbers are written in a consistent way for global compatibility. The format includes a '+' sign followed by the country code and national subscriber number, for example +14155552671. You can find more information about E.164 format online.
Secure your API keys, implement robust input validation and sanitization, use rate limiting, add authentication and authorization, and run the application over HTTPS. It's vital for preventing abuse and protecting your Vonage account.
Log in to your Vonage API Dashboard. Your API Key and Secret are displayed prominently on the main dashboard page. The article provides direct links to the dashboard and explains where to find your credentials.
The article highlights security best practices including storing API keys securely as environment variables, validating user input using libraries like Joi or express-validator, rate-limiting API requests using express-rate-limit to mitigate abuse, and implementing authentication/authorization protocols like JWT to control endpoint access.
Common errors include 'Non-Whitelisted Destination' (add the number to your verified list on the Vonage dashboard), 'Invalid Credentials' (check .env file for typos), and incorrect virtual number format. Check the guide's troubleshooting section for solutions.
Learn how to send SMS messages programmatically using Node.js, Express, and the Vonage SMS API. This comprehensive tutorial covers everything from initial setup to production deployment, including authentication, error handling, and best practices for SMS integration.
By the end of this guide, you'll have a fully functional Express API endpoint that sends SMS messages to any phone number worldwide using the Vonage Node.js SDK. Perfect for building SMS notifications, alerts, two-factor authentication, or customer communication features in your Node.js applications.
Project Overview and Goals
dotenv: Module to load environment variables from a.envfile, keeping sensitive credentials secure.curl, Postman, or Insomnia.1. Setting up the Node.js Project
Create your project directory and set up dependencies and configuration for sending SMS with Node.js.
Project Structure
Setup Steps
Create Project Directory: Open your terminal and create a new directory for your project.
mkdir vonage-sms-guide cd vonage-sms-guideInitialize Node.js Project: Initialize the project using npm. The
-yflag accepts default settings.This creates a
package.jsonfile.Install Dependencies: Install Express for the web server, the Vonage Server SDK for API interaction, and
dotenvfor environment variables.Enable ES Modules: Open your
package.jsonfile and add"type": "module"to use modernimport/exportsyntax.{ "name": "vonage-sms-guide", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@vonage/server-sdk": "^3.13.0", "dotenv": "^16.4.5", "express": "^4.19.2" }, "type": "module" }(Note: The versions shown are examples. Your
package.jsonwill reflect the specific versions installed bynpm install.)Create Environment File (
.env): Create a file named.envin the root of your project directory. This file stores your sensitive API credentials and configuration. Never commit this file to version control.Populate the
VONAGE_values in the next section.Create
.gitignoreFile: Prevent accidentally committing sensitive files and unnecessary directories by creating a.gitignorefile in the root of your project:Create Project Directories: Create the
libdirectory for your SMS logic:mkdir lib2. Getting Your Vonage API Credentials
Retrieve your Vonage SMS API credentials and configure your account to send text messages.
Retrieve API Credentials:
.envfile and replaceYOUR_API_KEYandYOUR_API_SECRETwith your actual credentials.Configure Sender Number/ID: Vonage requires a "from" number or identifier for your SMS messages. Choose one option:
+14155552671)."MyApp"). This supports one-way messaging only. Consult Vonage documentation for registration requirements.Update
VONAGE_VIRTUAL_NUMBERin your.envfile with your chosen number or Sender ID.Trial Account Limitation (Important):
"Non-Whitelisted Destination"error. Add payment details to upgrade and send messages globally.3. Implementing the SMS Sending Logic
Write the code that handles interaction with the Vonage SDK to send SMS messages programmatically.
SMS Sending Flow
Create the file
lib/sms.js:// lib/sms.js import { Vonage } from '@vonage/server-sdk'; import dotenv from 'dotenv'; // Load environment variables from .env file dotenv.config(); // Validate essential environment variables if (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET || !process.env.VONAGE_VIRTUAL_NUMBER) { console.error("Error: Missing Vonage API credentials or sender number in .env file."); process.exit(1); // Exit if critical config is missing } // Initialize Vonage client using API Key and Secret // Note: This guide uses the legacy SMS API authentication method (API Key/Secret) // via `vonage.sms.send()` for simplicity. Vonage recommends the // newer Messages API (using Application ID/Private Key and `vonage.messages.send()`) // for new applications as it supports more channels and features. const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET }); const sender = process.env.VONAGE_VIRTUAL_NUMBER; /** * Sends an SMS message using the Vonage SMS API. * @param {string} recipient - The recipient's phone number in E.164 format (e.g., +14155552671). * @param {string} message - The text message content. * @returns {Promise<object>} A promise that resolves with the Vonage API response data on success. * @throws {Error} Throws an error if the SMS sending fails. */ export const sendSms = async (recipient, message) => { console.log(`Attempting to send SMS from ${sender} to ${recipient}`); // Input validation (basic) if (!recipient || !message) { throw new Error("Recipient phone number and message text are required."); } // Basic E.164 format check (can be more robust) if (!/^\+[1-9]\d{1,14}$/.test(recipient)) { console.warn(`Recipient number ${recipient} might not be in valid E.164 format.`); // Depending on requirements, you might throw an error here instead. // throw new Error("Recipient phone number must be in E.164 format (e.g., +14155552671)."); } // Use the vonage.sms.send method (part of the older SMS API interface within the SDK) return new Promise((resolve, reject) => { vonage.sms.send(sender, recipient, message, (err, responseData) => { if (err) { console.error("Vonage API Error:", err); // Provide a more specific error message if possible reject(new Error(`Failed to send SMS via Vonage: ${err.message || err}`)); } else { // Check the status of the first (and likely only) message in the response if (responseData.messages[0]['status'] === "0") { console.log("SMS submitted successfully:", responseData.messages[0]); resolve(responseData); // Resolve with the full response data } else { const errorCode = responseData.messages[0]['status']; const errorText = responseData.messages[0]['error-text']; console.error(`Vonage Message Failed – Code: ${errorCode}, Error: ${errorText}`); // Reject with a specific error message including details from Vonage reject(new Error(`Vonage error ${errorCode}: ${errorText}`)); } } }); }); };Code Breakdown:
Vonagefrom the SDK anddotenvto load environment variables.dotenv.config()loads variables from.env. Basic checks ensure critical variables are present.Vonageinstance usingapiKeyandapiSecretfrom environment variables. The comment clarifies this uses the legacy SMS API authentication and mentions the newer Messages API as the recommended alternative.sendSmsFunction:recipientandmessageas arguments.vonage.sms.send(...)call in aPromisefor easierasync/awaithandling in the API layer.vonage.sms.sendtakes sender, recipient, message text, and a callback function.err).responseData.messages[0]['status']. Status"0"indicates success."0", logs the error code and text from Vonage and rejects the promise with a descriptive error.responseData.4. Building the Express API Endpoint
Create the Express server and REST API endpoint that uses the
sendSmsfunction to send text messages.Create the file
index.jsin the project root:// index.js import express from 'express'; import dotenv from 'dotenv'; import { sendSms } from './lib/sms.js'; // Import our SMS sending function // Load environment variables dotenv.config(); const app = express(); const PORT = process.env.PORT || 3000; // Use port from .env or default to 3000 // Middleware to parse JSON request bodies app.use(express.json()); // Middleware to parse URL-encoded request bodies app.use(express.urlencoded({ extended: true })); // Simple Root Route for Health Check/Info app.get('/', (req, res) => { res.status(200).json({ message: "Vonage SMS API Guide Server is running.", sendEndpoint: "/send-sms (POST)", requiredBody: { to: "+14155552671", text: "Your message content" } }); }); /** * API Endpoint: POST /send-sms * Sends an SMS message via Vonage. * Request Body: JSON { "to": "recipient_number", "text": "message_content" } * - "to": Recipient phone number in E.164 format (e.g., +14155552671) * - "text": The message body * Responses: * - 200 OK: { success: true, data: { message-count: '1', messages: […] } } * - 400 Bad Request: { success: false, message: "Validation error details" } * - 500 Internal Server Error: { success: false, message: "Error details from Vonage or server" } */ app.post('/send-sms', async (req, res) => { const { to, text } = req.body; // Input Validation if (!to || !text) { return res.status(400).json({ success: false, message: "Missing required fields: 'to' (recipient phone number) and 'text' (message content) are required in the request body." }); } // Optional: More specific validation (e.g., length checks, format) could go here // Using the basic E.164 check inside sendSms for now. console.log(`Received request to send SMS to: ${to}`); try { // Call the SMS Sending Logic const result = await sendSms(to, text); // Uses the function from lib/sms.js console.log("Vonage API Response:", result); // Send Success Response res.status(200).json({ success: true, message: "SMS submitted successfully to Vonage.", data: result // Include the full response from Vonage }); } catch (error) { // Handle Errors console.error("Error in /send-sms endpoint:", error); // Default error message and status let statusCode = 500; let errorMessage = "Failed to send SMS due to an internal server error."; // Check if it's a Vonage-specific error message from our lib/sms.js rejection // Note: Relying on string matching can be brittle; see Section 6 for improvements. if (error.message.startsWith("Vonage error") || error.message.includes("Failed to send SMS via Vonage")) { errorMessage = error.message; // Potentially adjust status code based on Vonage error (e.g., 400 for bad number format if validation was stricter) // For simplicity, keeping 500 for most Vonage backend errors unless clearly client-input related. // The "Non-Whitelisted Destination" error is common on trial accounts. if (error.message.includes("Non-Whitelisted Destination")) { errorMessage = "Failed to send SMS: The recipient number is not whitelisted for this trial account. Verify the number in the Vonage dashboard."; statusCode = 400; // Consider this a client-side setup issue } else if (error.message.includes("Invalid Credentials")) { errorMessage = "Failed to send SMS: Invalid Vonage API Key or Secret. Check your .env file."; statusCode = 500; // Server config issue } } else if (error.message.includes("Recipient phone number and message text are required")) { // Error from our basic validation within sendSms statusCode = 400; errorMessage = error.message; } res.status(statusCode).json({ success: false, message: errorMessage }); } }); // Start the server app.listen(PORT, () => { console.log(`Server listening at http://localhost:${PORT}`); console.log(`API Endpoint available at POST http://localhost:${PORT}/send-sms`); });Code Breakdown:
expressand oursendSmsfunction.dotenv, create the Expressapp, and define thePORT.express.json()andexpress.urlencoded()to parse incoming JSON and form data in request bodies./): A simple GET endpoint to confirm the server is running./send-smsEndpoint (POST):app.post.toandtextfromreq.body.toandtextare provided. Returns a400 Bad Requestif not.sendSms: Calls the imported function within atry...catchblock to handle potential errors (both network errors and Vonage API errors returned via promise rejection).sendSmsresolves successfully, sends a200 OKresponse containing the success status and the data received from the Vonage API.sendSmsrejects, thecatchblock executes. It logs the error and sends an appropriate error response (usually500 Internal Server Error, but checks for specific known error messages like validation failures or whitelisting issues to potentially return a400 Bad Request).app.listenstarts the server on the specified port.5. Testing Your SMS Endpoint
Run the Node.js application and test the SMS sending functionality.
Verify
.envConfiguration: Verify your.envfile contains the correctVONAGE_API_KEY,VONAGE_API_SECRET, andVONAGE_VIRTUAL_NUMBER. Confirm the recipient phone number is whitelisted if using a trial account.Start the Server: Open your terminal in the project root directory and run:
Or directly:
Expected output:
Test with
curl: Open a new terminal window. Replace[REPLACE_WITH_YOUR_VERIFIED_NUMBER]with your verified recipient phone number (in E.164 format, e.g.,+14155552671) and adjust the message text.curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "[REPLACE_WITH_YOUR_VERIFIED_NUMBER]", "text": "Hello from Node.js and Vonage!" }'Expected Success Response: If the request succeeds, you should see output in the terminal where
curlwas run similar to this (themessage-id,remaining-balance, andmessage-pricewill vary):{ "success": true, "message": "SMS submitted successfully to Vonage.", "data": { "message-count": "1", "messages": [ { "to": "1xxxxxxxxxx", "message-id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6", "status": "0", "remaining-balance": "1.85000000", "message-price": "0.00650000", "network": "12345" } ] } }You should receive the SMS on the recipient phone shortly after. The terminal running the Node.js server will show log messages.
Example Error Response (Missing Field): If you omit the
textfield:curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{"to": "[REPLACE_WITH_YOUR_VERIFIED_NUMBER]"}'Response:
{ "success": false, "message": "Missing required fields: 'to' (recipient phone number) and 'text' (message content) are required in the request body." }Example Error Response (Non-Whitelisted Number on Trial):
{ "success": false, "message": "Failed to send SMS: The recipient number is not whitelisted for this trial account. Verify the number in the Vonage dashboard." }6. Error Handling and Logging
Current Implementation
lib/sms.jsfunction checks thestatuscode from the Vonage response and rejects the promise with a specific error message if it's not"0".index.jsendpoint uses atry...catchblock to handle promise rejections fromsendSms.to,text) inindex.js.console.logandconsole.errorare used for logging basic information and errors to the terminal.Production Enhancements
Implement these patterns to improve reliability and observability:
error-text) and handle critical ones more explicitly if needed (e.g., insufficient funds, barring).error.message.includes(...)) with custom error classes thrown fromlib/sms.jsor checking specific error codes provided by the Vonage SDK (if available) for more reliable error type detection in thecatchblock.async-retry. Avoid retrying errors caused by invalid input.7. Security Considerations
Security Checklist
.envlocally and use secure configuration management in production. Never commit to version control.joiorexpress-validatorto strictly validate phone number format and message content.express-rate-limitto prevent abuse and accidental loops.8. Troubleshooting and Caveats
Common Issues and Solutions
Non-Whitelisted DestinationerrorInvalid Credentialserror.env.envfile loadingVONAGE_VIRTUAL_NUMBER+14155552671/send-smspath,POSTmethod, and server portContent-Type: application/jsonheader with valid JSON body9. Deployment (Conceptual)
Deploy this application by running the Node.js process on a server and managing environment variables securely.
Deployment Checklist
.env)NODE_ENV=productionConclusion
You've successfully built a Node.js and Express application that sends SMS messages programmatically using the Vonage SMS API with API Key/Secret authentication. This tutorial covered project setup, credential management, SMS sending logic with the Vonage Node.js SDK, API endpoint creation, testing, error handling, security, and deployment considerations.
Extend this foundation by:
Frequently Asked Questions (FAQ)
How much does it cost to send SMS with Vonage?
Vonage pricing varies by destination country. New accounts receive free credit for testing. Check the Vonage Pricing page for specific rates. SMS typically costs $0.0075–$0.05 per message depending on the destination.
Can I send SMS to any phone number with a Vonage trial account?
No. Trial accounts can only send SMS to verified phone numbers added in the Vonage Dashboard. Verify test numbers before sending. Upgrade to a paid account by adding payment details to send messages globally without restrictions.
What is E.164 phone number format?
E.164 is the international phone number format required by Vonage. It includes a plus sign (+), country code, and phone number without spaces or special characters. Example:
+14155552671for a US number.How do I fix "Non-Whitelisted Destination" errors?
Add and verify the recipient number in your Vonage Dashboard under "Test Numbers" or upgrade your account to a paid plan.
What's the difference between Vonage SMS API and Messages API?
The SMS API uses API Key/Secret authentication and supports SMS only. The Messages API uses Application ID/Private Key authentication and supports multiple channels (SMS, WhatsApp, Viber, Facebook Messenger). Vonage recommends the Messages API for new applications.
Can I receive SMS messages with this setup?
This tutorial covers sending SMS only. To receive messages, configure webhook URLs in your Vonage Dashboard to receive inbound message notifications. Implement webhook endpoints in Express to handle incoming requests.
How do I handle SMS delivery receipts?
Configure delivery receipt webhooks in the Vonage Dashboard. Create an Express endpoint to receive delivery status updates (delivered, failed, expired). Use the message ID to track delivery status for each sent SMS.
Is the Vonage Node.js SDK production-ready?
Yes. The
@vonage/server-sdkpackage is officially maintained by Vonage and suitable for production. Implement proper error handling, rate limiting, and monitoring for production deployments.Further Reading