code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

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 .env file into process.env.

System Architecture:

text
+-------------+        +-----------------+        +-------------+        +---------+
| 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:

  1. Accepting API requests to send SMS messages.
  2. Receiving delivery status updates on a dedicated webhook endpoint.
  3. 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.

  1. Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.

    bash
    mkdir vonage-sms-dlr-guide
    cd vonage-sms-dlr-guide
  2. Initialize Node.js Project: Initialize the project using npm, accepting the defaults.

    bash
    npm init -y

    This creates a package.json file.

  3. Install Dependencies: Install Express for the web server, the Vonage Server SDK, and dotenv for environment variable management.

    bash
    npm install express @vonage/server-sdk dotenv --save
    • express: Web framework.
    • @vonage/server-sdk: Official Vonage SDK for Node.js.
    • dotenv: Loads environment variables from .env.
  4. Create Project Structure: Create a basic structure for clarity.

    bash
    mkdir src
    touch src/server.js
    touch .env
    touch .gitignore
    • src/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.
  5. Configure .gitignore: Add node_modules and .env to your .gitignore file to prevent committing them. Also explicitly ignore private key files.

    plaintext
    # .gitignore
    
    node_modules/
    .env
    *.key # Also ignore private key files if stored directly
  6. Set Up Environment Variables (.env): Open the .env file 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 placeholder

    Explanation:

    • 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 .gitignore prevents 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 initial localhost value 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.

  1. Get API Key and Secret:

    • Log in to your Vonage API Dashboard.
    • On the main page, you'll find your API key and API secret.
    • Copy these values and paste them into your .env file for VONAGE_API_KEY and VONAGE_API_SECRET.
  2. Set Default SMS API (Important):

    • Navigate to Account Settings in the Vonage Dashboard.
    • Scroll down to API settings -> Default SMS Setting.
    • Ensure Messages API is 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 older SMS API is selected, DLRs might not arrive at your expected endpoint.
    • Click Save changes.
  3. 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.key file. Save this file in the root directory of your project (the same level as package.json and .env). Ensure VONAGE_PRIVATE_KEY_PATH in your .env file points to this location (./private.key). Vonage stores the public key.
    • Capabilities: Enable the Messages capability.
    • Configure Webhooks:
      • Status URL: Enter YOUR_BASE_URL/webhooks/status. Replace YOUR_BASE_URL with a placeholder for now, like http://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.
    • 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 .env file for VONAGE_APPLICATION_ID.
  4. Link Your Vonage Number:

    • On the same application details page, scroll down to the Linked numbers section.
    • Find your purchased Vonage virtual number in the dropdown or list.
    • Click the Link button 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 .env file for VONAGE_NUMBER.

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:

javascript
// 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:

  1. Imports & Setup: Loads dotenv, express, and the Vonage SDK. Initializes Express.
  2. Middleware: Uses express.json() and express.urlencoded() to parse incoming request bodies.
  3. Environment Validation: Checks if all required environment variables are present before initializing Vonage. Exits if any are missing.
  4. Vonage Initialization: Creates a Vonage client instance using credentials from .env. It uses path.resolve to 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.
  5. /ping Route: A simple health check endpoint.
  6. /send-sms Route (POST):
    • Extracts to (recipient number) and text (message content) from the JSON request body.
    • Performs basic validation on input, including checking the to number format (allows optional leading +).
    • Calls vonage.messages.send() with the required parameters (message_type, text, to, from, channel).
    • Uses async/await for cleaner handling of the promise returned by the SDK.
    • Logs the result or error.
    • Returns the message_uuid on success or an error message on failure. Includes improved error logging from the Vonage response if available.
  7. /webhooks/status Route (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 OK response immediately to acknowledge receipt. Failure to do so will cause Vonage to retry the webhook delivery.
  8. /webhooks/inbound Route (POST):
    • Handles incoming SMS replies (optional for this guide). Logs the sender and message text.
    • Also sends a 200 OK response.
  9. 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.

  1. 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_PORT in .env, which is 3000).

    bash
    ngrok http 3000
  2. Get Forwarding URL: ngrok will display session information, including a Forwarding URL that looks something like https://<random-string>.ngrok-free.app. Copy the https URL.

  3. Update BASE_URL (Temporary for Dev):

    • Go back to your .env file.
    • Update the BASE_URL variable with the https URL 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_URL in .env is primarily used here to make it easy to copy the URL into the Vonage Dashboard configuration. Your application code itself might not need to reference process.env.BASE_URL directly unless it needs to construct self-referential URLs.
  4. 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.


5. Verification and Testing

Now, let's run the application and test the SMS sending and delivery receipt process.

  1. Start the Node.js Server: In the terminal window where your project code is located, run:

    bash
    node src/server.js

    You should see the output: Server listening at http://localhost:3000.

  2. Send an SMS using curl (or Postman): Open another terminal window. Replace YOUR_PHONE_NUMBER with your actual mobile phone number (including country code, e.g., +14155551212) and YOUR_NGROK_URL with your actual ngrok Forwarding URL. Use a generic message.

    bash
    curl -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."
               }'
  3. Check Server Logs (Sending): Look at the terminal where your node src/server.js command is running. You should see logs indicating the attempt to send and the success response from Vonage, including the message_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' }
  4. Check Your Phone: You should receive the SMS message on the phone number you specified.

  5. 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/status endpoint. 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 include submitted, 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.
  6. 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 failed or rejected, 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.log and console.error with a structured logger like winston or pino. This enables log levels, formatting, and easier integration with log management systems.
  • Detailed Error Logging: As implemented in the /send-sms route, 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...catch block.
    • If processing fails after receiving the webhook (e.g., database update error), log the error but still return 200 OK to 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 status field (e.g., update a database record, notify an administrator on failure).
  • Common Vonage Errors:
    • 401 Unauthorized: Incorrect API Key/Secret or Application ID/Private Key setup. Check .env and Vonage configuration. Ensure the private key file path is correct and readable.
    • 400 Bad Request: Invalid parameters (e.g., malformed to number, missing text). 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/status endpoint 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 responding 200 OK. Offload heavy processing to a background job if necessary.

7. Security Considerations

  • Credentials:
    • NEVER commit your .env file or private.key file 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.
  • Input Validation:
    • Always validate and sanitize input received from API requests (/send-sms) and webhooks. The example includes basic validation for to and text. Use libraries like joi or express-validator for more complex validation.
    • Validate the format of phone numbers (e.g., using a dedicated library for E.164 validation if needed).
  • 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-sms endpoint from abuse by implementing rate limiting (e.g., using express-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 API is the default, Vonage may attempt to route delivery receipts via a different, unconfigured mechanism, and they will likely not arrive at the Status URL defined 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 OK status 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_PATH in .env correctly points to the private.key file downloaded during application creation, and that the Node.js process has read permissions for this file. Using path.resolve helps, but file existence and permissions are still crucial.
  • Vonage Dashboard Logs: If webhooks aren't arriving, check the Logs section in your Vonage Dashboard. Search for your message_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.

  1. Choose a Platform: Select a hosting provider (e.g., Heroku, AWS EC2/Lambda/ECS, Google Cloud Run, DigitalOcean App Platform, Vercel, Railway).
  2. 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 .env file or the private.key file directly in your deployment package.
  3. Update Webhook URL: Get the public HTTPS URL of your deployed application (e.g., https://your-app-name.herokuapp.com). Update the Status URL and Inbound URL in your Vonage Application settings to use this production URL.
  4. Build Step (If needed): If using TypeScript or a build process, ensure it's run before deployment.
  5. Start Command: Configure your hosting provider to start the application using node src/server.js (or similar).
  6. Process Manager: Use a process manager like pm2 in production environments (if deploying to VMs/containers) to handle restarts, clustering, and monitoring. npm install pm2 -g, then start with pm2 start src/server.js --name vonage-sms-app.
  7. CI/CD: Set up a pipeline (e.g., GitHub Actions, GitLab CI, Jenkins) to automate testing, building, and deploying your application on code changes.
  8. 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/status handler.
  • 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-sms endpoint.
  • Handle Inbound Replies: Add logic to the /webhooks/inbound endpoint 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.