code examples
code examples
Send SMS with Node.js, Express, and Vonage
Build a production-ready Node.js application using Express framework to send SMS messages via the Vonage Messages API. Learn project setup, implementation, error handling, security, and deployment.
Send SMS with Node.js, Express, and Vonage
This guide walks you through building a production-ready Node.js application using the Express framework to send SMS messages via the Vonage Messages API. You'll learn project setup, Vonage configuration, implementation, error handling, security, and deployment.
By the end, you'll have a robust API endpoint that accepts requests and sends SMS messages programmatically.
Project Overview and Goals
What You'll Build: A Node.js Express server with a single API endpoint (/send-sms) that accepts a recipient phone number and message text, then uses the Vonage Messages API to send an SMS.
Problem Solved: Enable your applications to send SMS notifications, alerts, verification codes, or other messages to users worldwide.
Technologies Used:
- Node.js: JavaScript runtime for building server-side applications
- Express: Minimal, flexible Node.js web framework for creating the API endpoint
- Vonage Messages API: Powerful API for sending and receiving messages across SMS, MMS, WhatsApp, and other channels (we'll focus on SMS)
- @vonage/server-sdk: Official Vonage Node.js SDK for interacting with Vonage APIs
- dotenv: Module for loading environment variables from a
.envfile intoprocess.env
System Architecture:
+-------------+ +---------------------+ +----------------+ +-----------+
| User/Client | ----> | Node.js/Express API | ----> | Vonage SDK | ----> | Vonage API| ----> SMS Recipient
| (e.g. curl, | | (POST /send-sms) | | (@vonage/ | | (Messages)|
| Postman) | <---- | (Sends Response) | <---- | server-sdk) | <---- | |
+-------------+ +---------------------+ +----------------+ +-----------+Prerequisites:
- Node.js and npm (or yarn): Install on your system. Download Node.js
- Vonage API Account: Sign up for a free account to start. Sign up for Vonage
- Vonage Virtual Number: Get at least one Vonage phone number capable of sending SMS from the Vonage dashboard after signing up
- Text Editor or IDE: Use VS Code or similar
- Basic Command Line Knowledge: Familiarity with
cd,npm, etc.
(Optional but Recommended)
- Vonage CLI: Manage Vonage applications and numbers via the command line. Install with
npm install -g @vonage/cli - ngrok: Expose your local server if you plan to extend this to receive SMS messages later. Download ngrok
Expected Outcome: A running Node.js server listening on a specified port (e.g., 3000) with a POST /send-sms endpoint. Sending a request to this endpoint with a valid to phone number and text message will trigger an SMS delivery via Vonage.
1. Setting up the Project
Create the project structure and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
bashmkdir vonage-sms-sender cd vonage-sms-sender -
Initialize Node.js Project: Create a
package.jsonfile to manage dependencies and project metadata.bashnpm init -y -
Enable ES Modules: Our
index.jsexample usesimport/exportsyntax, so configure Node.js to treat.jsfiles as ES Modules. Edit yourpackage.jsonfile and add the following top-level key-value pair:json// package.json { "name": "vonage-sms-sender", "version": "1.0.0", "description": "", "main": "index.js", "type": "module", // <-- Add this line "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": {} // Dependencies will be added next }Save the
package.jsonfile. -
Install Dependencies: Install
expressfor the web server,@vonage/server-sdkto interact with the Vonage API, anddotenvto manage configuration securely.bashnpm install express @vonage/server-sdk dotenv --saveexpress: Web framework@vonage/server-sdk: Vonage's official Node.js librarydotenv: Loads environment variables from.envfile--save: Adds these packages to yourdependenciesinpackage.json
-
Create Project Files: Create the main application file and a file for environment variables.
bashtouch index.js .env .gitignore -
Configure
.gitignore: Protect sensitive information like API keys and your private key file from being committed. Add the following lines to your.gitignorefile:text# .gitignore # Dependencies node_modules # Environment variables .env *.env.* !.env.example # Vonage private key private.key # Log files *.log logs *.log.*.[0-9]*.gz # Operating system specific files .DS_Store Thumbs.db -
Project Structure: Your project should now look like this:
textvonage-sms-sender/ ├── .env ├── .gitignore ├── index.js ├── package.json ├── package-lock.json └── node_modules/
2. Integrating with Vonage
Configure your Vonage account and obtain the necessary credentials. The Messages API requires an Application ID and a private key for authentication.
-
Sign In to Vonage Dashboard: Access your Vonage API Dashboard.
-
Get API Key and Secret: Your API Key and API Secret are visible at the top of the dashboard home page. You'll need these for setting up the SDK initially and potentially for the Vonage CLI.
-
Set Default SMS API to "Messages API":
- Navigate to your account Settings in the left-hand menu.
- Scroll down to the API Settings section.
- Under Default SMS Setting, ensure Messages API is selected. If it's set to "SMS API," change it to "Messages API."
- Click Save changes.
- Why? Vonage has two SMS APIs. The SDK calls and webhook formats differ. We're using the newer, more versatile Messages API for this guide.
-
Create a Vonage Application: Applications act as containers for your communication settings and credentials.
- In the dashboard menu, go to Applications > Create a new application.
- Give your application a descriptive Name (e.g.,
My Node SMS App). - Click Generate public and private key. This will automatically download a file named
private.key. Save this file securely inside your project directory (e.g.,vonage-sms-sender/private.key). Remember, we addedprivate.keyto.gitignoreso it won't be committed. - Enable the Messages capability. Toggle the switch ON.
- You'll see fields for Inbound URL and Status URL. Even though we're only sending SMS in this guide, the Messages API Application requires these URLs to potentially send message status updates back to your application. For now, you can use dummy URLs or point them to your local server if you plan to add receiving capabilities later.
- Inbound URL:
http://localhost:3000/webhooks/inbound - Status URL:
http://localhost:3000/webhooks/status - Set the HTTP method for both to
POST. - Explanation: If you were receiving messages, the
Inbound URLwould receive the message content. TheStatus URLreceives delivery status updates (e.g.,delivered,failed).
- Inbound URL:
- Click Generate new application.
-
Get Application ID: After creating the application, you'll be taken to its configuration page. Copy the Application ID. It's a UUID (e.g.,
a1b2c3d4-e5f6-7890-abcd-ef1234567890). -
Link Your Vonage Number:
- On the same application configuration page, scroll down to the Link virtual numbers section.
- Find the Vonage number you want to send SMS from and click the Link button next to it.
-
Configure Environment Variables: Open the
.envfile you created earlier and add your Vonage credentials. Replace the placeholder values with your actual credentials.dotenv# .env # Vonage API Credentials VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Vonage Number to send SMS FROM (in E.164 format, e.g., 14155550100) VONAGE_NUMBER=YOUR_VONAGE_NUMBER # Server Configuration PORT=3000VONAGE_API_KEY,VONAGE_API_SECRET: Found on the dashboard homepage. Used by the SDK internally sometimes.VONAGE_APPLICATION_ID: Copied after creating the Vonage Application.VONAGE_PRIVATE_KEY_PATH: The relative path fromindex.jsto your downloadedprivate.keyfile../private.keyassumes it's in the same directory.VONAGE_NUMBER: The Vonage virtual number you linked to the application, which will appear as the sender ID. Use E.164 format (country code + number, no spaces or symbols).PORT: The port your Express server will listen on.
Security Note: Never commit the
.envfile to version control (like Git). Ensure*.envandprivate.keyare in your.gitignorefile. Use platform-specific environment variable management for deployment.
3. Implementing Core Functionality & API Layer
Write the Node.js code to initialize the Vonage SDK, create the Express server, and define the /send-sms endpoint.
Edit your index.js file:
// index.js
import express from 'express';
import { Vonage } from '@vonage/server-sdk';
import { Auth } from '@vonage/auth'; // Correct import for Auth
import 'dotenv/config'; // Load environment variables from .env file
// --- Initialization ---
const app = express();
const port = process.env.PORT || 3000; // Use port from .env or default to 3000
// Vonage Client Initialization
// Use Application ID and Private Key for Messages API authentication
const credentials = new Auth({
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_PRIVATE_KEY_PATH,
// apiKey and apiSecret are not directly needed for Messages API JWT auth
// but can be useful for other SDK functions or fallback.
// apiKey: process.env.VONAGE_API_KEY,
// apiSecret: process.env.VONAGE_API_SECRET
});
const vonage = new Vonage(credentials);
// --- Middleware ---
// Enable parsing of JSON request bodies
app.use(express.json());
// Enable parsing of URL-encoded request bodies
app.use(express.urlencoded({ extended: true }));
// --- API Endpoint ---
app.post('/send-sms', async (req, res) => {
console.log('Received request body:', req.body); // Log incoming request
const { to, text } = req.body;
// Basic Input Validation (More robust validation recommended - see Security section)
if (!to || !text) {
console.error('Validation Error: Missing `to` or `text` in request body');
return res.status(400).json({
success: false,
error: 'Missing required fields: `to` (recipient phone number) and `text` (message content).',
});
}
// Ensure 'from' number is correctly loaded from environment variables
const fromNumber = process.env.VONAGE_NUMBER;
if (!fromNumber) {
console.error('Configuration Error: VONAGE_NUMBER is not set in environment variables.');
return res.status(500).json({ success: false, error: 'Server configuration error: Sender number not set.' });
}
console.log(`Attempting to send SMS from ${fromNumber} to ${to}`);
try {
const resp = await vonage.messages.send({
message_type: 'text',
to: to, // Recipient phone number (E.164 format recommended)
from: fromNumber, // Your Vonage virtual number
channel: 'sms',
text: text, // The message content
});
console.log('Vonage API Response:', resp);
res.status(200).json({
success: true,
message_uuid: resp.message_uuid,
message: `SMS submitted successfully to ${to}.`,
});
} catch (error) {
console.error('Vonage API Error:', error); // Log the detailed error
// Provide more specific feedback if possible
let errorMessage = 'Failed to send SMS.';
let errorDetails = error.message; // Default details
if (error.response && error.response.data) {
errorMessage = `Vonage API Error: ${error.response.data.title || error.response.data.detail || 'Unknown error'}`;
errorDetails = error.response.data;
console.error('Vonage Error Details:', error.response.data);
}
res.status(error.response?.status || 500).json({
success: false,
error: errorMessage,
details: errorDetails, // Include details for debugging
});
}
});
// --- Basic Health Check Endpoint ---
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});
// --- Start Server ---
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});Code Explanation:
- Imports: Import
express, theVonageclass andAuthclass from the SDK, anddotenv/configto load environment variables immediately. - Initialization: Create an Express application instance and initialize the
Vonageclient using theAuthclass, passing the Application ID and the path to the private key from environment variables. - Middleware:
express.json()andexpress.urlencoded()are essential middleware to parse incoming request bodies in JSON and URL-encoded formats, respectively. /send-smsEndpoint (POST):- This is an
asyncfunction to allow usingawaitfor the asynchronousvonage.messages.sendcall. - It extracts the
to(recipient number) andtext(message body) from thereq.body. - Basic Validation: It checks if
toandtextexist in the request. See the Security section for more robust validation. - It retrieves the
fromnumber from the environment variables. vonage.messages.send({...}): This is the core SDK call.message_type: 'text': Specifies we're sending plain text.to: The recipient's phone number (E.164 format like14155550101is recommended).from: Your Vonage virtual number (loaded from.env).channel: 'sms': Specifies the communication channel.text: The content of the SMS message.
- Response Handling:
- On success (
tryblock), it logs the Vonage response and sends a200 OKJSON response back to the client, including themessage_uuidprovided by Vonage. - On failure (
catchblock), it logs the detailed error object from the Vonage SDK and sends an appropriate error status code (extracted from the error object if possible, otherwise 500) and a JSON error message.
- On success (
- This is an
/healthEndpoint (GET): A simple endpoint useful for monitoring systems to check if the server is running.app.listen: Starts the Express server, making it listen for incoming requests on the specifiedport.
Testing the Endpoint:
-
Start the Server: In your terminal, run:
bashnode index.jsYou should see
Server listening at http://localhost:3000. -
Send a Request (using
curl): Open another terminal window. ReplaceYOUR_RECIPIENT_NUMBERwith a valid phone number (including country code, e.g.,12125551234). Note: If you're using a Vonage trial account, this number must be added to your allowed list in the dashboard (Settings > Test Numbers).bashcurl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_RECIPIENT_NUMBER", "text": "Hello from Node.js and Vonage!" }' -
Send a Request (using Postman):
- Open Postman.
- Set the request type to
POST. - Enter the URL:
http://localhost:3000/send-sms - Go to the
Bodytab, selectraw, and chooseJSONfrom the dropdown. - Enter the JSON payload:
json
{ "to": "YOUR_RECIPIENT_NUMBER", "text": "Hello from Postman, Node.js, and Vonage!" } - Click
Send.
Expected Responses:
-
Success (200 OK):
json{ "success": true, "message_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", // Example UUID "message": "SMS submitted successfully to YOUR_RECIPIENT_NUMBER." }You should receive the SMS on the recipient phone shortly after.
-
Validation Error (400 Bad Request):
json{ "success": false, "error": "Missing required fields: `to` (recipient phone number) and `text` (message content)." } -
Vonage API Error (e.g., 401 Unauthorized if credentials are wrong):
json{ "success": false, "error": "Vonage API Error: Unauthorized", "details": { "type": "https://developer.nexmo.com/api-errors/messages-olympus#unauthorized", "title": "Unauthorized", "detail": "You did not provide valid credentials.", "instance": "bf0ca710-55e8-48a6-b0e6-a9fbd5b2f22a" // Example instance ID } }
4. Implementing Proper Error Handling and Logging
While the basic try...catch block handles errors, production applications need more robust strategies.
-
Consistent Error Format: We already implemented sending back a consistent JSON error object (
{ success: false, error: '...', details: '...' }). Stick to this format. -
Specific Error Handling: You could add checks within the
catchblock for specific Vonage error codes (e.g.,error.response?.data?.title === 'Insufficient Balance') to provide more tailored user feedback or trigger alerts. -
Logging: The current
console.logandconsole.errorare basic. For production, use a dedicated logging library like Winston or Pino. These enable:- Different log levels (debug, info, warn, error).
- Structured logging (JSON format is common for easier parsing by log analysis tools).
- Outputting logs to files, databases, or external logging services (like Datadog, Logstash, Splunk).
Example (Conceptual Winston Setup):
javascript// Conceptual - requires npm install winston import winston from 'winston'; const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', // Log level from env or default to info format: winston.format.combine( winston.format.timestamp(), winston.format.json() // Log in JSON format ), defaultMeta: { service: 'sms-sender-service' }, transports: [ // Write all logs with level `error` and below to `error.log` new winston.transports.File({ filename: 'error.log', level: 'error' }), // Write all logs with level `info` and below to `combined.log` new winston.transports.File({ filename: 'combined.log' }), ], exceptionHandlers: [ // Optional: Log unhandled exceptions new winston.transports.File({ filename: 'exceptions.log' }) ], rejectionHandlers: [ // Optional: Log unhandled promise rejections new winston.transports.File({ filename: 'rejections.log' }) ] }); // If we're not in production then log to the `console` with colors if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), })); } // Replace console.log/error with logger calls: // logger.info(`Server listening at http://localhost:${port}`); // logger.error('Vonage API Error', { // message: error.message, // status: error.response?.status, // details: error.response?.data // }); -
Retry Mechanisms: For transient network errors or temporary Vonage issues, you might implement a retry strategy (e.g., using libraries like
async-retry). Use exponential backoff to avoid overwhelming the API. However, be cautious retrying SMS sends, as you could accidentally send duplicate messages if the initial request did succeed but the response failed. Retries are often better suited for status checks or configuration tasks.
5. Adding Security Features
Protecting your API endpoint is crucial.
-
Input Validation and Sanitization: Never trust user input. The basic check in
index.jsis insufficient. Use a dedicated validation library like Joi or express-validator to enforce:to: Must be a string, potentially matching a phone number pattern (E.164).text: Must be a non-empty string, perhaps with a maximum length limit (to control costs and prevent abuse).
Example (Conceptual
express-validator):javascript// Conceptual - requires npm install express-validator import { body, validationResult } from 'express-validator'; // Add this middleware array before your route handler const validateSmsRequest = [ body('to') .trim() .notEmpty().withMessage('Recipient `to` number is required.') .isMobilePhone('any', { strictMode: false }).withMessage('Invalid phone number format for `to`. E.164 format recommended.'), // Basic phone check body('text') .trim() .notEmpty().withMessage('Message `text` is required.') .isLength({ max: 1600 }).withMessage('Message text exceeds maximum length (1600 characters).'), // SMS limit (generous) ]; // In your route definition: app.post('/send-sms', validateSmsRequest, async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { // Log validation errors for debugging console.error('Validation Errors:', errors.array()); return res.status(400).json({ success: false, errors: errors.array() }); } // ... rest of your existing route logic ... }); -
Rate Limiting: Prevent abuse (accidental or malicious) by limiting how many requests a client can make in a given time window. Use a library like express-rate-limit.
Example (Basic Rate Limiting):
javascript// Conceptual - requires npm install express-rate-limit import rateLimit from 'express-rate-limit'; const smsLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per `windowMs` message: { success: false, error: 'Too many SMS requests from this IP, please try again after 15 minutes.' }, standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers // store: // RedisStore, MemcachedStore, etc. for distributed environments }); // Apply the rate limiting middleware specifically to the SMS endpoint app.use('/send-sms', smsLimiter); // Define your endpoint *after* applying the limiter app.post('/send-sms', /* validation middleware, */ async (req, res) => { /* ... */ }); -
API Key / Authentication: For internal or protected APIs, you would typically require an API key or other authentication mechanism (like JWT tokens) on the
/send-smsendpoint itself, not just rely on Vonage credentials. This prevents unauthorized parties from using your endpoint to send SMS messages via your Vonage account. This is beyond the scope of this basic guide but essential for production. Common approaches include checking for a specific header (X-API-Key) or using middleware like Passport.js for more complex strategies. -
Secure Credential Management: Reiterate: Use environment variables (
.envlocally, platform-specific variables in deployment). Never hardcode credentials or commit.envorprivate.key. Consider using secrets management tools for production environments (e.g., AWS Secrets Manager, HashiCorp Vault, Google Secret Manager).
6. Database Schema and Data Layer
This specific example does not require a database as it only sends outgoing messages based on immediate API requests.
If you were building features like scheduled messages, storing message history, or managing user preferences, you would need to:
- Choose a database (e.g., PostgreSQL, MongoDB, MySQL).
- Design a schema (e.g., tables/collections for
messages,users,schedules). Amessagestable might include columns likemessage_uuid(from Vonage),recipient_number,sender_number,message_text,status(e.g., submitted, delivered, failed),vonage_response,created_at,updated_at. - Use an ORM (like Prisma, Sequelize, TypeORM) or a database driver (like
pgfor PostgreSQL,mysql2for MySQL,mongodbfor MongoDB) to interact with the database. - Implement data access logic (creating, reading, updating, deleting records) within your service layer or dedicated data access modules.
- Manage database migrations to handle schema changes over time (tools like Prisma Migrate, Sequelize CLI, TypeORM migrations).
This is out of scope for the current guide.
7. Handling Special Cases
- Trial Account Limitations: Vonage trial accounts can typically only send SMS messages to phone numbers that have been verified and added to the ""Test Numbers"" list in the Vonage dashboard (Settings > Test Numbers). Ensure your
tonumber is on this list during development if using a trial account. Attempting to send to other numbers will result in an error (often related to whitelisting or permissions). - Sender ID: In some countries, the
fromnumber might be replaced by a generic ID (like ""InfoSMS"") or an Alphanumeric Sender ID if you have one registered and approved with Vonage. Behavior varies significantly by country and carrier regulations. Check Vonage documentation for country-specific sender ID rules. Using an unregistered Alphanumeric Sender ID where required might cause messages to fail. - Character Limits and Encoding: A standard SMS segment has 160 characters (using GSM-7 encoding). Longer messages, or messages with non-GSM characters (like emojis, certain accented characters, requiring Unicode/UCS-2 encoding), will be split into multiple segments. Each UCS-2 segment holds fewer characters (typically 70). Vonage charges per segment. The Messages API handles this segmentation automatically, but be mindful of the
textlength and character set to predict and manage costs. - Delivery Status: This guide doesn't handle status updates (DLRs - Delivery Receipts). To track if a message was actually delivered, you would need to:
- Ensure your Vonage Application's
Status URLpoints to a valid, publicly accessible endpoint on your server (using ngrok locally or your deployed URL). - Create an endpoint (e.g.,
POST /webhooks/status) to receive status updates (likesubmitted,delivered,failed,rejected,accepted) from Vonage via HTTP POST requests. - Implement logic in this endpoint to parse the incoming JSON payload from Vonage, identify the corresponding message (using the
message_uuid), and update its status (e.g., log it, update a database record). - Secure this webhook endpoint (e.g., by verifying Vonage signatures using JWT).
- Ensure your Vonage Application's
- International Formatting: Always aim to use the E.164 format for phone numbers (
+followed by country code and number, without spaces or symbols, e.g.,+447700900000,+14155550101) for bothtoandfromto ensure reliable international delivery and proper routing. The SDK and API might tolerate other formats, but E.164 is the most robust standard.
8. Performance Optimizations
For this simple endpoint, performance is unlikely to be a major issue unless under very high load. Key considerations:
- SDK Initialization: Initialize the Vonage SDK (
new Vonage(credentials)) once when your application starts, not inside the request handler. Our current code already does this correctly. Recreating the client on every request adds unnecessary overhead (including reading the private key file repeatedly). - Asynchronous Operations: The Vonage SDK methods are asynchronous (
async/await). Node.js handles this efficiently without blocking the event loop. Ensure all I/O operations (like potential database calls if added later, or complex logging transports) are also handled asynchronously using Promises orasync/await. - Payload Size: Keep request and response JSON payloads reasonably small. Avoid sending excessively large amounts of data back and forth if not necessary.
- Connection Pooling: If interacting with a database, ensure you are using connection pooling to reuse database connections efficiently, rather than opening/closing a connection for every request. ORMs typically handle this automatically.
- Caching: If certain data is frequently requested and doesn't change often (e.g., configuration settings, user permissions), consider caching it in memory (e.g., using a simple object or
Map) or using an external cache like Redis to reduce load on downstream services or databases.
9. Monitoring, Observability, and Analytics
For production systems:
- Health Checks: The
/healthendpoint is a basic start. More advanced checks could verify connectivity to Vonage (e.g., by making a low-impact API call like fetching account balance) or other critical dependencies (like a database). Kubernetes and other orchestrators use health checks for managing container lifecycles. - Metrics: Track key application and business metrics using libraries like
prom-client(for Prometheus) or platform-specific agents:- Request rate and latency for
/send-smsand/health. - Error rates (HTTP 4xx, 5xx) per endpoint.
- Node.js process metrics (CPU usage, memory usage, event loop lag).
- Vonage API success/error counts (by parsing responses or processing status webhooks).
- Number of SMS messages sent/failed per time period.
- Request rate and latency for
- Logging: As mentioned in Error Handling, use structured logging (JSON) and centralize logs using services like Elasticsearch/Logstash/Kibana (ELK stack), Splunk, Datadog Logs, Grafana Loki, or cloud provider logging services (AWS CloudWatch Logs, Google Cloud Logging). This allows for searching, filtering, and analyzing logs effectively.
- Distributed Tracing: For more complex systems involving multiple microservices, implement distributed tracing (e.g., using OpenTelemetry with Jaeger or Zipkin) to track requests as they flow through different services, helping pinpoint bottlenecks and errors.
- Error Tracking: Use services like Sentry, Bugsnag, or Rollbar to capture, aggregate, and alert on application errors (unhandled exceptions, promise rejections, logged errors) in real-time, providing stack traces and context.
- Monitoring Tools: Integrate with monitoring platforms (Datadog, Prometheus/Grafana, New Relic, Dynatrace) to visualize metrics, set up dashboards, and create alerts based on thresholds or anomalies (e.g., alert if the 5xx error rate exceeds 1%, or if Vonage API latency spikes above 500ms).
10. Troubleshooting and Caveats
401 Unauthorized: Double-checkVONAGE_APPLICATION_IDandVONAGE_PRIVATE_KEY_PATHin your.envfile or environment variables. Ensure theprivate.keyfile exists at the specified path relative to where you runnode index.jsand is readable by the Node.js process. Verify the Application ID matches the one in the Vonage dashboard exactly. Ensure the private key content is correct if reading from an environment variable.Non-Whitelisted Destination/Illegal Sender Address(Trial Account): If using a trial account, ensure thetonumber is verified and added to your Vonage dashboard under Settings > Test Numbers. Also, ensure thefromnumber (VONAGE_NUMBER) is correctly linked to your Vonage Application in the dashboard.Invalid Parameters/400 Bad Requestfrom Vonage: Check the format of thetoandfromnumbers (E.164 recommended:+14155550101). Ensuretextis not empty or excessively long. Consult the Vonage Messages API reference for required fields and valid values formessage_type,channel, etc. Check your server logs for the detailed error response from Vonage.- Server Error
500: Check your server logs (console.erroroutput or dedicated log files/services) for detailed stack traces. Common causes include:- Incorrect SDK initialization (e.g., missing credentials).
- Missing or incorrect environment variables (like
VONAGE_NUMBERnot being set). - File system errors (e.g., cannot read
private.key). - Unhandled exceptions in your code logic.
- Network connectivity issues between your server and Vonage.
Cannot find module '@vonage/server-sdk'or other modules: Runnpm installagain to ensure all dependencies listed inpackage.jsonare installed in thenode_modulesdirectory. Check for typos inimportstatements.- Syntax Errors (e.g.,
SyntaxError: Cannot use import statement outside a module): Ensure your Node.js version supports ES Modules (v14+ recommended). Verify that""type"": ""module""is correctly added to the top level of yourpackage.json. If using CommonJS (require), ensure you are using the correct import syntax (const { Vonage } = require('@vonage/server-sdk');) and remove""type"": ""module"". - Private Key Permissions: On Linux/macOS systems, ensure the
private.keyfile has the correct read permissions for the user running the Node.js process (e.g.,chmod 600 private.key). - Check Vonage Dashboard: The Vonage Dashboard (under Applications > Your Application > Logs, or the global API Logs section) often provides valuable insight into API requests and reasons for failures directly from the Vonage platform.
11. Deployment and CI/CD
-
Environment Variables: Crucially, do not deploy your
.envfile orprivate.keyfile directly. Use your hosting provider's mechanism for setting environment variables (e.g., Heroku Config Vars, AWS Systems Manager Parameter Store / Secrets Manager, Google Secret Manager, Vercel Environment Variables, Docker environment variables). You will need to securely setVONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_ID,VONAGE_NUMBER, andPORT. For the private key, the best practice is to store the content of the private key in a secure environment variable (e.g.,VONAGE_PRIVATE_KEY_CONTENT) and modify the SDK initialization to read it from there, rather than relying on a file path.Example reading key from environment variable:
javascript// In index.js, modify the credentials setup: const privateKeyContent = process.env.VONAGE_PRIVATE_KEY_CONTENT; if (!privateKeyContent) { throw new Error('VONAGE_PRIVATE_KEY_CONTENT environment variable not set.'); } const credentials = new Auth({ applicationId: process.env.VONAGE_APPLICATION_ID, // Read key content directly from an environment variable // Replace escaped newlines if necessary (common issue with multi-line env vars) privateKey: privateKeyContent.replace(/\\n/g, '\n'), // apiKey: process.env.VONAGE_API_KEY, // Keep if needed // apiSecret: process.env.VONAGE_API_SECRET // Keep if needed }); // Ensure the VONAGE_PRIVATE_KEY_CONTENT environment variable is set // in your deployment environment, containing the full multi-line text // of the private key (-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----). -
.gitignore: Ensurenode_modules,.env,*.env.*(except maybe.env.example),private.key, log files (*.log,logs/), and OS-specific files (.DS_Store) are listed in your.gitignorefile before your first commit. -
Build Step: If using TypeScript or a bundler (like Webpack, esbuild), ensure you have a build script in your
package.json(""build"": ""tsc""or similar) and run this step as part of your deployment process to generate the JavaScript code that will be executed. Deploy the built artifacts (e.g., adistfolder), not the source code directly. -
Starting the Server: Use a process manager like PM2 (
pm2 start index.js --name sms-app) or rely on your hosting platform's mechanism (e.g., Heroku Procfileweb: node index.js, DockerCMD [""node"", ""index.js""], systemd service) to runnode index.js. Process managers handle restarting the application if it crashes and can manage clustering for better performance. -
CI/CD Pipeline: Set up a pipeline using tools like GitHub Actions, GitLab CI, Jenkins, CircleCI, or Bitbucket Pipelines to automate:
- Linting/Formatting: Run ESLint, Prettier.
- Testing: Run unit tests, integration tests.
- Building: Compile TypeScript, bundle assets (if applicable).
- Security Scanning: Scan dependencies for vulnerabilities (e.g.,
npm audit), check code for security issues. - Deploying: Push the built application to your hosting environment (e.g., Heroku, AWS Elastic Beanstalk, Vercel, Docker registry).
12. Verification and Testing
-
Manual Verification:
- Deploy the application to a staging or production environment.
- Configure all required environment variables securely on the host.
- Send a
POSTrequest to the deployed/send-smsendpoint usingcurl, Postman, or another HTTP client. Use a valid recipient number (whitelisted if needed for trial accounts). - Verify you receive a
200 OKresponse with asuccess: trueand amessage_uuid. - Check the recipient phone for the actual SMS message delivery. Note potential delays.
- Check the Vonage dashboard logs (API Logs or Messages API Logs) for the message status (
submitted,delivered, etc.). - Test error cases: send requests with missing
toortext, invalid phone number formats, incorrect authentication (if implemented), etc., and verify you receive appropriate error responses (e.g., 400, 401, 403) withsuccess: false.
-
Automated Testing:
-
Unit Tests: Use a framework like Jest or Mocha with Chai/Sinon to test individual functions or modules in isolation. You would mock the
@vonage/server-sdkto avoid making real API calls during tests and to assert that the SDK methods are called with the correct parameters.Example (Conceptual Jest Unit Test for the route handler):
javascript// Conceptual - requires npm install --save-dev jest @types/jest // __tests__/smsRoute.test.js (assuming route logic is in a testable function) // Mock the Vonage SDK const mockSend = jest.fn(); jest.mock('@vonage/server-sdk', () => ({ Vonage: jest.fn().mockImplementation(() => ({ messages: { send: mockSend, }, })), })); jest.mock('@vonage/auth', () => ({ // Mock Auth as well Auth: jest.fn().mockImplementation(() => ({})), })); // Import your app or route handler function after mocks // const { handleSendSms } = require('../src/smsHandler'); // Example structure describe('POST /send-sms handler', () => { beforeEach(() => { // Reset mocks before each test mockSend.mockClear(); process.env.VONAGE_NUMBER = '15551234567'; // Set necessary env var }); test('should send SMS successfully with valid input', async () => { const mockReq = { body: { to: '15559876543', text: 'Test message' }, }; const mockRes = { // Mock Express response object status: jest.fn().mockReturnThis(), json: jest.fn(), }; mockSend.mockResolvedValue({ message_uuid: 'test-uuid' }); // Mock successful API call // await handleSendSms(mockReq, mockRes); // Call the handler // expect(mockSend).toHaveBeenCalledWith({ // message_type: 'text', // to: '15559876543', // from: '15551234567', // channel: 'sms', // text: 'Test message', // }); // expect(mockRes.status).toHaveBeenCalledWith(200); // expect(mockRes.json).toHaveBeenCalledWith({ // success: true, // message_uuid: 'test-uuid', // message: expect.stringContaining('SMS submitted successfully'), // }); }); test('should return 400 if `to` is missing', async () => { // ... test case for missing 'to' ... // expect(mockSend).not.toHaveBeenCalled(); // expect(mockRes.status).toHaveBeenCalledWith(400); // expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({ success: false })); }); // ... more unit tests for other scenarios (missing text, API error) ... }); -
Integration Tests: Test the interaction between your API endpoint and the (mocked or real, in specific environments) Vonage service. You might use libraries like
supertestto make HTTP requests to your running Express application during tests and assert the responses. -
End-to-End (E2E) Tests: These tests simulate real user scenarios, making actual API calls to your deployed application (potentially in a staging environment) and verifying the outcome, including checking for the received SMS (which is harder to automate fully). Use tools like Cypress or Playwright if you have a frontend interacting with this API. For API-only E2E, tools like Postman (with Newman CLI) or custom scripts using HTTP clients can be used. Be mindful of costs and rate limits when running E2E tests that make real API calls.
-
Frequently Asked Questions
how to send sms with node.js and express
Use the Vonage Messages API with the @vonage/server-sdk and an Express.js server. Create a /send-sms endpoint that takes the recipient's number and message text, then uses the SDK to send the SMS via the Vonage API. This setup allows your Node.js application to programmatically send SMS messages.
what is the vonage messages api
The Vonage Messages API is a service that allows you to send and receive messages across multiple channels, including SMS, MMS, WhatsApp, and more. It provides a flexible and powerful way to integrate messaging into your applications using various programming languages, including Node.js.
why use dotenv with vonage and express
Dotenv is used for managing environment variables securely. It loads variables from a .env file into process.env, preventing sensitive information like API keys and secrets from being hardcoded in your application and accidentally exposed in version control.
when to use ngrok with vonage sms api
Ngrok is useful when developing locally and you need to expose your local server to the public internet. This is particularly important if you want to receive SMS messages or status updates via webhooks, as Vonage needs a publicly accessible URL to send these requests to.
how to create a vonage application for sms
Log into your Vonage Dashboard, navigate to Applications, and click "Create a new application." Give your application a name, generate public and private keys (store the private key securely), enable the Messages capability, set the Inbound and Status URLs for webhooks, and click "Generate new application." This creates the application and provides you with an Application ID.
what is vonage application id used for
The Vonage Application ID is a unique identifier for your Vonage application. Along with your private key, it is used to authenticate your application with the Vonage Messages API and other Vonage services. This ID is required when initializing the Vonage SDK.
how to link a vonage virtual number
After creating a Vonage Application, go to its configuration page in the dashboard. In the "Link virtual numbers" section, find the number you want to send SMS messages from and click "Link." This associates your Vonage number with your application so you can send messages from it using the Vonage API.
what is vonage private key path
The Vonage Private Key Path specifies the location of your downloaded private.key file on your server. It's used by the @vonage/server-sdk for authentication with the Vonage API and should be stored securely. Never expose this file in version control systems like Git.
how to handle vonage sms api errors
Use a try...catch block around the vonage.messages.send() call to catch potential errors. Provide specific error responses to the user, log detailed error information (including the Vonage API's error response if available) for debugging, and consider implementing retry mechanisms with exponential backoff for transient errors. Use structured JSON for errors where possible.
how to secure node.js vonage sms api
Implement robust input validation using libraries like Joi or express-validator, sanitize user input to prevent injection attacks, use rate limiting to avoid abuse, and consider API key authentication or JWT for internal or protected endpoints. Securely manage Vonage credentials using environment variables and avoid hardcoding them.
how to set up error logging for node.js vonage sms
Use a dedicated logging library like Winston or Pino. Configure different log levels, format logs in JSON for easier parsing and analysis, and output logs to files or external logging services. Also, log unhandled exceptions and promise rejections for complete error tracking.
what are vonage sms character limits
Standard SMS messages are limited to 160 characters when using GSM-7 encoding. Messages with non-GSM characters (emojis, some accented characters) use UCS-2 encoding, and each segment holds around 70 characters. Vonage charges per segment, so long messages are split into multiple segments.
how to check vonage sms delivery status
Set a valid Status URL in your Vonage Application configuration. This URL should point to an endpoint in your application (e.g., /webhooks/status) that can receive delivery receipts (DLRs) from Vonage. Implement logic to process these status updates, typically updating a database or logging the status. Secure the webhook endpoint.
why is my vonage sms not sending trial account
Trial accounts often have restrictions on sending to unverified numbers. Add the recipient's phone number to your allowed list in the Vonage Dashboard under Settings > Test Numbers. Also verify your 'from' number is linked and the default SMS API is set to Messages API, not SMS API.