Frequently Asked Questions
Use the Vonage Server SDK for Node.js and the /send-sms endpoint created in this tutorial. This endpoint interacts with the Vonage SMS API to send messages programmatically after receiving the recipient's number and the message text from a POST request. Remember to configure your Vonage API key and secret in the .env file.
A Delivery Receipt (DLR) is a status update sent by Vonage to your application after an SMS message is sent. It confirms the delivery status (e.g., delivered, failed, expired) of the message, providing crucial feedback for reliable communication workflows. Vonage uses webhooks to send DLRs, ensuring real-time updates.
Set up a webhook URL in your Vonage API dashboard pointing to a /delivery-receipt endpoint in your Express app. When Vonage sends the DLR as a POST request containing the delivery status, your Express app parses and logs it. Don't forget to use ngrok during development to expose the local server for webhook configuration and testing.
Vonage uses webhooks for DLRs to provide real-time updates asynchronously without your application needing to constantly poll the API. This push-based mechanism allows your application to react immediately to delivery status changes, which is essential for time-sensitive applications.
For new projects, consider using the Messages API for its unified webhook format and support for multiple messaging channels beyond just SMS (e.g., WhatsApp). If working with an existing SMS API implementation, migrating to the Messages API is possible but requires adjusting endpoints and data structures.
You need Node.js and npm (or yarn), a Vonage API account with API Key and Secret, a Vonage virtual number for sending SMS, ngrok for testing webhooks, and some familiarity with JavaScript, Node.js, Express, and REST APIs. The tutorial uses other packages such as dotenv, express-validator, and optionally, nodemon.
Run ngrok http 3000 in a separate terminal to expose your local server. Copy the HTTPS URL provided by ngrok. Update the BASE_URL in your .env file with this HTTPS URL, and configure it as your webhook URL for Delivery Receipts in the Vonage API Dashboard. Restart your Node.js app to apply the changes.
Express-validator is used for input validation, enhancing the robustness of your application. It performs checks on incoming requests in the /send-sms endpoint, such as ensuring the "to" and "text" fields are present and correctly formatted before sending the SMS, improving security and data quality.
Use try...catch blocks around vonage.sms.send() to handle potential errors like network issues or incorrect API credentials. Check the resp.messages[0].status code for immediate submission issues, error.response.data for detailed error information, and return appropriate HTTP status codes with error messages.
If vonage.sms.send() fails due to temporary issues (e.g., network, 5xx error from Vonage), implement application-level retries using libraries like async-retry with exponential backoff. Be careful not to retry on permanent errors like validation failures or incorrect number formats indicated by 4xx errors.
Always respond to Vonage DLR webhooks with a 200 OK status, even if internal processing fails. This prevents Vonage from retrying the webhook and potentially causing duplicate processing. Log any internal processing errors for debugging after sending the 200 OK.
While this tutorial provides a conceptual schema for a database, it's crucial in production to store messageId, status, and other relevant data. This allows querying and tracking of messages. Upon sending, store initial data. On receiving a DLR, update the corresponding record with the delivery status.
Common DLR statuses include 'delivered', 'accepted', 'failed', 'expired', 'rejected', and 'buffered'. 'delivered' signifies successful delivery, 'failed' indicates issues like invalid numbers, and 'rejected' might relate to spam filters. Consult Vonage's DLR status documentation for complete details and corresponding error codes.
Replace console.* logging with structured loggers like Pino or Winston. These loggers provide log levels, JSON formatting, and easier integration with log management systems, helping in tracking, analyzing, and debugging applications more efficiently.
Log in to the Vonage API Dashboard. Your API Key and API Secret are prominently displayed on the main page. Securely store these credentials in a .env file and never commit them to version control systems like Git.
This guide provides a step-by-step walkthrough for building a Node.js application using Express to send SMS messages via the Vonage SMS API and receive real-time delivery status updates through webhooks.
By the end of this tutorial, you'll have built a functional application capable of:
You'll solve the common need for applications to confirm whether SMS notifications were successfully delivered to the recipient's handset – crucial for reliable communication workflows.1
Technologies Used
@vonage/server-sdkNode.js library..envfile intoprocess.env.System Architecture
The basic flow of your application works as follows:
/send-smsendpoint./delivery-receipt).Prerequisites
1. Setting Up the Project
Initialize your Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
Initialize Node.js Project: Initialize the project using npm, accepting the defaults.
This creates a
package.jsonfile.Enable ES Modules: Since you'll use ES Module
importsyntax (e.g.,import express from 'express'), tell Node.js to treat.jsfiles as modules. Open yourpackage.jsonfile and add the line"type": "module"at the top level:Install Dependencies: Install Express for the web server, the Vonage Server SDK for interacting with the API, dotenv for managing environment variables, express-validator for input validation, and optionally nodemon for development convenience.
Configure
nodemon(Optional): If you installednodemon, add a development script to yourpackage.json. Openpackage.jsonand add/modify thescriptssection:This allows you to run
npm run devto start your server with auto-reloading on file changes.Create Core Files: Create the main application file and files for environment variables and ignored files.
Configure
.gitignore: Prevent sensitive information and unnecessary files from being committed to version control. Add the following to your.gitignorefile:Configure
.env: Add placeholders for your Vonage credentials and number. You'll also add aBASE_URLwhich will be your ngrok URL later. Don't commit this file to Git.VONAGE_API_KEY: Your API key from the Vonage Dashboard.VONAGE_API_SECRET: Your API secret from the Vonage Dashboard.VONAGE_NUMBER: The Vonage virtual number you purchased (in E.164 format, e.g.,+14155550100).PORT: The port your Express server will listen on.BASE_URL: The public-facing base URL of your application. Crucial for webhook configuration.2. Implementing Core Functionality: Sending SMS
Write the code to initialize Express and the Vonage SDK, and create an endpoint to send an SMS message.
Explanation:
express,dotenv, andVonagefrom@vonage/server-sdk.dotenv.config()loads variables from.env. Add a check to ensure critical Vonage variables are present.express.json,express.urlencoded) to easily parse incoming request bodies.Vonageclient instance using the API key and secret from your environment variables./send-smsEndpoint:POSTroute at/send-sms.to) and message content (text) from the request body.toandtextare provided andtolooks somewhat like a phone number (clarifying E.164 format is preferred).vonage.sms.send()within anasyncfunction to send the message. This method requiresto,from(your Vonage number), andtext.vonage.sms.send()confirms submission to Vonage, not final delivery. It includes astatuscode ('0'means success) and themessage-id. Check this status for immediate feedback.try...catchfor handling network or API-level errors during the call. It logs errors and returns appropriate status codes (400 for submission errors, 500 for server errors)..env(or default 3000). A warning reminds you about ngrok if theBASE_URLhasn't been updated.SIGINT(Ctrl+C) for a cleaner shutdown process.3. Handling Delivery Status Callbacks (Webhooks)
Vonage uses webhooks to send Delivery Receipts (DLRs) back to your application. We need an endpoint to receive these POST requests.
Set up
ngrok: Open a new terminal window and runngrokto expose your local server running on port 3000.ngrokwill display forwarding URLs (e.g.,https://<random-string>.ngrok-free.app). Copy thehttpsURL. This is your public URL.Update
.env: Replace theBASE_URLin your.envfile with thehttpsURL provided by ngrok.Restart your Node.js application (
npm run devornpm start) for the changes in.envto take effect.Configure Vonage Webhooks:
YOUR_NGROK_HTTPS_URL/delivery-receipt(e.g.,https://<random-string>.ngrok-free.app/delivery-receipt).POST./webhooks/dlr) and provides a unified webhook format for multiple channels (SMS, WhatsApp, etc.). For new implementations, you might consider using the Messages API instead, although its setup is slightly different.Implement the Webhook Endpoint: Add the following route handler to your
index.jsfile, typically before theapp.listencall.Explanation:
POSTroute at/delivery-receipt, matching the URL configured in the Vonage dashboard.req.body. Express'sjson()andurlencoded()middleware handle parsing based on theContent-Typeheader sent by Vonage.message-id,status,err-code,msisdn(recipient), andscts(timestamp) are extracted and logged. Note that field names might vary slightly (e.g.,message-idvsmessageId), so we check for both common variants.TODOcomment highlights where you would integrate this data into your application's logic (e.g., database updates).deliveredorfailed.200 OKresponse back to Vonage usingres.status(200).send('OK'). This acknowledges receipt of the webhook. If Vonage doesn't receive a 200 OK, it will assume the webhook failed and may retry sending it.4. Building a Complete API Layer
Let's refine the
/send-smsendpoint with robust validation usingexpress-validator.Update
/send-smswith Validation: Modify the/send-smsendpoint definition inindex.js.Changes:
bodyandvalidationResultfromexpress-validator.sendSmsValidationRulesarray specifying rules fortoandtext. We useisMobilePhonefor general validation but message guides towards E.164. Added length constraints fortext.validateRequestmiddleware function to checkvalidationResultand return errors if any.app.post('/send-sms', ...)definition before the main async handler.express-validatornow handles it more robustly.vonage.sms.sendand catching broader API/network errors.API Testing Examples (
curl):Send SMS (Success): Replace placeholders with your actual recipient number (use E.164 format like
+14155550100).Expected Response (200 OK):
Send SMS (Validation Error - Missing 'text'):
Expected Response (400 Bad Request):
Send SMS (Validation Error - Invalid 'to'):
Expected Response (400 Bad Request):
Delivery Receipt Webhook (Simulated): You can't easily simulate the exact Vonage DLR POST with
curlwithout knowing a validmessage-idbeforehand, but you can test if your endpoint receives a POST correctly. Use the ngrok inspector (http://127.0.0.1:4040in your browser) to see the real DLRs coming from Vonage after sending a message.5. Integrating with Vonage Services (Recap & Details)
Let's consolidate the key integration points with Vonage.
API Credentials:
.envfile (VONAGE_API_KEY,VONAGE_API_SECRET) and ensure.envis listed in your.gitignore. Never hardcode credentials directly in your source code.@vonage/server-sdkto authenticate your API requests.Virtual Number:
vonage numbers:search GB --features=SMS,vonage numbers:buy <number> GB).+14155550100) in your.envfile (VONAGE_NUMBER).Webhook Configuration (Delivery Receipts):
YOUR_NGROK_HTTPS_URL/delivery-receipt. Must be publicly accessible (hencengrok).POST.BASE_URL): Storing the ngrok URL (or your production URL) in.env(BASE_URL) helps manage this, although it's primarily used here for the reminder log message. The critical part is pasting the correct URL into the Vonage dashboard.6. Error Handling, Logging, and Retry Mechanisms
Error Handling Strategy:
/send-sms): Usetry...catchblocks aroundvonage.sms.send(). Check the direct response status (resp.messages[0].status). Log detailed errors (console.error). Checkerror.response.datafor specific Vonage API error details if available during exceptions. Return appropriate HTTP status codes (400 for validation/submission errors, 500 for server/API errors) with clear JSON error messages./delivery-receipt): Log incoming DLRs. Usetry...catcharound your internal processing logic (e.g., database updates). Crucially, always return200 OKto Vonage, even if your internal processing fails, to prevent retries. Log internal processing errors separately.Logging:
console.logfor informational messages (sending attempts, DLR received, status updates).console.warnfor non-critical issues (e.g., validation errors, incomplete DLRs).console.errorfor actual errors (API call failures, submission failures, internal processing failures).console.*with a structured logger like Pino or Winston. This allows for log levels, JSON formatting, and easier integration with log management systems.Retry Mechanisms (Vonage):
vonage.sms.send()fails due to a temporary network issue or a 5xx error from Vonage (caught in thecatchblock), you might implement application-level retries (e.g., using a library likeasync-retry) with exponential backoff. However, be cautious not to retry on permanent errors (like invalid number format - 4xx errors or submission errors indicated in the response).200 OKwithin a reasonable timeout (usually several seconds). Your primary responsibility is to ensure your/delivery-receiptendpoint is reliable and responds quickly with200 OK. Offload heavy processing (like database writes) to happen asynchronously after sending the 200 OK, perhaps using a message queue.Common Vonage DLR Statuses/Errors:
delivered: Successfully delivered to the handset.accepted: Accepted by the carrier, awaiting final status.failed: Delivery failed (e.g., number invalid, blocked). Checkerr-code.expired: Message validity period expired before delivery.rejected: Rejected by Vonage or carrier (e.g., spam filter, permission issue). Checkerr-code.buffered: Temporarily stored, awaiting delivery attempt.err-codemeanings.7. Database Schema and Data Layer (Conceptual)
While this guide doesn't implement a database, in a production system, you'd need one to track message status.
Why: To persistently store the
messageIdreceived when sending and associate it with the subsequent delivery status received via webhook. This allows you to query the status of any message sent.Example Schema (Conceptual - e.g., PostgreSQL):
Data Layer Logic:
/send-smssuccess): Insert a new record intosms_messageswith thevonage_message_id, recipient, sender, text, and setstatusto'submitted'./delivery-receipt):sms_messagesusing thevonage_message_idfrom the webhook payload.status,vonage_status_code,price,network_code,dlr_timestamp, andlast_updated_atfields based on the DLR data.messageIdmight not be found (log an error).status(e.g., trigger notifications for failures).Conclusion
You have successfully built a Node.js application using Express that can send SMS messages via the Vonage API and receive delivery status updates through webhooks. You've learned how to:
/send-sms) to send messages, including input validation.ngrokto expose your local server for webhook testing./delivery-receipt) to receive and process DLRs.This foundation allows you to build more complex communication workflows requiring reliable SMS delivery confirmation. Remember to replace placeholder credentials and URLs with your actual values and consider using a structured logger and robust database integration for production environments.
References
Footnotes
Vonage Developer Documentation. "SMS Delivery Receipts API Guide." Vonage API Documentation. Accessed October 2025. https://developer.vonage.com/en/messaging/sms/guides/delivery-receipts. Explains how delivery receipts work, DLR status messages, and error codes for tracking SMS delivery confirmation. ↩