code examples
code examples
Send SMS and Receive Delivery Status with Node.js, Express, and Vonage
A guide to building a Node.js/Express application using the Vonage Messages API to send SMS and receive delivery status updates via webhooks.
This guide provides a comprehensive walkthrough for building a Node.js application using the Express framework to send SMS messages via the Vonage Messages API and reliably receive delivery status updates through webhooks. Understanding message delivery status is crucial for applications requiring confirmation that messages have reached the recipient's handset.
Project Goals:
- Send SMS messages programmatically using the Vonage Node.js SDK.
- Set up a webhook endpoint to receive real-time delivery receipt (DLR) callbacks from Vonage.
- Securely manage API credentials and configuration.
- Implement basic error handling and logging.
- Provide a robust foundation for building production-ready SMS features.
Problem Solved:
While the Vonage API confirms successful submission of an SMS request almost instantly, this doesn't guarantee delivery to the end user's device. Network issues, carrier limitations, or invalid numbers can prevent delivery. This guide implements the webhook mechanism needed to get asynchronous confirmation (or failure notifications) from the mobile carrier network via Vonage.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express: A minimal and flexible Node.js web application framework used to create the API endpoint and webhook listener.
- Vonage Messages API: A powerful Vonage API for sending and receiving messages across various channels, including SMS. We'll use it for sending SMS and receiving status updates.
- Vonage Node.js SDK (
@vonage/server-sdk): Simplifies interaction with Vonage APIs within a Node.js environment. - ngrok: A utility to expose local development servers to the internet, essential for testing webhooks.
- dotenv: A module to load environment variables from a
.envfile intoprocess.env.
System Architecture:
+-------------+ +-----------------+ +-------------+ +---------+
| Your Client | -----> | Node.js/Express | -----> | Vonage API | -----> | Carrier |
| (e.g., UI, | | App (Sends SMS) | | (Messages) | | Network |
| script) | +-----------------+ +-------------+ +----+----+
+-------------+ ^ |
| | (SMS Delivered/Failed)
| (Receives DLR) v
| +----+----+
+---------------------+ +-------------+
| Webhook POST | <----- | Vonage API |
+---------------+ | (Forwards DLR)|
+-------------+Final Outcome:
By the end of this guide, you will have a running Node.js Express application capable of:
- Accepting API requests to send SMS messages.
- Receiving delivery status updates on a dedicated webhook endpoint.
- Logging relevant information for sending and status updates.
Prerequisites:
- Vonage API Account: Sign up for free at the Vonage API Dashboard. Note your API Key and API Secret.
- Vonage Application and Private Key: You'll create this during the setup.
- Vonage Virtual Number: Purchase an SMS-capable number in your Vonage Dashboard.
- Node.js and npm: Installed on your system (LTS version recommended). Download Node.js
- ngrok: Installed and authenticated. Download ngrok
- Basic understanding: Familiarity with Node.js, Express, APIs, and terminal commands.
1. Setting up the Project
Let's create the project structure, install dependencies, and configure the environment.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
bashmkdir vonage-sms-dlr-guide cd vonage-sms-dlr-guide -
Initialize Node.js Project: Initialize the project using npm, accepting the defaults.
bashnpm init -yThis creates a
package.jsonfile. -
Install Dependencies: Install Express for the web server, the Vonage Server SDK, and
dotenvfor environment variable management.bashnpm install express @vonage/server-sdk dotenv --saveexpress: Web framework.@vonage/server-sdk: Official Vonage SDK for Node.js.dotenv: Loads environment variables from.env.
-
Create Project Structure: Create a basic structure for clarity.
bashmkdir src touch src/server.js touch .env touch .gitignoresrc/server.js: Main application code..env: Stores sensitive credentials and configuration (DO NOT commit to Git)..gitignore: Specifies intentionally untracked files that Git should ignore.
-
Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing them. Also explicitly ignore private key files.plaintext# .gitignore node_modules/ .env *.key # Also ignore private key files if stored directly -
Set Up Environment Variables (
.env): Open the.envfile and add the following variables. You'll obtain these values in the next section. Replace placeholders later.dotenv# .env # Vonage API Credentials (from Vonage Dashboard -> API Settings) VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Details (created in Vonage Dashboard -> Applications) VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root # Vonage Number (from Vonage Dashboard -> Numbers) VONAGE_NUMBER=YOUR_VONAGE_NUMBER # Application Configuration APP_PORT=3000 # Port the local server will run on # BASE_URL will be replaced by the ngrok URL for Vonage webhook configuration BASE_URL=http://localhost:3000 # Initial placeholderExplanation:
VONAGE_API_KEY,VONAGE_API_SECRET: Authenticate basic API requests. Found on your Vonage Dashboard homepage.VONAGE_APPLICATION_ID,VONAGE_PRIVATE_KEY_PATH: Used by the Messages API for authentication. You'll generate these. The path points to where you'll save the private key file. Security Warning: While.gitignoreprevents committing the key file during development, storing private keys directly in the project structure is not recommended for production. Use secure secrets management practices, such as environment variables injected by your hosting platform or a dedicated secrets manager.VONAGE_NUMBER: The Vonage virtual number used to send the SMS. Must include the country code (e.g.,12015550123).APP_PORT: The port your local Express server listens on.BASE_URL: The publicly accessible URL for your application. The initiallocalhostvalue is a placeholder; it will be replaced by the public URL provided by ngrok, which is essential for Vonage to send webhooks to your local server during development.
2. Configuring Vonage
Now, let's configure the necessary components in your Vonage account.
-
Get API Key and Secret:
- Log in to your Vonage API Dashboard.
- On the main page, you'll find your
API keyandAPI secret. - Copy these values and paste them into your
.envfile forVONAGE_API_KEYandVONAGE_API_SECRET.
-
Set Default SMS API (Important):
- Navigate to Account Settings in the Vonage Dashboard.
- Scroll down to
API settings->Default SMS Setting. - Ensure
Messages APIis selected as the default. This is critical; it ensures delivery receipts for messages sent via the Messages API are routed to the webhook URL configured in your Vonage Application. If the olderSMS APIis selected, DLRs might not arrive at your expected endpoint. - Click
Save changes.
-
Create a Vonage Application: The Messages API requires a Vonage Application for authentication and webhook configuration.
- In the Vonage Dashboard, navigate to
Applications->Create a new application. - Name: Give your application a descriptive name (e.g.,
Node SMS DLR Guide App). - Generate Public and Private Key: Click this button. Your browser will automatically download a
private.keyfile. Save this file in the root directory of your project (the same level aspackage.jsonand.env). EnsureVONAGE_PRIVATE_KEY_PATHin your.envfile points to this location (./private.key). Vonage stores the public key. - Capabilities: Enable the
Messagescapability. - Configure Webhooks:
- Status URL: Enter
YOUR_BASE_URL/webhooks/status. ReplaceYOUR_BASE_URLwith a placeholder for now, likehttp://example.com/webhooks/status. We will update this later with the actual ngrok URL. This is the endpoint Vonage will send delivery receipts to. - Inbound URL: Enter
YOUR_BASE_URL/webhooks/inbound. This endpoint would receive incoming SMS replies (not the focus of this guide, but required by the Application setup). Use the same placeholder base URL.
- Status URL: Enter
- Click
Generate new application. - You'll be taken to the application details page. Copy the Application ID displayed near the top.
- Paste this ID into your
.envfile forVONAGE_APPLICATION_ID.
- In the Vonage Dashboard, navigate to
-
Link Your Vonage Number:
- On the same application details page, scroll down to the
Linked numberssection. - Find your purchased Vonage virtual number in the dropdown or list.
- Click the
Linkbutton next to it. This associates incoming messages and status updates for this number with your application's webhooks. - Copy this linked Vonage number (including country code) and paste it into your
.envfile forVONAGE_NUMBER.
- On the same application details page, scroll down to the
3. Implementing the Express Server
Let's write the code for our Express server to handle API requests and incoming webhooks.
Open src/server.js and add the following code:
// src/server.js
require('dotenv').config(); // Load environment variables from .env file
const express = require('express');
const { Vonage } = require('@vonage/server-sdk');
const path = require('path'); // Import path module for private key
const app = express();
// --- Middleware ---
// Use built-in middleware for parsing JSON and URL-encoded bodies
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// --- Vonage Setup ---
// Validate essential environment variables
const requiredEnv = [
'VONAGE_API_KEY', 'VONAGE_API_SECRET', 'VONAGE_APPLICATION_ID',
'VONAGE_PRIVATE_KEY_PATH', 'VONAGE_NUMBER', 'APP_PORT'
];
const missingEnv = requiredEnv.filter(envVar => !process.env[envVar]);
if (missingEnv.length > 0) {
console.error(`Error: Missing required environment variables: ${missingEnv.join(', ')}`);
console.error('Please check your .env file.');
process.exit(1); // Exit if essential config is missing
}
// Resolve the private key path relative to the project root
// path.resolve ensures the correct absolute path regardless of where the script is run from
const privateKeyPath = path.resolve(process.cwd(), process.env.VONAGE_PRIVATE_KEY_PATH);
// Initialize Vonage SDK
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: privateKeyPath // Use the resolved absolute path
});
// --- Routes ---
// Basic health check route
app.get('/ping', (req, res) => {
res.status(200).send('pong');
});
// Route to send an SMS message
app.post('/send-sms', async (req, res) => {
const { to, text } = req.body;
// Basic input validation
if (!to || !text) {
return res.status(400).json({ error: 'Missing `to` or `text` in request body.' });
}
// Validate phone number format (allow optional leading '+' for E.164)
if (!/^\+?\d+$/.test(to)) {
return res.status(400).json({ error: '`to` number must contain only digits, optionally preceded by a `+` (e.g., +14155550100 or 14155550100).' });
}
console.log(`Attempting to send SMS from ${process.env.VONAGE_NUMBER} to ${to}`);
try {
const resp = await vonage.messages.send({
message_type: 'text',
text: text,
to: to, // Destination number
from: process.env.VONAGE_NUMBER, // Your Vonage number
channel: 'sms'
});
console.log('Message submitted successfully:', resp);
// Success: Message UUID is returned if the request was accepted by Vonage
res.status(200).json({
message: 'SMS submitted successfully!',
message_uuid: resp.message_uuid
});
} catch (err) {
console.error('Error sending SMS:', err);
// Provide more specific feedback if possible
let statusCode = 500;
let errorMessage = 'Failed to send SMS due to an internal server error.';
if (err.response && err.response.data) {
console.error('Vonage API Error Details:', err.response.data);
errorMessage = `Vonage API Error: ${err.response.data.title || 'Unknown error'}. ${err.response.data.detail || ''}`;
// Check for common client errors
if (err.response.status >= 400 && err.response.status < 500) {
statusCode = err.response.status;
}
} else if (err.message) {
errorMessage = `Error: ${err.message}`;
}
res.status(statusCode).json({ error: errorMessage });
}
});
// Webhook endpoint for Delivery Receipts (DLRs)
app.post('/webhooks/status', (req, res) => {
const params = req.body;
console.log('--- Delivery Receipt Received ---');
console.log('Status:', params.status);
console.log('Message UUID:', params.message_uuid);
console.log('To:', params.to);
console.log('Timestamp:', params.timestamp); // Example: 2023-10-27T12:00:00.000Z
if (params.error) {
console.error('Error Code:', params.error.code);
console.error('Error Reason:', params.error.reason);
}
console.log('---------------------------------');
console.log('Full DLR Payload:', JSON.stringify(params, null, 2)); // Log the full payload for inspection
// Acknowledge receipt to Vonage
// IMPORTANT: Vonage expects a 200 OK response quickly, otherwise it will retry.
res.status(200).send('OK');
});
// Webhook endpoint for Inbound Messages (Replies) - Optional
app.post('/webhooks/inbound', (req, res) => {
const params = req.body;
console.log('--- Inbound SMS Received ---');
console.log('From:', params.from);
console.log('Text:', params.text);
console.log('Timestamp:', params.timestamp);
console.log('--------------------------');
console.log('Full Inbound Payload:', JSON.stringify(params, null, 2));
// Acknowledge receipt
res.status(200).send('OK');
});
// --- Start Server ---
const port = process.env.APP_PORT;
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});Code Explanation:
- Imports & Setup: Loads
dotenv,express, and theVonageSDK. Initializes Express. - Middleware: Uses
express.json()andexpress.urlencoded()to parse incoming request bodies. - Environment Validation: Checks if all required environment variables are present before initializing Vonage. Exits if any are missing.
- Vonage Initialization: Creates a
Vonageclient instance using credentials from.env. It usespath.resolveto construct an absolute path to the private key, ensuring it's found correctly regardless of the directory from which the Node.js script is executed. /pingRoute: A simple health check endpoint./send-smsRoute (POST):- Extracts
to(recipient number) andtext(message content) from the JSON request body. - Performs basic validation on input, including checking the
tonumber format (allows optional leading+). - Calls
vonage.messages.send()with the required parameters (message_type,text,to,from,channel). - Uses
async/awaitfor cleaner handling of the promise returned by the SDK. - Logs the result or error.
- Returns the
message_uuidon success or an error message on failure. Includes improved error logging from the Vonage response if available.
- Extracts
/webhooks/statusRoute (POST):- This is the endpoint Vonage will call when a delivery status update is available.
- Logs key information from the incoming DLR payload (
status,message_uuid,to,timestamp, and error details if present). - Logs the entire payload for debugging.
- Crucially: Sends a
200 OKresponse immediately to acknowledge receipt. Failure to do so will cause Vonage to retry the webhook delivery.
/webhooks/inboundRoute (POST):- Handles incoming SMS replies (optional for this guide). Logs the sender and message text.
- Also sends a
200 OKresponse.
- Server Start: Starts the Express server, listening on the port defined in
APP_PORT.
4. Running Locally with ngrok
To receive webhooks from Vonage on your local machine, you need to expose your local server to the internet. ngrok creates a secure tunnel for this.
-
Start ngrok: Open a new terminal window (keep the one for the server running later). Run ngrok, telling it to forward to the port your Express app listens on (defined by
APP_PORTin.env, which is3000).bashngrok http 3000 -
Get Forwarding URL: ngrok will display session information, including a
ForwardingURL that looks something likehttps://<random-string>.ngrok-free.app. Copy thehttpsURL. -
Update BASE_URL (Temporary for Dev):
- Go back to your
.envfile. - Update the
BASE_URLvariable with thehttpsURL you just copied from ngrok. Make sure there's no trailing slash.
dotenv# .env # ... other variables ... BASE_URL=https://<random-string>.ngrok-free.app- Note: This
BASE_URLin.envis primarily used here to make it easy to copy the URL into the Vonage Dashboard configuration. Your application code itself might not need to referenceprocess.env.BASE_URLdirectly unless it needs to construct self-referential URLs.
- Go back to your
-
Update Vonage Application Webhooks:
- Go back to your Vonage Application settings in the Dashboard (
Applications-> Your App Name). - Click
Edit. - Update the Status URL to
YOUR_NGROK_HTTPS_URL/webhooks/status(e.g.,https://<random-string>.ngrok-free.app/webhooks/status). - Update the Inbound URL to
YOUR_NGROK_HTTPS_URL/webhooks/inbound. - Scroll down and click
Save changes.
Why update Vonage? Vonage needs the public ngrok URL to know where to send the status and inbound webhooks.
- Go back to your Vonage Application settings in the Dashboard (
5. Verification and Testing
Now, let's run the application and test the SMS sending and delivery receipt process.
-
Start the Node.js Server: In the terminal window where your project code is located, run:
bashnode src/server.jsYou should see the output:
Server listening at http://localhost:3000. -
Send an SMS using
curl(or Postman): Open another terminal window. ReplaceYOUR_PHONE_NUMBERwith your actual mobile phone number (including country code, e.g.,+14155551212) andYOUR_NGROK_URLwith your actual ngrok Forwarding URL. Use a generic message.bashcurl -X POST YOUR_NGROK_URL/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_PHONE_NUMBER", "text": "Hello from Vonage Node Guide! DLR Test." }'- Example:
bash
curl -X POST https://<random-string>.ngrok-free.app/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "+14155551212", "text": "Hello from Vonage Node Guide! DLR Test." }'
- Example:
-
Check Server Logs (Sending): Look at the terminal where your
node src/server.jscommand is running. You should see logs indicating the attempt to send and the success response from Vonage, including themessage_uuid:Server listening at http://localhost:3000 Attempting to send SMS from 12015550123 to +14155551212 Message submitted successfully: { message_uuid: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } -
Check Your Phone: You should receive the SMS message on the phone number you specified.
-
Check Server Logs (Delivery Receipt): Wait a few seconds (delivery times vary). Watch the same server log terminal. You should see the incoming webhook request logged by the
/webhooks/statusendpoint. The timestamp will reflect the actual time of the event.--- Delivery Receipt Received --- Status: delivered Message UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 To: +14155551212 Timestamp: YYYY-MM-DDTHH:mm:ss.sssZ --------------------------------- Full DLR Payload: { "message_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "to": "+14155551212", "from": "12015550123", "timestamp": "YYYY-MM-DDTHH:mm:ss.sssZ", "status": "delivered", "usage": { "currency": "USD", "price": "0.0075" }, "client_ref": null }- Key field:
status. Common values includesubmitted,delivered,rejected,undeliverable,expired. message_uuid: Matches the ID returned when you initially sent the message. This allows you to correlate the status update with the original outbound message.
- Key field:
-
Test Failure Case (Optional): Try sending to an invalid or non-existent number (if you know one that reliably fails). You should receive a DLR with a status like
failedorrejected, potentially with an error code.
6. Error Handling and Logging
Production applications need more robust error handling and logging.
- Use a Dedicated Logger: For production, replace
console.logandconsole.errorwith a structured logger likewinstonorpino. This enables log levels, formatting, and easier integration with log management systems. - Detailed Error Logging: As implemented in the
/send-smsroute, log detailed error information received from the Vonage API (err.response.data) when available. This is crucial for debugging API interaction issues. - Webhook Error Handling: The current webhook handler simply logs and responds 200. In a real application, you might:
- Wrap the processing logic in a
try...catchblock. - If processing fails after receiving the webhook (e.g., database update error), log the error but still return
200 OKto Vonage to prevent retries of the same webhook. Handle the internal failure separately (e.g., add to a retry queue). - Implement more specific logic based on the
statusfield (e.g., update a database record, notify an administrator on failure).
- Wrap the processing logic in a
- Common Vonage Errors:
- 401 Unauthorized: Incorrect API Key/Secret or Application ID/Private Key setup. Check
.envand Vonage configuration. Ensure the private key file path is correct and readable. - 400 Bad Request: Invalid parameters (e.g., malformed
tonumber, missingtext). Check request payload and API documentation. - 402 Payment Required: Insufficient funds in your Vonage account.
- 403 Forbidden/Invalid Credentials: Often related to Application ID/Private Key issues or number permissions.
- Webhook Timeouts: If your
/webhooks/statusendpoint takes too long to respond (> 3-5 seconds), Vonage will consider it failed and retry. Ensure your handler is fast and only performs essential processing before responding200 OK. Offload heavy processing to a background job if necessary.
- 401 Unauthorized: Incorrect API Key/Secret or Application ID/Private Key setup. Check
7. Security Considerations
- Credentials:
- NEVER commit your
.envfile orprivate.keyfile to version control (Git). Use.gitignore. - Production Secret Management: Use environment variables injected securely by your hosting platform or a dedicated secrets management service (like AWS Secrets Manager, Google Secret Manager, HashiCorp Vault) for sensitive data in production deployments. Do not rely on files within the project structure.
- NEVER commit your
- Input Validation:
- Always validate and sanitize input received from API requests (
/send-sms) and webhooks. The example includes basic validation fortoandtext. Use libraries likejoiorexpress-validatorfor more complex validation. - Validate the format of phone numbers (e.g., using a dedicated library for E.164 validation if needed).
- Always validate and sanitize input received from API requests (
- Webhook Security (Advanced):
- While not implemented here, for higher security, you should verify that incoming webhooks genuinely originate from Vonage. The Messages API supports Signed Webhooks (JWT). Refer to Vonage Signed Webhooks documentation for implementation details. This involves verifying a signature included in the request header using your Vonage signature secret. Without signature verification, your webhook endpoint is vulnerable to spoofing, meaning anyone who discovers the URL could potentially send fake status updates to your application.
- Rate Limiting: Protect your
/send-smsendpoint from abuse by implementing rate limiting (e.g., usingexpress-rate-limit). - HTTPS: Always use HTTPS for your webhook endpoints in production (ngrok provides this for development).
8. Troubleshooting and Caveats
- DLR Support: Delivery receipts are dependent on downstream carrier networks. Not all carriers in all countries support DLRs, or they may provide limited status updates (e.g., only
submitted). Refer to Vonage SMS Features documentation for country-specific details. - Messages API Default: Double-check that the Messages API is set as the default SMS setting in your Vonage account settings (Section 2, Step 2). This is critical. If the older
SMS APIis the default, Vonage may attempt to route delivery receipts via a different, unconfigured mechanism, and they will likely not arrive at theStatus URLdefined in your Vonage Application, even if you use the Messages API SDK to send the message. - Webhook Accessibility: Your webhook endpoint (
/webhooks/status) must be publicly accessible via HTTPS for Vonage to reach it. ngrok handles this in development. In production, ensure your server is correctly deployed and accessible, and that firewalls allow incoming POST requests from Vonage IP ranges if applicable. - Webhook Response: Your webhook endpoint must respond with a
200 OKstatus code quickly (within 3-5 seconds). Non-2xx responses or timeouts will cause Vonage to retry delivery, potentially leading to duplicate processing if not handled idempotently. - Private Key: Ensure the
VONAGE_PRIVATE_KEY_PATHin.envcorrectly points to theprivate.keyfile downloaded during application creation, and that the Node.js process has read permissions for this file. Usingpath.resolvehelps, but file existence and permissions are still crucial. - Vonage Dashboard Logs: If webhooks aren't arriving, check the
Logssection in your Vonage Dashboard. Search for yourmessage_uuid. It often provides details on API call errors or webhook delivery failures (including reasons like connection timeouts or non-200 responses from your webhook URL). - SDK Version: Ensure you are using a compatible version of the
@vonage/server-sdk. Check the SDK's documentation if you encounter unexpected behavior.
9. Deployment and CI/CD (Conceptual)
Deploying this application involves moving beyond ngrok.
- Choose a Platform: Select a hosting provider (e.g., Heroku, AWS EC2/Lambda/ECS, Google Cloud Run, DigitalOcean App Platform, Vercel, Railway).
- Environment Variables: Configure your production environment variables (
VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_ID,VONAGE_NUMBER,APP_PORT,VONAGE_PRIVATE_KEY- potentially the key content itself, not the path) securely using your hosting provider's mechanisms. Do not include the.envfile or theprivate.keyfile directly in your deployment package. - Update Webhook URL: Get the public HTTPS URL of your deployed application (e.g.,
https://your-app-name.herokuapp.com). Update theStatus URLandInbound URLin your Vonage Application settings to use this production URL. - Build Step (If needed): If using TypeScript or a build process, ensure it's run before deployment.
- Start Command: Configure your hosting provider to start the application using
node src/server.js(or similar). - Process Manager: Use a process manager like
pm2in production environments (if deploying to VMs/containers) to handle restarts, clustering, and monitoring.npm install pm2 -g, then start withpm2 start src/server.js --name vonage-sms-app. - CI/CD: Set up a pipeline (e.g., GitHub Actions, GitLab CI, Jenkins) to automate testing, building, and deploying your application on code changes.
- Rollback: Have a plan to revert to a previous working version if a deployment introduces issues.
10. Conclusion and Next Steps
You have successfully built a Node.js Express application that can send SMS messages using the Vonage Messages API and receive delivery status updates via webhooks. This provides crucial visibility into message delivery success.
Further Enhancements:
- Database Integration: Store message details (
message_uuid,to,status,timestamp) in a database to track history and query message status. Update the status in the/webhooks/statushandler. - Robust Error Handling: Implement more granular error handling and potentially retry mechanisms for transient Vonage API errors or webhook processing failures.
- Webhook Signature Verification: Implement JWT signature verification for enhanced webhook security (highly recommended for production).
- User Interface: Build a frontend to interact with the
/send-smsendpoint. - Handle Inbound Replies: Add logic to the
/webhooks/inboundendpoint to process replies from users. - Monitoring & Alerting: Integrate with monitoring tools (e.g., Prometheus, Grafana, Datadog) to track API usage, errors, and webhook latency. Set up alerts for failures.
- Unit & Integration Tests: Write tests using frameworks like Jest or Mocha to verify the functionality of your routes and Vonage interactions.
This guide provides a solid foundation. Remember to consult the official Vonage Messages API documentation for more advanced features and details.
Frequently Asked Questions
How to send SMS with Node.js and Vonage
Use the Vonage Messages API and Node.js SDK. Install the `@vonage/server-sdk` package, initialize the Vonage client with your API credentials, and then use the `messages.send()` method, providing the recipient's number, your Vonage number, and the message text. This allows you to programmatically send SMS messages from your Node.js application.
What is a Vonage delivery receipt (DLR)
A Vonage Delivery Receipt (DLR) is a webhook notification that provides real-time updates on the delivery status of your SMS messages. These updates include statuses like 'delivered', 'rejected', 'failed', or 'undeliverable', giving you insights into whether your message reached the recipient's handset or encountered issues along the way. The information is crucial for applications requiring delivery confirmation.
Why use webhooks for delivery status
While Vonage confirms message *submission*, it doesn't guarantee *delivery*. Webhooks offer asynchronous delivery updates. The `/webhooks/status` endpoint in your app receives these updates, allowing you to react to successful deliveries or handle failures without blocking your main application flow.
When should I use the Vonage Messages API
Use the Vonage Messages API when you need to send and receive messages programmatically across different channels, including SMS. It's ideal for applications that require confirmation that messages have been successfully delivered to the recipient's device, providing reliable message delivery handling.
How to set up Vonage DLR webhooks with Express
Create a Vonage application in the Vonage Dashboard, enable the 'Messages' capability, and configure the 'Status URL' to point to your Express app's webhook route (e.g., `YOUR_BASE_URL/webhooks/status`). When Vonage sends a DLR POST request, your app must respond with '200 OK' to acknowledge receipt, ensuring Vonage doesn't retry.
What is ngrok used for with Vonage webhooks
ngrok creates a public tunnel to your local development server. This is crucial for testing webhooks during development, as Vonage needs a publicly accessible URL to send DLR callbacks to your `/webhooks/status` endpoint when running locally.
How to get Vonage API key and secret
Log in to your Vonage API Dashboard. Your API key and API secret are displayed on the main homepage. Copy these values and paste them into your `.env` file (or other secure storage mechanism) as `VONAGE_API_KEY` and `VONAGE_API_SECRET` respectively.
What is the Vonage private key used for
The Vonage private key, downloaded when you create a Vonage Application, is crucial for authenticating your application with the Messages API. It's used along with your Application ID to ensure secure API access. Never commit this key to Git; use environment variables or a secure secret store.
How to fix 'Vonage API Error: Unauthorized'
A 401 Unauthorized error usually indicates incorrect or missing Vonage API credentials. Double-check your `VONAGE_API_KEY`, `VONAGE_API_SECRET`, `VONAGE_APPLICATION_ID`, and `VONAGE_PRIVATE_KEY_PATH` in your `.env` file. Verify they match the values in your Vonage Dashboard, and that the private key file exists and is readable.
Why does Vonage webhook not work
Check that your webhook endpoint (`/webhooks/status`) is publicly accessible (ngrok for development). Ensure it responds with a 200 OK within a few seconds. Verify your Vonage Application's 'Status URL' is correctly set to the public URL, and the default SMS setting is 'Messages API', not 'SMS API'.
How to secure Vonage webhooks in production
For enhanced security, use Signed Webhooks by configuring a webhook signing key in your Vonage Application. Your webhook handler should verify the signature in the request header using this key, preventing spoofing. See the Vonage documentation for details on Signed Webhooks.
What do Vonage DLR statuses mean
Statuses like 'delivered' mean the message reached the handset. 'rejected' or 'failed' indicate delivery failures. 'undeliverable' suggests a permanent issue, while 'expired' means the message timed out before successful delivery. Check Vonage DLR documentation for a comprehensive list of statuses.
Can I test Vonage DLR with invalid numbers
Yes, using an invalid or test phone number is a good way to test failure paths and see how your application handles DLR statuses like 'failed', 'rejected', or 'undeliverable', providing insights into your application's error handling.
How to handle Vonage webhook errors
Your `/webhooks/status` endpoint should handle potential errors (e.g., database issues) gracefully. Use `try...catch` blocks. Always return a `200 OK` to Vonage first, even if your internal processing fails, to avoid Vonage webhook retries. Log the error and handle it separately, potentially using a retry queue.