How to Send SMS with Node.js Using Vonage Messages API: Complete Express Tutorial
Learn how to send SMS programmatically with Node.js and Express using the Vonage Messages API. Step-by-step guide covering setup, security, error handling, and production deployment.
Build a production-ready Node.js and Express application that sends SMS messages programmatically using the Vonage Messages API. This comprehensive tutorial covers project setup, API integration, security best practices, error handling, and deployment strategies.
By the end, you'll have a REST API endpoint that accepts a phone number and message text, then sends an SMS via Vonage. Use this foundation to integrate SMS functionality into larger applications for notifications, two-factor authentication (2FA), OTP verification, or customer alerts.
Time to complete: 45–60 minutes
Skill level: Intermediate (requires basic JavaScript and Node.js knowledge)
Project Overview and Goals
What you're building:
A Node.js web server using Express that exposes a single API endpoint (POST /send-sms). When this endpoint receives a request with a destination phone number and message text, it uses the Vonage Messages API to send an SMS.
Problem solved:
Programmatically send SMS messages from your web application without managing direct carrier integrations.
Technologies:
Node.js: JavaScript runtime for server-side applications. Offers strong performance, a large ecosystem (npm), and asynchronous I/O suitable for API calls
Express: Minimal and flexible Node.js web framework for setting up routes and handling HTTP requests
Vonage Messages API: Communication platform enabling SMS, MMS, WhatsApp, and other messaging channels with robust global infrastructure
@vonage/server-sdk: Official Vonage Node.js library
dotenv: Module for loading environment variables from .env files to securely manage API credentials
Node.js 14+ and npm (or yarn): Download from nodejs.org
Vonage API Account: Sign up at Vonage API Dashboard. New accounts receive free credits
Vonage Application: Create a Vonage application with Messages capabilities enabled
Private Key: Associated with your Vonage application
Vonage Virtual Number: Purchase or rent a phone number through your Vonage account capable of sending SMS. Expect costs of $1–$5/month for number rental plus per-message fees (~$0.01–$0.05 per SMS)
Basic JavaScript/Node.js knowledge: Familiarity with fundamental concepts
Terminal access: For running commands
1. Set Up Your Node.js SMS Project
Create the project directory, initialize Node.js, and install dependencies.
Create project directory:
Open your terminal and create a new directory for your project.
Bash
mkdir vonage-sms-sender
cd vonage-sms-sender
Initialize Node.js project:
Create a package.json file with default settings.
Bash
npm init -y
Install dependencies:
Install express for the web server, @vonage/server-sdk for Vonage API interaction, and dotenv for environment variable management.
Bash
npm install express @vonage/server-sdk dotenv
express: Framework for building the API
@vonage/server-sdk: Simplifies Vonage Messages API calls
dotenv: Loads environment variables from .env files for secure configuration
Create project files:
Create the main application file, environment variables file, and Git ignore rules.
macOS/Linux:
Bash
touch index.js .env .gitignore
Windows (Command Prompt):
cmd
type nul > index.js
type nul > .env
type nul > .gitignore
Windows (PowerShell):
powershell
New-Item index.js, .env, .gitignore
index.js: Contains your Express server and API logic
.env: Stores sensitive credentials like API keys. Never commit this file to version control.
.gitignore: Specifies files Git should ignore (like .env and node_modules)
Configure .gitignore:
Open .gitignore and add these lines to prevent committing sensitive information:
Name your application (e.g., "Node SMS Sender Guide")
Click "Generate public and private key". Important: A file named private.key downloads immediately. Save this file in the root of your project directory (vonage-sms-sender/). You cannot download it again. Vonage stores the public key
Scroll to the "Capabilities" section
Toggle on "Messages". You'll see fields for "Inbound URL" and "Status URL". For sending SMS, these aren't strictly required immediately, but Vonage requires them. Enter placeholder URLs like https://example.com/webhooks/inbound and https://example.com/webhooks/status. Update these with real endpoints later if you plan to receive messages or delivery receipts
Click "Generate new application"
On the application details page, copy the Application ID – you'll need it for your .env file
Link your Vonage number:
Navigate to "Numbers" > "Your numbers" in the dashboard
If you don't have a number, go to "Buy numbers", find one with SMS capability in your desired country, and purchase it. Expect costs of $1–$5/month for number rental
In "Your numbers", find the number you want to use for sending SMS
Click the gear icon or "Manage" button next to the number
Under "Messaging Settings", select the Vonage Application you created ("Node SMS Sender Guide") from the dropdown menu
Click "Save". Copy this phone number (including country code) – this is your sender ID (VONAGE_FROM_NUMBER)
Choosing the right number type:
Long code (standard number): Most common for person-to-person messaging. Suitable for low-volume SMS
Short code: 5–6 digit numbers for high-volume messaging. Requires approval and higher costs
Toll-free: Free for recipients. Good for customer support. May have restrictions in some countries
Get API Key and Secret (optional but recommended):
While you primarily use Application ID and Private Key for Messages API authentication, your main account API Key and Secret are on the dashboard landing page ("API settings"). Store these for future use with other Vonage APIs.
Configure environment variables (.env):
Open the .env file and add these variables, replacing placeholders with your actual credentials:
.env
# Vonage Credentials
VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID_HERE
VONAGE_PRIVATE_KEY_PATH=./private.key
VONAGE_FROM_NUMBER=YOUR_VONAGE_NUMBER_HERE
# Optional: Vonage Account API Key/Secret (for other APIs)
# VONAGE_API_KEY=YOUR_API_KEY_HERE
# VONAGE_API_SECRET=YOUR_API_SECRET_HERE
# Server Configuration
PORT=3000
VONAGE_APPLICATION_ID: The Application ID from your Vonage application
VONAGE_PRIVATE_KEY_PATH: Relative path from index.js to your private.key file. ./private.key assumes it's in the same directory as index.js
VONAGE_FROM_NUMBER: Your Vonage virtual number in E.164 format (e.g., +14155550100)
PORT: Port number your Express server listens on
Security:.env is in .gitignore, preventing accidental commits of secret credentials. Ensure private.key is also in .gitignore.
Recommended: Create a .env.example file with placeholder values to help other developers set up their own .env file:
If you lose your private key: You cannot download it again. Generate a new key pair in the Vonage Dashboard under your application settings, then download and replace the old private.key file.
3. Implement the SMS Sending API Endpoint
Write the Node.js/Express code to create the server and SMS sending endpoint.
Set up Express server (index.js):
Open index.js and add the initial setup:
JavaScript
// index.jsrequire('dotenv').config(); // Load environment variables from .env fileconst express = require('express');
const { Vonage } = require('@vonage/server-sdk');
// --- Vonage Client Initialization ---// Ensure necessary environment variables are loadedif (!process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_PRIVATE_KEY_PATH) {
console.error('Error: VONAGE_APPLICATION_ID or VONAGE_PRIVATE_KEY_PATH not set in .env');
process.exit(1); // Exit if essential config is missing
}
const vonage = newVonage({
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_PRIVATE_KEY_PATH
});
// --- Express App Setup ---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 }));
// --- API Endpoints ---// (Add /send-sms endpoint here)// --- Basic Health Check Endpoint ---
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
// --- Start Server ---
app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`);
});
require('dotenv').config();: Loads variables from .env into process.env. Must be called at the top
Imports express and Vonage from the SDK
Initializes the Vonage client using Application ID and private key path from environment variables. Includes a check to ensure these critical variables are set
Sets up Express app (app)
Uses express.json() and express.urlencoded() middleware to parse incoming request bodies
Includes a /health endpoint for monitoring
Starts the server with app.listen
Add CORS support (if needed for frontend integration):
Install the cors package:
Bash
npm install cors
Update index.js:
JavaScript
// index.js (near the top)const cors = require('cors');
// ...const app = express();
app.use(cors()); // Enable CORS for all routes// ... (rest of middleware)
Implement the /send-sms endpoint:
Add this route handler within the // --- API Endpoints --- section of index.js:
JavaScript
// index.js (inside API Endpoints section)
app.post('/send-sms', async (req, res) => {
// Extract and validate inputconst { to, text } = req.body;
constfrom = process.env.VONAGE_FROM_NUMBER;
if (!to || !text) {
console.error('Missing "to" or "text" in request body');
return res.status(400).json({
success: false,
message: 'Missing "to" phone number or "text" message in request body.'
});
}
if (!from) {
console.error('Error: VONAGE_FROM_NUMBER not set in .env');
return res.status(500).json({
success: false,
message: 'Server configuration error: Sender number not set.'
});
}
// Validate message length (160 chars for single SMS)if (text.length > 1600) { // 10 messages max as example limitreturn res.status(400).json({
success: false,
message: 'Message too long. Maximum 1600 characters (10 SMS segments).'
});
}
console.log(`Attempting to send SMS from ${from} to ${to} with text: "${text}"`);
try {
const resp = await vonage.messages.send({
message_type: "text",
text: text,
to: to,
from: from,
channel: "sms"
});
console.log('Message sent successfully:', resp);
// Vonage Messages API returns message_uuid on success
res.status(200).json({
success: true,
message_uuid: resp.message_uuid
});
} catch (error) {
console.error('Error sending SMS via Vonage:', error);
// Provide specific feedback based on errorlet errorMessage = 'Failed to send SMS.';
let statusCode = 500;
if (error.response?.data) {
console.error('Vonage API Error Details:', error.response.data);
// Extract specific error message from Vonage response
errorMessage = error.response.data.title || error.response.data.detail || errorMessage;
// Set appropriate status code based on error typeif (error.response.status >= 400 && error.response.status < 500) {
statusCode = 400; // Client error
}
} elseif (error.message) {
errorMessage = error.message;
}
res.status(statusCode).json({
success: false,
message: errorMessage,
errorDetails: error.response?.data
});
}
});
Defines a POST route at /send-sms
Uses an async function to handle the asynchronous vonage.messages.send call with await
Input validation: Extracts to (recipient number) and text (message content) from the JSON request body. Checks they exist and that the message isn't too long. Returns 400 Bad Request or 500 Internal Server Error if validation fails
Vonage call: Calls vonage.messages.send() with:
message_type: Set to "text" for standard SMS
text: SMS message content
to: Recipient's phone number (should be in E.164 format, e.g., +14155550101)
from: Your Vonage virtual number (sender ID) from .env
channel: Must be "sms"
Success handling: Logs the response (includes message_uuid) and sends a 200 OK response with success: true and the message_uuid
Error handling: Logs detailed errors and sends a 400 or 500 response with success: false and an error message, potentially including Vonage API response details
Example request/response:
Request:
Bash
curl -X POST http://localhost:3000/send-sms \
-H "Content-Type: application/json" \
-d '{"to": "+14155550101", "text": "Hello from Vonage!"}'
// index.js (near the top, after dotenv)const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// Log to console in developmentif (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// Replace console.log and console.error with logger// Example: logger.info('Server started'), logger.error('Error occurred', { error })
Retry Mechanisms
Vonage internal retries: Vonage handles some retries internally for transient network issues when delivering messages to carriers.
Application-level retries: Network issues can occur between your server and the Vonage API. For critical messages, implement application-level retries with exponential backoff.
Not all SMS messages get delivered. Common reasons:
Invalid or disconnected number
Phone powered off or out of coverage
Carrier blocking or filtering
Number porting in progress
Implement Status Webhooks (Delivery Receipts):
Update your Vonage Application's "Status URL" to your webhook endpoint (e.g., https://yourdomain.com/webhooks/status).
Create the webhook endpoint:
JavaScript
// index.js
app.post('/webhooks/status', (req, res) => {
const { message_uuid, status, error_text } = req.body;
logger.info('Message status update', { message_uuid, status, error_text });
// Store status in database or trigger alerts// Status values: submitted, delivered, rejected, expired, etc.
res.status(200).send('OK');
});
Test Numbers (Free Tier)
On Vonage trial accounts, you can only send SMS to verified test numbers. To add a test number:
Go to Vonage Dashboard > Settings > Test Numbers
Enter the phone number
Verify via SMS or voice call
Sending to unverified numbers results in a "Non-Whitelisted Destination" error.
SMS Compliance and Spam Regulations
Compliance requirements:
Obtain consent: Recipients must opt-in to receive messages
Provide opt-out: Include "Reply STOP to unsubscribe" in your messages
Honor opt-outs: Maintain a suppression list and stop sending to opted-out numbers
TCPA (US): Telephone Consumer Protection Act requires prior express written consent for marketing messages
GDPR (EU): General Data Protection Regulation requires consent and data protection measures
CTIA Guidelines: Follow Cellular Telecommunications Industry Association best practices
Implementation example:
JavaScript
// Maintain opt-out list (use database in production)const optOutList = newSet();
app.post('/send-sms', authenticateApiKey, smsLimiter, async (req, res) => {
const { to, text } = req.body;
// Check opt-out listif (optOutList.has(to)) {
return res.status(400).json({
success: false,
message: 'Recipient has opted out of receiving messages.'
});
}
// ... rest of endpoint logic ...
});
// Handle opt-out webhook (inbound SMS)
app.post('/webhooks/inbound', (req, res) => {
const { from, text } = req.body;
if (text.trim().toUpperCase() === 'STOP') {
optOutList.add(from);
logger.info('User opted out', { phone: from });
// Send confirmation (required by TCPA)
vonage.messages.send({
message_type: "text",
text: "You have been unsubscribed. Reply START to resume messages.",
to: from,
from: process.env.VONAGE_FROM_NUMBER,
channel: "sms"
});
}
res.status(200).send('OK');
});
7. Test Your SMS Implementation
Ensure your implementation works correctly.
Start the Server
Verify your .env file contains correct Vonage credentials.
Bash
node index.js
You should see: Server listening on http://localhost:3000
Manual Testing with curl
Replace YOUR_RECIPIENT_NUMBER with a valid phone number (use a verified test number if on a trial account).
Test successful SMS send:
Bash
curl -X POST http://localhost:3000/send-sms \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-api-key" \
-d '{
"to": "YOUR_RECIPIENT_NUMBER",
"text": "Hello from my Node.js Vonage App!"
}'
Expected output:
Terminal (server):
Text
Server listening on http://localhost:3000
Attempting to send SMS from YOUR_VONAGE_NUMBER to YOUR_RECIPIENT_NUMBER with text: "Hello from my Node.js Vonage App!"
Message sent successfully: { message_uuid: '...' }
Pin SDK version in package.json for stability: "@vonage/server-sdk": "3.x.x" (replace with your version)
Test thoroughly after major version upgrades
Vonage Rate Limits and Quotas
Vonage imposes the following limits (as of 2024):
Free tier: ~1,000 messages (varies by country)
API rate limits:
100 requests/second per account
1,000 requests/second aggregate across all Vonage APIs
SMS throughput: Depends on number type:
Long code: 1 message/second
Short code: Up to 100 messages/second
Toll-free: Up to 3 messages/second
Check your specific limits in the Vonage Dashboard under "Settings" > "API Settings".
FAQ
Q: Can I use this with TypeScript?
A: Yes. Install @types/express and @types/node, rename index.js to index.ts, and compile with TypeScript.
Q: How do I handle inbound SMS?
A: Create a webhook endpoint (POST /webhooks/inbound) and configure it in your Vonage Application's "Inbound URL". The endpoint receives from, to, text, and other parameters.
Q: Can I send MMS?
A: Yes. Change message_type to "image" and add image.url parameter. Requires MMS-capable Vonage number.
Q: How do I track message delivery?
A: Implement status webhooks (see "Delivery Failures" section). Vonage sends updates to your "Status URL" endpoint.
Q: What's the cost per message?
A: Varies by destination country. US SMS typically costs $0.0075–$0.0150 per segment. Check Vonage pricing.
9. Deploy Your SMS Application to Production
Deploy your Node.js application to a production environment with proper configuration management.
Choose a Hosting Platform
Platform comparison:
Platform
Type
Pros
Cons
Cost
Heroku
PaaS
Simple deployment, add-ons ecosystem
Higher cost, limited free tier
~$7/month (Eco dyno)
Render
PaaS
Modern PaaS, free tier, auto-deploy
Newer platform, fewer add-ons
Free tier available, ~$7/month (Starter)
AWS Elastic Beanstalk
PaaS
AWS ecosystem integration, scalable
More complex setup
Pay per usage (~$10–50/month)
AWS EC2
IaaS
Full control, highly scalable
Requires server management
Pay per usage (~$5–100/month)
DigitalOcean
IaaS
Simple VPS, predictable pricing
Manual setup required
~$6–40/month
AWS Lambda
Serverless
Pay per request, auto-scaling
Cold starts, API Gateway costs
Pay per invocation (~$0.20 per 1M requests)
Environment Variable Management
Critical: Never commit .env or private.key to version control.
Platform-specific configuration:
Heroku: Use Config Vars (Dashboard > Settings > Config Vars or heroku config:set)
# View recent releases
heroku releases
# Rollback to previous release
heroku rollback
# Rollback to specific version
heroku rollback v42
Docker rollback:
Bash
# Tag images with version numbers
docker build -t your-app:v1.2.3 .
# Switch to previous version
docker-compose down
docker tag your-app:v1.2.2 your-app:latest
docker-compose up -d
// index.js (at the top)constSentry = require('@sentry/node');
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: 1.0,
});
// Add Sentry request handler after body parsers
app.use(Sentry.Handlers.requestHandler());
// Add routes here// Add Sentry error handler before other error handlers
app.use(Sentry.Handlers.errorHandler());
Set up health check monitoring:
Configure your monitoring service to ping https://your-app.com/health every 1–5 minutes and alert on failures.
Conclusion
You've built a production-ready Node.js and Express application that sends SMS messages programmatically via the Vonage Messages API. This comprehensive guide covered:
Complete project setup with Node.js, Express, and Vonage SDK
Secure credential management with environment variables
Core SMS API implementation with robust error handling
Security best practices (authentication, rate limiting, input validation)
Special case handling (phone formatting, international sending, compliance)
Comprehensive testing strategies (manual and automated)
Production deployment with CI/CD pipelines and monitoring
Use this foundation to integrate SMS capabilities into your applications for notifications, OTP verification, two-factor authentication, alerts, and customer engagement.
Use the Vonage Messages API and the @vonage/server-sdk. Set up an Express server, create a /send-sms endpoint, and use the SDK to send messages. Don't forget to configure your Vonage credentials in a .env file.
The Vonage Messages API is a communication platform for sending SMS, MMS, WhatsApp messages, and more. It provides a simple and reliable way to integrate messaging into your applications, abstracting away the complexity of dealing with carriers directly.
Node.js, with its asynchronous nature, is efficient for I/O operations like API calls. Express simplifies routing and HTTP request handling, making it ideal for creating the API endpoint for sending SMS messages.
Create a Vonage Application in the Vonage Dashboard, link a Vonage virtual number, and download your private key. Store your Application ID, Private Key Path, and Vonage Number in a .env file for secure access.
The private.key file is used to authenticate your Node.js application with the Vonage API. It should be kept secure and never committed to version control. Its path is specified in the VONAGE_PRIVATE_KEY_PATH environment variable.
Use npm install express @vonage/server-sdk dotenv. This installs Express for the web server, the Vonage Server SDK for interacting with the API, and dotenv for managing environment variables.
Log in to your Vonage Dashboard, navigate to 'Applications', and click 'Create a new application'. Generate your public and private keys, enable the 'Messages' capability, and link a Vonage virtual number.
Implement a try-catch block around the vonage.messages.send() call. Return appropriate HTTP status codes (400/500) and informative JSON error messages for client-side and API errors. Consider adding retries for transient network issues.
Use middleware like Joi or express-validator to validate incoming phone numbers and message text. Check for required parameters and enforce limits on text length to ensure only valid data reaches the Vonage API.
The main project files are index.js (containing the server logic), .env (for environment variables), .gitignore, package.json, and package-lock.json. The private.key is in the root directory.
Use the Heroku CLI. Create a Procfile, set config vars for your Vonage credentials and private key path, commit changes, and push to Heroku. Ensure your private.key is handled securely during deployment.
Implement retries with exponential backoff when network issues might disrupt communication between your server and the Vonage API. Use libraries like async-retry for easier implementation, but avoid retrying on certain client errors.
Dotenv loads environment variables from the .env file into process.env, allowing you to securely manage your Vonage API credentials and server configuration without exposing them in your code.
Double-check that your .env file has the correct VONAGE_APPLICATION_ID, VONAGE_PRIVATE_KEY_PATH, and that the private.key file exists at the specified path. Verify dotenv is correctly loaded in index.js.
Rate limiting prevents abuse by restricting the number of requests from a single IP address within a time window. Use express-rate-limit to protect your Vonage account and API endpoint.
Build a production-ready Node.js and Express application that sends SMS messages programmatically using the Vonage Messages API. This comprehensive tutorial covers project setup, API integration, security best practices, error handling, and deployment strategies.
By the end, you'll have a REST API endpoint that accepts a phone number and message text, then sends an SMS via Vonage. Use this foundation to integrate SMS functionality into larger applications for notifications, two-factor authentication (2FA), OTP verification, or customer alerts.
Time to complete: 45–60 minutes
Skill level: Intermediate (requires basic JavaScript and Node.js knowledge)
Project Overview and Goals
What you're building:
A Node.js web server using Express that exposes a single API endpoint (
POST /send-sms). When this endpoint receives a request with a destination phone number and message text, it uses the Vonage Messages API to send an SMS.Problem solved:
Programmatically send SMS messages from your web application without managing direct carrier integrations.
Technologies:
@vonage/server-sdk: Official Vonage Node.js librarydotenv: Module for loading environment variables from.envfiles to securely manage API credentialsSystem architecture:
Prerequisites:
1. Set Up Your Node.js SMS Project
Create the project directory, initialize Node.js, and install dependencies.
Create project directory:
Open your terminal and create a new directory for your project.
mkdir vonage-sms-sender cd vonage-sms-senderInitialize Node.js project:
Create a
package.jsonfile with default settings.Install dependencies:
Install
expressfor the web server,@vonage/server-sdkfor Vonage API interaction, anddotenvfor environment variable management.express: Framework for building the API@vonage/server-sdk: Simplifies Vonage Messages API callsdotenv: Loads environment variables from.envfiles for secure configurationCreate project files:
Create the main application file, environment variables file, and Git ignore rules.
macOS/Linux:
touch index.js .env .gitignoreWindows (Command Prompt):
Windows (PowerShell):
index.js: Contains your Express server and API logic.env: Stores sensitive credentials like API keys. Never commit this file to version control..gitignore: Specifies files Git should ignore (like.envandnode_modules)Configure
.gitignore:Open
.gitignoreand add these lines to prevent committing sensitive information:Verify project structure:
Your project directory should look like this:
2. Configure Vonage API Credentials
Obtain credentials from Vonage and configure your application to use them securely via environment variables.
Log in to Vonage Dashboard:
Access your Vonage API Dashboard.
Create a Vonage Application:
private.keydownloads immediately. Save this file in the root of your project directory (vonage-sms-sender/). You cannot download it again. Vonage stores the public keyhttps://example.com/webhooks/inboundandhttps://example.com/webhooks/status. Update these with real endpoints later if you plan to receive messages or delivery receipts.envfileLink your Vonage number:
VONAGE_FROM_NUMBER)Choosing the right number type:
Get API Key and Secret (optional but recommended):
While you primarily use Application ID and Private Key for Messages API authentication, your main account API Key and Secret are on the dashboard landing page ("API settings"). Store these for future use with other Vonage APIs.
Configure environment variables (
.env):Open the
.envfile and add these variables, replacing placeholders with your actual credentials:VONAGE_APPLICATION_ID: The Application ID from your Vonage applicationVONAGE_PRIVATE_KEY_PATH: Relative path fromindex.jsto yourprivate.keyfile../private.keyassumes it's in the same directory asindex.jsVONAGE_FROM_NUMBER: Your Vonage virtual number in E.164 format (e.g.,+14155550100)PORT: Port number your Express server listens onSecurity:
.envis in.gitignore, preventing accidental commits of secret credentials. Ensureprivate.keyis also in.gitignore.Recommended: Create a
.env.examplefile with placeholder values to help other developers set up their own.envfile:If you lose your private key: You cannot download it again. Generate a new key pair in the Vonage Dashboard under your application settings, then download and replace the old
private.keyfile.3. Implement the SMS Sending API Endpoint
Write the Node.js/Express code to create the server and SMS sending endpoint.
Set up Express server (
index.js):Open
index.jsand add the initial setup:// index.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const { Vonage } = require('@vonage/server-sdk'); // --- Vonage Client Initialization --- // Ensure necessary environment variables are loaded if (!process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_PRIVATE_KEY_PATH) { console.error('Error: VONAGE_APPLICATION_ID or VONAGE_PRIVATE_KEY_PATH not set in .env'); process.exit(1); // Exit if essential config is missing } const vonage = new Vonage({ applicationId: process.env.VONAGE_APPLICATION_ID, privateKey: process.env.VONAGE_PRIVATE_KEY_PATH }); // --- Express App Setup --- 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 })); // --- API Endpoints --- // (Add /send-sms endpoint here) // --- Basic Health Check Endpoint --- app.get('/health', (req, res) => { res.status(200).send('OK'); }); // --- Start Server --- app.listen(port, () => { console.log(`Server listening on http://localhost:${port}`); });require('dotenv').config();: Loads variables from.envintoprocess.env. Must be called at the topexpressandVonagefrom the SDKVonageclient using Application ID and private key path from environment variables. Includes a check to ensure these critical variables are setapp)express.json()andexpress.urlencoded()middleware to parse incoming request bodies/healthendpoint for monitoringapp.listenAdd CORS support (if needed for frontend integration):
Install the
corspackage:Update
index.js:// index.js (near the top) const cors = require('cors'); // ... const app = express(); app.use(cors()); // Enable CORS for all routes // ... (rest of middleware)Implement the
/send-smsendpoint:Add this route handler within the
// --- API Endpoints ---section ofindex.js:// index.js (inside API Endpoints section) app.post('/send-sms', async (req, res) => { // Extract and validate input const { to, text } = req.body; const from = process.env.VONAGE_FROM_NUMBER; if (!to || !text) { console.error('Missing "to" or "text" in request body'); return res.status(400).json({ success: false, message: 'Missing "to" phone number or "text" message in request body.' }); } if (!from) { console.error('Error: VONAGE_FROM_NUMBER not set in .env'); return res.status(500).json({ success: false, message: 'Server configuration error: Sender number not set.' }); } // Validate message length (160 chars for single SMS) if (text.length > 1600) { // 10 messages max as example limit return res.status(400).json({ success: false, message: 'Message too long. Maximum 1600 characters (10 SMS segments).' }); } console.log(`Attempting to send SMS from ${from} to ${to} with text: "${text}"`); try { const resp = await vonage.messages.send({ message_type: "text", text: text, to: to, from: from, channel: "sms" }); console.log('Message sent successfully:', resp); // Vonage Messages API returns message_uuid on success res.status(200).json({ success: true, message_uuid: resp.message_uuid }); } catch (error) { console.error('Error sending SMS via Vonage:', error); // Provide specific feedback based on error let errorMessage = 'Failed to send SMS.'; let statusCode = 500; if (error.response?.data) { console.error('Vonage API Error Details:', error.response.data); // Extract specific error message from Vonage response errorMessage = error.response.data.title || error.response.data.detail || errorMessage; // Set appropriate status code based on error type if (error.response.status >= 400 && error.response.status < 500) { statusCode = 400; // Client error } } else if (error.message) { errorMessage = error.message; } res.status(statusCode).json({ success: false, message: errorMessage, errorDetails: error.response?.data }); } });POSTroute at/send-smsasyncfunction to handle the asynchronousvonage.messages.sendcall withawaitto(recipient number) andtext(message content) from the JSON request body. Checks they exist and that the message isn't too long. Returns400 Bad Requestor500 Internal Server Errorif validation failsvonage.messages.send()with:message_type: Set to"text"for standard SMStext: SMS message contentto: Recipient's phone number (should be in E.164 format, e.g.,+14155550101)from: Your Vonage virtual number (sender ID) from.envchannel: Must be"sms"message_uuid) and sends a200 OKresponse withsuccess: trueand themessage_uuid400or500response withsuccess: falseand an error message, potentially including Vonage API response detailsExample request/response:
Request:
curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{"to": "+14155550101", "text": "Hello from Vonage!"}'Success response (200):
{ "success": true, "message_uuid": "abcd1234-5678-90ef-ghij-klmnopqrstuv" }Error response (400):
{ "success": false, "message": "Missing \"to\" phone number or \"text\" message in request body." }4. Advanced Error Handling and Logging for Production
Refine error handling and logging for production readiness.
Logging Strategy
Current implementation uses
console.logfor information andconsole.errorfor failures.Production logging: Use a dedicated logging library:
Install Winston:
Configure Winston in
index.js:// index.js (near the top, after dotenv) const winston = require('winston'); const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ] }); // Log to console in development if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() })); } // Replace console.log and console.error with logger // Example: logger.info('Server started'), logger.error('Error occurred', { error })Retry Mechanisms
Vonage internal retries: Vonage handles some retries internally for transient network issues when delivering messages to carriers.
Application-level retries: Network issues can occur between your server and the Vonage API. For critical messages, implement application-level retries with exponential backoff.
Install async-retry:
Add retry logic to
/send-smsendpoint:// index.js (at the top) const retry = require('async-retry'); // ... inside app.post('/send-sms') ... try { const resp = await retry(async (bail) => { try { const result = await vonage.messages.send({ message_type: "text", text: text, to: to, from: from, channel: "sms" }); return result; } catch (error) { // Don't retry client errors (4xx) if (error.response?.status >= 400 && error.response?.status < 500) { bail(error); // Stop retrying return; } throw error; // Retry on 5xx or network errors } }, { retries: 3, // Number of retries factor: 2, // Exponential backoff factor minTimeout: 1000, // Initial timeout (1 second) maxTimeout: 5000 // Maximum timeout (5 seconds) }); logger.info('Message sent successfully', { message_uuid: resp.message_uuid }); res.status(200).json({ success: true, message_uuid: resp.message_uuid }); } catch (error) { // ... existing error handling ... }Idempotency
Prevent duplicate messages by implementing idempotency:
idempotency_keyin the request bodyExample:
// Pseudocode - requires Redis or database const idempotencyKey = req.body.idempotency_key; if (idempotencyKey) { const cachedResult = await redis.get(`idempotency:${idempotencyKey}`); if (cachedResult) { return res.status(200).json(JSON.parse(cachedResult)); } } // Send message... const resp = await vonage.messages.send({...}); // Cache result if (idempotencyKey) { await redis.setex(`idempotency:${idempotencyKey}`, 86400, JSON.stringify({ success: true, message_uuid: resp.message_uuid })); }5. Security Best Practices for SMS APIs
Implement security best practices to protect your application and Vonage account.
Input Validation
Use validation libraries for robust input checking:
Install joi:
Add validation to
/send-smsendpoint:// index.js (at the top) const Joi = require('joi'); // Define schema const smsSchema = Joi.object({ to: Joi.string().pattern(/^\+[1-9]\d{1,14}$/).required(), // E.164 format text: Joi.string().min(1).max(1600).required() }); // ... inside app.post('/send-sms') ... const { error, value } = smsSchema.validate(req.body); if (error) { return res.status(400).json({ success: false, message: error.details[0].message }); } const { to, text } = value; // ... rest of endpoint logic ...Rate Limiting
Protect your API from abuse with rate limiting:
Install express-rate-limit:
Configure rate limiting:
// index.js (near the top, after express) const rateLimit = require('express-rate-limit'); // Create rate limiter const smsLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many SMS requests from this IP. Try again after 15 minutes.', standardHeaders: true, // Return rate limit info in RateLimit-* headers legacyHeaders: false, // Disable X-RateLimit-* headers }); // Apply to SMS endpoint app.post('/send-sms', smsLimiter, async (req, res) => { // ... existing endpoint logic ... });Authentication
Protect your endpoint with API key authentication:
Generate API keys: Use a UUID generator or secure random string generator.
Store API keys: Use environment variables or a database.
Implement middleware:
// index.js const API_KEYS = new Set([ process.env.API_KEY_1, process.env.API_KEY_2 ].filter(Boolean)); // Remove undefined keys function authenticateApiKey(req, res, next) { const apiKey = req.headers['x-api-key']; if (!apiKey || !API_KEYS.has(apiKey)) { return res.status(401).json({ success: false, message: 'Invalid or missing API key' }); } next(); } // Apply to SMS endpoint app.post('/send-sms', authenticateApiKey, smsLimiter, async (req, res) => { // ... existing endpoint logic ... });Usage:
curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -H "X-API-Key: your-secret-api-key" \ -d '{"to": "+14155550101", "text": "Hello!"}'HTTPS/TLS
Development: Use HTTP locally.
Production: Always use HTTPS. Most hosting platforms (Heroku, Render, AWS) provide HTTPS by default. If self-hosting:
Helmet
Use Helmet to set security-related HTTP headers:
Install helmet:
Configure helmet:
// index.js (near the top) const helmet = require('helmet'); const app = express(); app.use(helmet()); // Apply Helmet middleware early // ... (rest of middleware) ...Security Checklist
.envin.gitignore)private.keyin.gitignore)6. Handle Special Cases
Consider edge cases relevant to SMS messaging.
Phone Number Formatting
Vonage expects numbers in E.164 format (e.g.,
+14155550100). Standardize input usinglibphonenumber-js:Install libphonenumber-js:
Validate and format phone numbers:
// index.js (at the top) const { parsePhoneNumber, isValidPhoneNumber } = require('libphonenumber-js'); // ... inside app.post('/send-sms') ... const { to, text } = req.body; // Validate and format phone number if (!isValidPhoneNumber(to)) { return res.status(400).json({ success: false, message: 'Invalid phone number format. Use E.164 format (e.g., +14155550100).' }); } const phoneNumber = parsePhoneNumber(to); const formattedTo = phoneNumber.format('E.164'); // Use formattedTo in vonage.messages.send() const resp = await vonage.messages.send({ message_type: "text", text: text, to: formattedTo, // Use formatted number from: from, channel: "sms" });Character Limits and Encoding
Vonage handles concatenation automatically. Be mindful of costs – multi-part messages consume multiple message credits.
International Sending
Sender ID requirements vary by country:
Consult Vonage's country-specific documentation before sending internationally.
Delivery Failures
Not all SMS messages get delivered. Common reasons:
Implement Status Webhooks (Delivery Receipts):
Update your Vonage Application's "Status URL" to your webhook endpoint (e.g.,
https://yourdomain.com/webhooks/status).Create the webhook endpoint:
// index.js app.post('/webhooks/status', (req, res) => { const { message_uuid, status, error_text } = req.body; logger.info('Message status update', { message_uuid, status, error_text }); // Store status in database or trigger alerts // Status values: submitted, delivered, rejected, expired, etc. res.status(200).send('OK'); });Test Numbers (Free Tier)
On Vonage trial accounts, you can only send SMS to verified test numbers. To add a test number:
Sending to unverified numbers results in a "Non-Whitelisted Destination" error.
SMS Compliance and Spam Regulations
Compliance requirements:
Implementation example:
// Maintain opt-out list (use database in production) const optOutList = new Set(); app.post('/send-sms', authenticateApiKey, smsLimiter, async (req, res) => { const { to, text } = req.body; // Check opt-out list if (optOutList.has(to)) { return res.status(400).json({ success: false, message: 'Recipient has opted out of receiving messages.' }); } // ... rest of endpoint logic ... }); // Handle opt-out webhook (inbound SMS) app.post('/webhooks/inbound', (req, res) => { const { from, text } = req.body; if (text.trim().toUpperCase() === 'STOP') { optOutList.add(from); logger.info('User opted out', { phone: from }); // Send confirmation (required by TCPA) vonage.messages.send({ message_type: "text", text: "You have been unsubscribed. Reply START to resume messages.", to: from, from: process.env.VONAGE_FROM_NUMBER, channel: "sms" }); } res.status(200).send('OK'); });7. Test Your SMS Implementation
Ensure your implementation works correctly.
Start the Server
Verify your
.envfile contains correct Vonage credentials.You should see:
Server listening on http://localhost:3000Manual Testing with curl
Replace
YOUR_RECIPIENT_NUMBERwith a valid phone number (use a verified test number if on a trial account).Test successful SMS send:
curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -H "X-API-Key: your-secret-api-key" \ -d '{ "to": "YOUR_RECIPIENT_NUMBER", "text": "Hello from my Node.js Vonage App!" }'Expected output:
Terminal (server):
Terminal (curl):
{"success":true,"message_uuid":"SOME_UUID_FROM_VONAGE"}Recipient phone receives the SMS message.
Test Error Cases
Missing parameters:
curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -H "X-API-Key: your-secret-api-key" \ -d '{"text": "Missing recipient"}' # Expected: {"success":false,"message":"Missing \"to\" phone number or \"text\" message in request body."} (Status 400)Invalid phone number:
curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -H "X-API-Key: your-secret-api-key" \ -d '{ "to": "invalid-number", "text": "Testing invalid recipient" }' # Expected: {"success":false,"message":"Invalid phone number format..."} (Status 400)Non-whitelisted number (trial account):
curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -H "X-API-Key: your-secret-api-key" \ -d '{ "to": "+15555555555", "text": "Testing non-whitelisted number" }' # Expected: {"success":false,"message":"Non-Whitelisted Destination",...} (Status 400/500)Rate limit exceeded:
# Send 101 requests rapidly for i in {1..101}; do curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -H "X-API-Key: your-secret-api-key" \ -d '{"to": "+14155550101", "text": "Test '$i'"}' done # Expected (on 101st request): {"success":false,"message":"Too many SMS requests..."} (Status 429)Automated Testing
Install Jest:
Create test file (
index.test.js):const request = require('supertest'); const express = require('express'); // Mock Vonage SDK jest.mock('@vonage/server-sdk'); const { Vonage } = require('@vonage/server-sdk'); describe('SMS API', () => { let app; let mockSend; beforeEach(() => { // Set up mock mockSend = jest.fn().mockResolvedValue({ message_uuid: 'test-uuid-123' }); Vonage.mockImplementation(() => ({ messages: { send: mockSend } })); // Set required environment variables process.env.VONAGE_APPLICATION_ID = 'test-app-id'; process.env.VONAGE_PRIVATE_KEY_PATH = './test-private.key'; process.env.VONAGE_FROM_NUMBER = '+14155550100'; process.env.API_KEY_1 = 'test-api-key'; // Load app (in real scenario, export app from index.js) app = require('./index'); }); afterEach(() => { jest.clearAllMocks(); }); test('POST /send-sms - success', async () => { const response = await request(app) .post('/send-sms') .set('X-API-Key', 'test-api-key') .send({ to: '+14155550101', text: 'Test message' }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.message_uuid).toBe('test-uuid-123'); expect(mockSend).toHaveBeenCalledWith({ message_type: 'text', text: 'Test message', to: '+14155550101', from: '+14155550100', channel: 'sms' }); }); test('POST /send-sms - missing parameters', async () => { const response = await request(app) .post('/send-sms') .set('X-API-Key', 'test-api-key') .send({ text: 'Test message' }); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.message).toContain('Missing'); }); test('POST /send-sms - invalid API key', async () => { const response = await request(app) .post('/send-sms') .set('X-API-Key', 'wrong-key') .send({ to: '+14155550101', text: 'Test message' }); expect(response.status).toBe(401); expect(response.body.success).toBe(false); }); test('POST /send-sms - Vonage API error', async () => { mockSend.mockRejectedValue({ response: { status: 400, data: { title: 'Invalid number format' } } }); const response = await request(app) .post('/send-sms') .set('X-API-Key', 'test-api-key') .send({ to: 'invalid', text: 'Test message' }); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.message).toContain('Invalid'); }); });Add test script to
package.json:{ "scripts": { "test": "jest", "start": "node index.js" } }Run tests:
npm testVerification Checklist
npm install).envfile created with correct Vonage Application ID, Private Key Path, and From Numberprivate.keyfile downloaded and placed at specified path.envandprivate.keyin.gitignorenode index.js)/healthendpoint returnsOK(Status 200)/send-smsreturns{"success":true, "message_uuid":"..."}(Status 200)8. Troubleshooting Common SMS Issues
Credentials Not Found
Error:
Credentials could not be foundor similar authentication errors.Solutions:
VONAGE_APPLICATION_IDandVONAGE_PRIVATE_KEY_PATHin.envare correctprivate.keyfile exists at the exact path specified inVONAGE_PRIVATE_KEY_PATHrelative to where you runnode index.jsprivate.keyfile content is correct (should start with-----BEGIN PRIVATE KEY-----)dotenvis loaded (require('dotenv').config();at the top ofindex.js)Non-Whitelisted Destination
Error:
Non-Whitelisted Destinationerror.Solutions:
Invalid From Number
Error:
Invalid 'From' numberor sender ID errors.Solutions:
VONAGE_FROM_NUMBERin.envis a valid number from your Vonage account in E.164 format (e.g.,+14155550100)VONAGE_APPLICATION_ID)Messages Not Received
Issue: SMS not delivered to recipient.
Solutions:
to) is correct and in E.164 formatmessage_uuidand any Vonage errors in thecatchblockRate Limits Exceeded
Error: Status 429 or rate limit messages.
Solutions:
express-rate-limit, check the limit configuration. Default is 100 requests per 15 minutes per IPSDK Version Compatibility
Issue: Unexpected behavior after updating
@vonage/server-sdk.Solutions:
package.jsonfor stability:"@vonage/server-sdk": "3.x.x"(replace with your version)Vonage Rate Limits and Quotas
Vonage imposes the following limits (as of 2024):
Check your specific limits in the Vonage Dashboard under "Settings" > "API Settings".
FAQ
Q: Can I use this with TypeScript?
A: Yes. Install
@types/expressand@types/node, renameindex.jstoindex.ts, and compile with TypeScript.Q: How do I handle inbound SMS?
A: Create a webhook endpoint (
POST /webhooks/inbound) and configure it in your Vonage Application's "Inbound URL". The endpoint receivesfrom,to,text, and other parameters.Q: Can I send MMS?
A: Yes. Change
message_typeto"image"and addimage.urlparameter. Requires MMS-capable Vonage number.Q: How do I track message delivery?
A: Implement status webhooks (see "Delivery Failures" section). Vonage sends updates to your "Status URL" endpoint.
Q: What's the cost per message?
A: Varies by destination country. US SMS typically costs $0.0075–$0.0150 per segment. Check Vonage pricing.
9. Deploy Your SMS Application to Production
Deploy your Node.js application to a production environment with proper configuration management.
Choose a Hosting Platform
Platform comparison:
Environment Variable Management
Critical: Never commit
.envorprivate.keyto version control.Platform-specific configuration:
heroku config:set)docker-compose.ymlor-eflagsFor
private.keyfile:Securely copy the file to your server during deployment (use SCP, CI/CD secure file handling, or secret managers)
Set
VONAGE_PRIVATE_KEY_PATHto the file location on the serverAlternative: Store the private key content as an environment variable and write it to a file at runtime:
// index.js const fs = require('fs'); const privateKeyPath = './private.key'; if (process.env.VONAGE_PRIVATE_KEY_CONTENT) { fs.writeFileSync(privateKeyPath, process.env.VONAGE_PRIVATE_KEY_CONTENT); } const vonage = new Vonage({ applicationId: process.env.VONAGE_APPLICATION_ID, privateKey: privateKeyPath });Deploy to Heroku
Prerequisites: Install Heroku CLI.
Steps:
Log in to Heroku:
Create Heroku app:
Add Procfile:
Create
Procfilein project root:Set config vars:
Replace placeholders with your actual values:
heroku config:set VONAGE_APPLICATION_ID=your_application_id heroku config:set VONAGE_FROM_NUMBER=+14155550100 heroku config:set API_KEY_1=your_secret_api_key # Option 1: Set private key path (requires copying file during deployment) heroku config:set VONAGE_PRIVATE_KEY_PATH=./private.key # Option 2: Store private key content as environment variable heroku config:set VONAGE_PRIVATE_KEY_CONTENT="$(cat private.key)"Commit changes:
git add . git commit -m "Add Procfile and prepare for Heroku"Deploy:
Verify deployment:
heroku logs --tail heroku openTest the
/healthendpoint:curl https://your-app-name.herokuapp.com/health # Expected: OKDeploy with Docker
Create
Dockerfile:Create
docker-compose.yml:version: '3.8' services: app: build: . ports: - "3000:3000" environment: - VONAGE_APPLICATION_ID=${VONAGE_APPLICATION_ID} - VONAGE_FROM_NUMBER=${VONAGE_FROM_NUMBER} - VONAGE_PRIVATE_KEY_PATH=/app/private.key - API_KEY_1=${API_KEY_1} - PORT=3000 volumes: - ./private.key:/app/private.key:ro restart: unless-stoppedCreate
.envfile for Docker Compose:Build and run:
Test:
CI/CD Pipeline
GitHub Actions example (
.github/workflows/deploy.yml):name: Deploy to Production on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run tests run: npm test env: VONAGE_APPLICATION_ID: ${{ secrets.VONAGE_APPLICATION_ID }} VONAGE_PRIVATE_KEY_CONTENT: ${{ secrets.VONAGE_PRIVATE_KEY_CONTENT }} VONAGE_FROM_NUMBER: ${{ secrets.VONAGE_FROM_NUMBER }} API_KEY_1: ${{ secrets.API_KEY_1 }} deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Heroku uses: akhileshns/heroku-deploy@v3.12.12 with: heroku_api_key: ${{ secrets.HEROKU_API_KEY }} heroku_app_name: "your-app-name" heroku_email: "your-email@example.com"Setup secrets in GitHub:
Go to Repository > Settings > Secrets and variables > Actions, add:
VONAGE_APPLICATION_IDVONAGE_PRIVATE_KEY_CONTENT(paste entire private key content)VONAGE_FROM_NUMBERAPI_KEY_1HEROKU_API_KEYGitLab CI example (
.gitlab-ci.yml):stages: - test - deploy test: stage: test image: node:18-alpine script: - npm ci - npm run lint - npm test variables: VONAGE_APPLICATION_ID: $VONAGE_APPLICATION_ID VONAGE_PRIVATE_KEY_CONTENT: $VONAGE_PRIVATE_KEY_CONTENT VONAGE_FROM_NUMBER: $VONAGE_FROM_NUMBER API_KEY_1: $API_KEY_1 deploy: stage: deploy image: ruby:3.1 script: - gem install dpl - dpl --provider=heroku --app=your-app-name --api-key=$HEROKU_API_KEY only: - mainRollback Strategy
Heroku rollback:
# View recent releases heroku releases # Rollback to previous release heroku rollback # Rollback to specific version heroku rollback v42Docker rollback:
# Tag images with version numbers docker build -t your-app:v1.2.3 . # Switch to previous version docker-compose down docker tag your-app:v1.2.2 your-app:latest docker-compose up -dMonitoring and Observability
Recommended tools:
Integrate Sentry for error tracking:
// index.js (at the top) const Sentry = require('@sentry/node'); Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV || 'development', tracesSampleRate: 1.0, }); // Add Sentry request handler after body parsers app.use(Sentry.Handlers.requestHandler()); // Add routes here // Add Sentry error handler before other error handlers app.use(Sentry.Handlers.errorHandler());Set up health check monitoring:
Configure your monitoring service to ping
https://your-app.com/healthevery 1–5 minutes and alert on failures.Conclusion
You've built a production-ready Node.js and Express application that sends SMS messages programmatically via the Vonage Messages API. This comprehensive guide covered:
Use this foundation to integrate SMS capabilities into your applications for notifications, OTP verification, two-factor authentication, alerts, and customer engagement.
Next steps:
Resources: