Frequently Asked Questions
Use the Vonage Node.js SDK and the `vonage.sms.send()` method. This method requires the recipient's number, your Vonage virtual number, and the message text. Ensure your API key and secret are configured correctly in your .env file and loaded into your application.
A Delivery Receipt (DLR) is a status update from the carrier network confirming the delivery status of your SMS message. Vonage forwards these updates to your application via webhooks, providing information like 'delivered', 'failed', or 'expired'.
Webhooks provide a real-time, asynchronous mechanism for Vonage to push delivery status updates to your application. This avoids the need for your application to constantly poll Vonage for status information.
Configure your webhook URL in the Vonage Dashboard *before* sending SMS messages if you want to track their delivery status. This ensures you receive DLRs as soon as they're available from the carriers.
Yes, ngrok creates a public HTTPS URL that tunnels requests to your local development server, allowing Vonage to deliver webhooks even if your server isn't publicly accessible.
Create an endpoint in your Express app (e.g., `/webhooks/dlr`) to listen for POST requests. Parse the request body, log the DLR information, and *always respond with a 2xx status code* to acknowledge receipt.
The `messageId` in the DLR corresponds to the `message-id` returned by the Vonage SMS API when you send a message. This ID is crucial for correlating DLRs with the original outbound messages.
Common status codes include 'delivered', 'failed', 'expired', 'buffered', 'rejected', 'accepted', and 'unknown'. Check the Vonage API reference for a comprehensive list and explanations of error codes.
Vonage retries sending the webhook if it doesn't receive a 2xx response quickly. Make your webhook handler idempotent so that processing the same DLR multiple times has no adverse effects. Use the messageId to check if the DLR has been processed before and skip the update if needed
Check your ngrok tunnel, server logs, and Vonage Dashboard settings. Ensure the webhook URL is correct and points to the right endpoint. Verify your server is running and responding with a 2xx status code.
Use HTTPS. Vonage signed webhooks with a JWT signature are supported by the Messages API but are not generally supported for standard SMS API DLRs. Use a rate limiter (e.g., `express-rate-limit`) to prevent abuse. Secure your API Key and secret. Though IP whitelisting is possible, it is less flexible due to potential IP range changes and generally avoided in favor of other security methods
Store message information and DLR statuses in a database. Create a table with fields for `vonage_message_id` (unique and indexed), `status`, `error_code`, timestamps, etc. Use the `messageId` from the DLR to update the corresponding record.
DLR delays depend on carrier network reporting times and can vary from seconds to minutes or occasionally longer. Design your application to tolerate these delays.
Not all carriers reliably support DLRs. Don't rely solely on DLRs for critical application logic. Check the recipient number and carrier capabilities. If issues persist, contact Vonage support.
Send SMS and Receive Delivery Status Callbacks with Node.js, Express, and Vonage
This guide provides a complete walkthrough for building a Node.js application using the Express framework to send SMS messages via the Vonage API and reliably receive delivery status updates (Delivery Receipts or DLRs) through webhooks. We'll cover everything from project setup to handling DLRs, ensuring you have a production-ready foundation.
You'll learn how to configure Vonage, send messages programmatically, set up a webhook endpoint to receive status updates, and handle potential issues. By the end, you'll have a functional application capable of sending SMS and tracking its delivery status.
Key Technologies:
System Architecture:
Prerequisites:
1. Setting up the Project
Let's create the project structure and install the necessary dependencies.
Create Project Directory: Open your terminal or command prompt and create a new directory for your project_ then navigate into it.
Initialize Node.js Project: Initialize the project using npm. The
-y
flag accepts default settings.This creates a
package.json
file.Install Dependencies: We need the Vonage Server SDK for interacting with the API_ Express for our web server_ and
dotenv
to manage environment variables securely.Create Project Files: Create the main files for our application logic and environment variables.
(Note for Windows CMD users: If
touch
is not available_ you can usetype nul > filename
for each file or create them manually in your editor.)index.js
: Will contain the code to send SMS messages.server.js
: Will contain the Express server code to receive DLR webhooks..env
: Will store sensitive credentials like API keys and phone numbers..gitignore
: Specifies files that Git should ignore (like.env
andnode_modules
).Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them to version control.Configure Environment Variables (
.env
): Open the.env
file and add your Vonage credentials and configuration. Replace the placeholder values with your actual data.VONAGE_API_KEY
,VONAGE_API_SECRET
: Found on the main page of your Vonage API Dashboard.VONAGE_NUMBER
: One of your virtual numbers purchased from Vonage (Vonage Dashboard > Numbers > Your Numbers). Ensure it's SMS-capable. Use E.164 format (e.g.,14155550100
).TO_NUMBER
: The destination phone number for your test SMS, also in E.164 format.PORT
: The local port ngrok will expose and your Express server will listen on.3000
is a common choice.2. Implementing Core Functionality: Sending SMS
Now, let's write the code in
index.js
to send an SMS message using the Vonage SDK.Edit
index.js
: Openindex.js
and add the following code:Explanation:
require('dotenv').config();
: Loads the variables defined in your.env
file intoprocess.env
.process.env.VONAGE_API_KEY
, etc.). Includes basic validation.Vonage
client using your API Key and Secret.sendSms
Function:vonage.sms.send()
. This method is specifically designed for the Vonage SMS API.to
,from
, andtext
properties.async/await
for cleaner handling of the promise returned by the SDK.messages
array. We check thestatus
of the first message ('0'
indicates success) and log themessage-id
which is crucial for correlating with Delivery Receipts later. We also log anyerror-text
if the status is not'0'
.try...catch
block to gracefully handle network issues or API errors during the send process. It attempts to log detailed error information if available from the Vonage SDK error object.sendSms()
function to initiate the process.Test Sending: Make sure you've saved your
.env
file with the correct credentials and numbers. Run the script from your terminal:You should see output indicating the message was sent successfully and receive an SMS on the
TO_NUMBER
phone. Keep the loggedMessage ID
handy – you'll see it again when the DLR comes back.3. Building the API Layer: Webhook for Delivery Receipts
Now, we'll create the Express server in
server.js
to listen for incoming POST requests from Vonage containing the DLRs.Edit
server.js
: Openserver.js
and add the following code:Explanation:
express.json()
andexpress.urlencoded({ extended: true })
are crucial for parsing incoming request bodies. Vonage DLRs typically arrive asapplication/x-www-form-urlencoded
, but supporting JSON is good practice./webhooks/dlr
):app.all()
to handle both GET and POST requests on this path, though Vonage primarily uses POST for DLRs.req.query
andreq.body
into a singleparams
object to handle data regardless of the HTTP method used by Vonage (though POST body is standard for DLRs).messageId
,status
,msisdn
,err-code
.// TODO:
comment indicating where you would add your application-specific logic (e.g., updating a database record with the delivery status).res.status(200).send('OK');
: This is vital. You must acknowledge receipt of the webhook to Vonage with a2xx
status code. Failing to respond or responding with an error code will cause Vonage to retry delivering the webhook./health
): A simple GET endpoint useful for verifying the server is running and accessible.PORT
.4. Integrating with Vonage: Configuring Webhooks
To receive DLRs, you need to tell Vonage where to send them. This involves exposing your local server using ngrok (during development) and configuring the webhook URL in the Vonage Dashboard.
Start Your Local Server: Open a terminal window in your project directory and run:
You should see
Server listening for webhooks at http://localhost:3000
.Expose Local Server with ngrok: Open a second terminal window. Run ngrok to create a public URL tunnel to your local port
3000
(or whichever port your server is using, matching thePORT
in your.env
).ngrok will display output similar to this:
Copy the
https://<random_string>.ngrok-free.app
URL. This is your public base URL.Configure Vonage SMS Settings:
https://<random_string>.ngrok-free.app/webhooks/dlr
Verification:
server.js
should be running.3000
.5. Implementing Error Handling and Logging
Robust error handling and logging are crucial for production systems.
SMS Sending Errors (
index.js
):try...catch
block inindex.js
already provides basic error handling for the API call itself.message['error-text']
) if the API indicates a failure (status !== '0'
).Webhook Handling Errors (
server.js
):200 OK
or204 No Content
to Vonage before undertaking potentially slow or fallible processing. This prevents Vonage retries due to timeouts. The example below demonstrates this "respond first" pattern.// TODO:
section) in its owntry...catch
block. If your internal processing fails (e.g., database error), log the error comprehensively, but ensure the 2xx response has already been sent to Vonage. You'll need separate monitoring/alerting for failures in your internal processing logic.Vonage Retries: Be aware that if Vonage doesn't receive a
2xx
response from your webhook endpoint within a reasonable timeframe (usually a few seconds), it will retry sending the DLR. Your webhook handler should be idempotent – meaning processing the same DLR multiple times should not cause adverse effects (e.g., use themessageId
to ensure you only update a status once, or use database transactions appropriately). The "respond first" pattern helps avoid retries due to processing time but doesn't inherently make the processing logic idempotent.6. Database Schema and Data Layer (Conceptual)
While this guide doesn't implement a full database layer, here's a conceptual schema for storing message and delivery status information:
Entity Relationship Diagram (Conceptual):
Data Access:
index.js
), you would insert a new record into theMessages
table with an initial status like'submitted'
, storing thevonage_message_id
returned by the API. You'd get back theinternal_message_id
.server.js
) receives a status update, you would use themessageId
from the payload (which corresponds tovonage_message_id
in your table) to find the relevant record in yourMessages
table and update thestatus
,last_status_update_at
,error_code
, andprice
fields accordingly.Tools:
sequelize-cli
or Prisma Migrate help manage database schema changes over time.7. Adding Security Features
Securing your application, especially the webhook endpoint, is crucial.
Secure Credential Storage: Already addressed by using
.env
and.gitignore
. Never commit secrets to version control. Use environment variable management provided by your hosting platform for production (VONAGE_API_KEY
,VONAGE_API_SECRET
, etc.).Webhook Security:
/webhooks/dlr/aBcD3fG7hJkL9mN1oPqR
) offers minimal protection but can deter casual scanners.Rate Limiting: Protect your webhook endpoint from abuse or potential retry storms by implementing rate limiting. The
express-rate-limit
package is a popular choice.Input Validation: While the DLR payload comes from Vonage, it's still good practice to validate expected fields and types before processing, though less critical than user-submitted data.
8. Handling Special Cases Relevant to SMS DLRs
Consider these real-world scenarios:
delivered
: Message successfully reached the handset.failed
: Message could not be delivered (checkerr-code
for reason).expired
: Message delivery timed out (often handset off or out of coverage).buffered
: Message is waiting on the carrier network (temporary state).rejected
: Message was rejected by Vonage or the carrier (often due to spam filters, invalid number, or permissions).accepted
: Message accepted by the downstream carrier, delivery pending.unknown
: The final status couldn't be determined.err-code
meanings.delivered
status. Sometimes, a message might be delivered without a DLR ever arriving.messageId
out of sequence (e.g.,buffered
thendelivered
). Ensure your status update logic handles this gracefully (e.g., only update if the new status is considered 'more final' than the current one, perhaps using timestamps).message-id
. You might receive separate DLRs for each part. You may need logic to determine the overall delivery status based on the statuses of all parts.9. Implementing Performance Optimizations
For high-volume applications, optimize your webhook handler.
2xx
immediately using the "respond first" pattern.2xx
.Messages
table (or equivalent) has an index on thevonage_message_id
column (as shown in the conceptual schema) for fast lookups when processing DLRs.server.js
application behind a load balancer and run multiple instances (e.g., using PM2 cluster mode or container orchestration like Kubernetes). Ensure your processing logic (especially database updates) can handle concurrent operations correctly (idempotency, transactions).10. Adding Monitoring, Observability, and Analytics
Understand how your SMS sending and DLR processing are performing.
/health
endpoint inserver.js
is a basic start. Production health checks might involve checking database connectivity or queue status.index.js
) and DLR processing (server.js
).vonage.sms.send()
.delivered
,failed
, etc.)./webhooks/dlr
).try...catch
block inserver.js
).prom-client
for Prometheus metrics or platform-specific monitoring agents (Datadog Agent, etc.).index.js
andserver.js
, especially the internal DLR processing errors.11. Troubleshooting and Caveats
Common issues and things to be aware of:
node server.js
running? Any startup errors? Check the console logs for incoming requests and processing logs.node index.js
. Watch both theserver.js
console and the ngrok console (http://127.0.0.1:4040
) for incoming requests to/webhooks/dlr
.VONAGE_API_KEY
andVONAGE_API_SECRET
in your.env
file are correct and have no extra spaces.from
Number: EnsureVONAGE_NUMBER
is a valid, SMS-capable number purchased in your Vonage account and formatted correctly (E.164).2xx
quickly.