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 the Express framework to send SMS messages via the Vonage SMS API and receive real-time delivery status updates (Delivery Receipts or DLRs) through webhooks.
By the end of this tutorial, you will have a functional application capable of:
This solves the common need for applications to not only send notifications via SMS but also to confirm whether those messages were successfully delivered to the end user's handset, which is crucial for reliable communication workflows.
Technologies Used
@vonage/server-sdk
Node.js library..env
file intoprocess.env
.System Architecture
The basic flow of the application is as follows:
/send-sms
endpoint./delivery-receipt
).Prerequisites
1. Setting Up the Project
Let's initialize our 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.json
file.Enable ES Modules: Since we'll be using ES Module
import
syntax (e.g.,import express from 'express'
), we need to tell Node.js to treat.js
files as modules. Open yourpackage.json
file 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.json
and add/modify thescripts
section:This allows you to run
npm run dev
to 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.gitignore
file:Configure
.env
: Add placeholders for your Vonage credentials and number. We will also add aBASE_URL
which will be our ngrok URL later. Do not 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
Now, let's write the code to initialize Express and the Vonage SDK, and create an endpoint to send an SMS message.
Explanation:
express
,dotenv
, andVonage
from@vonage/server-sdk
.dotenv.config()
loads variables from.env
. We add a check to ensure critical Vonage variables are present.express.json
,express.urlencoded
) to easily parse incoming request bodies.Vonage
client instance using the API key and secret from our environment variables./send-sms
Endpoint:POST
route at/send-sms
.to
) and message content (text
) from the request body.to
andtext
are provided andto
looks somewhat like a phone number (clarifying E.164 format is preferred).vonage.sms.send()
within anasync
function to send the message. This method requiresto
,from
(your Vonage number), andtext
.vonage.sms.send()
confirms submission to Vonage, not final delivery. It includes astatus
code ('0'
means success) and themessage-id
. We check this status for immediate feedback.try...catch
for 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 the user about ngrok if theBASE_URL
hasn'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 runngrok
to expose your local server running on port 3000.ngrok
will display forwarding URLs (e.g.,https://<random-string>.ngrok-free.app
). Copy thehttps
URL. This is your public URL.Update
.env
: Replace theBASE_URL
in your.env
file with thehttps
URL provided by ngrok.Restart your Node.js application (
npm run dev
ornpm start
) for the changes in.env
to 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.js
file, typically before theapp.listen
call.Explanation:
POST
route at/delivery-receipt
, matching the URL configured in the Vonage dashboard.req.body
. Express'sjson()
andurlencoded()
middleware handle parsing based on theContent-Type
header 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-id
vsmessageId
), so we check for both common variants.TODO
comment highlights where you would integrate this data into your application's logic (e.g., database updates).delivered
orfailed
.200 OK
response 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-sms
endpoint with robust validation usingexpress-validator
.Update
/send-sms
with Validation: Modify the/send-sms
endpoint definition inindex.js
.Changes:
body
andvalidationResult
fromexpress-validator
.sendSmsValidationRules
array specifying rules forto
andtext
. We useisMobilePhone
for general validation but message guides towards E.164. Added length constraints fortext
.validateRequest
middleware function to checkvalidationResult
and return errors if any.app.post('/send-sms', ...)
definition before the main async handler.express-validator
now handles it more robustly.vonage.sms.send
and 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
curl
without knowing a validmessage-id
beforehand, but you can test if your endpoint receives a POST correctly. Use the ngrok inspector (http://127.0.0.1:4040
in 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:
.env
file (VONAGE_API_KEY
,VONAGE_API_SECRET
) and ensure.env
is listed in your.gitignore
. Never hardcode credentials directly in your source code.@vonage/server-sdk
to authenticate your API requests.Virtual Number:
vonage numbers:search GB --features=SMS
,vonage numbers:buy <number> GB
).+14155550100
) in your.env
file (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...catch
blocks aroundvonage.sms.send()
. Check the direct response status (resp.messages[0].status
). Log detailed errors (console.error
). Checkerror.response.data
for 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...catch
around your internal processing logic (e.g., database updates). Crucially, always return200 OK
to Vonage, even if your internal processing fails, to prevent retries. Log internal processing errors separately.Logging:
console.log
for informational messages (sending attempts, DLR received, status updates).console.warn
for non-critical issues (e.g., validation errors, incomplete DLRs).console.error
for 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 thecatch
block), 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 OK
within a reasonable timeout (usually several seconds). Your primary responsibility is to ensure your/delivery-receipt
endpoint 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-code
meanings.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
messageId
received 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-sms
success): Insert a new record intosms_messages
with thevonage_message_id
, recipient, sender, text, and setstatus
to'submitted'
./delivery-receipt
):sms_messages
using thevonage_message_id
from the webhook payload.status
,vonage_status_code
,price
,network_code
,dlr_timestamp
, andlast_updated_at
fields based on the DLR data.messageId
might 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.ngrok
to 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.