Frequently Asked Questions
Use the Vonage Messages API with the Node.js SDK. Initialize the SDK with your API credentials, then use vonage.messages.send()
with to
, from
, and text
parameters. This sends an SMS from your Vonage number to the specified recipient.
The Vonage Messages API is a unified API for sending and receiving messages across various channels, including SMS. It offers more flexibility and features compared to older Vonage SMS APIs and is used in this guide for two-way SMS communication.
Vonage uses webhooks (publicly accessible URLs) to send real-time notifications to your application whenever an SMS is received on your Vonage virtual number or when delivery status updates. This allows you to process incoming messages and track delivery efficiently.
Use the Vonage Messages API for projects requiring SMS capabilities, such as building SMS marketing campaigns, setting up automated notifications, implementing two-factor authentication, or creating interactive SMS services.
Yes, create a webhook endpoint in your Node.js/Express application. When your Vonage number receives an SMS, Vonage sends an HTTP POST request to this endpoint. Ensure the path you define in your server matches the Inbound URL configured in your Vonage application.
In the Vonage Dashboard, create a new application, enable the Messages capability, generate public/private keys (securely store the private key), then configure Inbound and Status URLs pointing to your application's webhooks.
ngrok creates a secure tunnel to your local development server, providing a temporary public HTTPS URL. This is essential for testing webhooks locally because Vonage needs to reach your server, which would otherwise be inaccessible from the internet.
Use Express.js to create a POST route that matches the Inbound URL in your Vonage application settings. Log the request body and process incoming message data, ensuring a quick '200 OK' response to prevent Vonage retries.
The status webhook provides updates on the delivery status of outbound SMS messages. It sends information like 'delivered', 'failed', 'rejected', allowing you to track message delivery success or troubleshoot issues.
Use a library like async-retry
to handle temporary network issues or Vonage API failures. Configure the retry attempts, delay, and exponential backoff. Ensure non-recoverable errors (like incorrect number formats) do not trigger infinite retries.
A 200 OK response tells Vonage that your server successfully received the webhook. Without it, Vonage will retry sending the request, potentially leading to duplicate message processing and errors in your application.
Use signed webhooks (JWT verification) for secure communication between Vonage and your application. Validate all user inputs and use secure storage for your API keys. Also, implement rate limiting to prevent abuse.
Store your Vonage Application ID and private key securely. During development, you can store these in a .env file (listed in .gitignore) and load them with the dotenv library. For production, use your platform's secure secret storage.
A suitable schema should log inbound/outbound messages, track delivery statuses (using the message_uuid), manage recipients (for campaigns), and store campaign details. Using an ORM like Prisma simplifies database interactions.
Build SMS Marketing Campaigns with Node.js, Express & Vonage Messages API
Build a complete SMS marketing campaign system using Node.js, Express.js, and the Vonage Messages API. This guide provides a step-by-step walkthrough for creating applications that send and receive SMS messages, forming the foundation for SMS marketing campaigns, notifications, or two-factor authentication.
Learn how to implement two-way SMS messaging, handle webhooks for incoming messages, ensure TCPA compliance with new 2025 opt-out rules, and secure your application with JWT signature verification. You'll build a production-ready system capable of handling thousands of messages while respecting carrier rate limits and regulatory requirements.
What You'll Build:
Prerequisites: Basic knowledge of Node.js, Express.js, APIs, webhooks, and environment variable management. Familiarity with async/await patterns and RESTful APIs recommended.
Project Overview and Goals
Goal: To create a robust Node.js service that leverages the Vonage Messages API for sending outgoing SMS and handling incoming SMS messages via webhooks, providing a reusable base for SMS-driven applications.
Problem Solved: This guide addresses the need for developers to integrate SMS capabilities into their Node.js applications reliably and efficiently, handling both sending and receiving workflows required for interactive communication or campaign responses.
Technologies Used:
.env
file intoprocess.env
.System Architecture:
Prerequisites:
npm install -g @vonage/cli
1. Setting Up the Project
Let's initialize our Node.js project and install the necessary dependencies.
1. Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
2. Initialize Node.js Project: This creates a
package.json
file to manage dependencies and project metadata.3. Install Dependencies: We need Express for the web server, the Vonage SDK to interact with the API, and
dotenv
for managing environment variables securely.4. Create Project Files: Create the main files for our application logic and environment variables.
index.js
: Will contain the logic for sending SMS messages.server.js
: Will contain the Express server logic for receiving SMS messages via webhooks..env
: Will store sensitive credentials like Application ID, Private Key path/content, and phone numbers. Never commit this file to version control..gitignore
: Specifies intentionally untracked files that Git should ignore.5. Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.6. Project Structure: Your project directory should now look like this:
This basic structure separates the sending logic (
index.js
, often run as a one-off script) from the receiving server logic (server.js
, typically run as a long-running process) for clarity and modularity, although they could be combined in a single file for simpler applications. Using.env
ensures credentials are kept out of the codebase.2. Implementing Core Functionality: Sending SMS
We'll start by implementing the code to send an SMS message using the Vonage Messages API.
1. Configure Environment Variables: Open the
.env
file and add placeholders for your Vonage Application credentials and numbers. You will obtain these values in Section 4 (Integrating with Vonage). Note that the Vonage Node SDK uses Application ID and Private Key for authentication with the Messages API.2. Write Sending Logic (
index.js
): Editindex.js
to initialize the Vonage SDK and use themessages.send()
method.Explanation:
require('dotenv').config();
: Loads the variables from your.env
file intoprocess.env
.new Vonage({...})
: Initializes the SDK using your Application ID and the private key. The logic prioritizes using the key content fromVONAGE_PRIVATE_KEY_CONTENT
if set (common for deployments), otherwise it uses the file path fromVONAGE_APPLICATION_PRIVATE_KEY_PATH
. The Messages API primarily uses Application ID and Private Key for authentication when using the SDK.vonage.messages.send(...)
: This is the core function for sending messages.new SMS({...})
: We create an SMS message object specifying theto
,from
, andtext
parameters. The SDK handles structuring the request correctly for the Messages API.async/await
for cleaner handling of the asynchronous API call.messageUuid
on success. Thecatch
block logs detailed error information if the request fails, which is crucial for debugging.3. Building an API Layer: Receiving SMS via Webhook
To receive SMS messages, Vonage needs a publicly accessible URL (a webhook) to send HTTP POST requests to whenever your virtual number receives a message. We'll use Express to create this endpoint.
1. Write Server Logic (
server.js
): Editserver.js
to set up an Express server listening for POST requests on a specific path.Explanation:
express.json()
is essential for parsing the JSON payload sent by Vonage webhooks.express.urlencoded()
is included for general robustness./webhooks/inbound
): This specific path (POST
) listens for incoming messages. The path name can be customized, but it must match the Inbound URL configured in your Vonage application.req.body
) is logged. This is crucial for understanding the data structure Vonage sends. Specific fields like sender (from
), recipient (to
), message text (text
), andmessage_uuid
are logged individually./webhooks/status
): A separate endpoint to receive delivery receipts and status updates for outgoing messages you sent. This is vital for tracking message delivery.res.status(200).send('OK')
: This is critical. You must respond to Vonage with a 2xx status code (usually 200 OK) quickly to acknowledge receipt of the webhook. If Vonage doesn't receive this, it will assume the delivery failed and retry, leading to duplicate processing on your end./health
endpoint is good practice for monitoring systems to check if the server is running.PORT
environment variable or defaults to 3000.4. Integrating with Vonage: Configuration Steps
Now, let's configure your Vonage account and link it to the application code.
1. Purchase a Vonage Virtual Number:
Numbers
>Buy numbers
.14155550100
) into theVONAGE_NUMBER
variable in your.env
file.2. Create a Vonage Application: This application links your number, credentials, and webhook URLs together.
Applications
>Create a new application
.Node SMS App - Dev
).Generate public and private key
button. This will automatically download theprivate.key
file. Save this file securely in your project directory (or another secure location for local development). UpdateVONAGE_APPLICATION_PRIVATE_KEY_PATH
in your.env
file to point to the correct path (e.g.,./private.key
if it's in the root). The public key is stored by Vonage. (For deployment, you'll likely use the key content viaVONAGE_PRIVATE_KEY_CONTENT
- see Section 12 if available, or adapt based on deployment strategy).Messages
capability.YOUR_NGROK_HTTPS_URL/webhooks/inbound
(You'll getYOUR_NGROK_HTTPS_URL
in the next step). Set the method toPOST
.YOUR_NGROK_HTTPS_URL/webhooks/status
. Set the method toPOST
.Generate new application
.VONAGE_APPLICATION_ID
variable in your.env
file.3. Link Your Number to the Application:
Numbers
>Your numbers
.Link
button (or edit icon) next to the number.Node SMS App - Dev
) from theForward to Application
dropdown under theMessages
capability.Save
.4. Set Default SMS API (Crucial):
API settings
.Default SMS Setting
section.Messages API
as the default handler for SMS. This ensures incoming messages use the Messages API format and webhooks.Save changes
.5. Run ngrok: Now that the server code is ready, expose your local server to the internet using ngrok so Vonage can reach your webhooks. Run this in a separate terminal window.
Forwarding
URL usinghttps
. It looks something likehttps://<random_string>.ngrok.io
.Applications
> Your App Name > Edit). Paste the copied ngrok HTTPS URL into theInbound URL
andStatus URL
fields, making sure to append the correct paths (/webhooks/inbound
and/webhooks/status
).https://a1b2c3d4e5f6.ngrok.io/webhooks/inbound
https://a1b2c3d4e5f6.ngrok.io/webhooks/status
Your local development environment is now configured to communicate with Vonage.
5. Implementing Error Handling, Logging, and Retry Mechanisms
Robust applications need proper error handling and logging.
Error Handling:
Sending (
index.js
): Thetry...catch
block already handles errors during thevonage.messages.send()
call. It logs detailed error information fromerr.response.data
if available. For production, consider:err.response.status
or error codes withinerr.response.data
to handle specific issues differently (e.g., invalid number format, insufficient funds).async-retry
orp-retry
. Caution: Do not retry errors like invalid number format (4xx errors) indefinitely.First, install the library:
Then, you can implement retry logic like this:
Receiving (
server.js
):app.post
) in atry...catch
block to handle unexpected errors during message processing (e.g., database errors).res.status(200).send('OK')
happens outside the maintry...catch
or within afinally
block if you need to guarantee it, even if your internal processing fails. This prevents Vonage retries for issues unrelated to receiving the webhook itself.Logging:
The current
console.log
is suitable for development.For production, use a structured logging library like Pino or Winston:
info
,warn
,error
,debug
.Install Pino:
Integrate Pino:
Retry Mechanisms:
2xx
response within a certain timeout (usually a few seconds). It uses an exponential backoff strategy. This handles temporary network issues between Vonage and your server. Your primary responsibility is to respond200 OK
promptly.async-retry
, implement this for sending SMS if you need resilience against temporary API failures on the Vonage side or network issues from your server to Vonage.6. Creating a Database Schema and Data Layer (Conceptual)
While this basic guide doesn't implement a database, a real-world application (especially for campaigns) needs one.
Purpose:
Conceptual Schema (using Prisma as an example ORM):
Implementation Steps (High-Level):
pg
,mysql2
).npm install @prisma/client
andnpm install -D prisma
.npx prisma init
.prisma/schema.prisma
.DATABASE_URL
: Add your database connection string to.env
.npx prisma migrate dev --name init
to create tables.npx prisma generate
.index.js
andserver.js
to interact with the database.Note: These code snippets assume you have fully set up Prisma (schema, database connection, migrations, client generation) as outlined in the high-level steps above. They will not run correctly without that setup.
7. Adding Security Features
Securing your application and webhook endpoints is crucial.
Authorization
header (e.g.,Bearer <token>
). The@vonage/server-sdk
may offer helper functions for this (check its documentation forverifySignature
or similar methods)./webhook
).joi
orzod
can help define schemas for validation..env
file (added to.gitignore
). For production deployments, use secure environment variable management provided by your hosting platform (e.g., AWS Secrets Manager, Google Secret Manager, Heroku Config Vars).express-rate-limit
) to prevent abuse or denial-of-service attacks.Frequently Asked Questions About SMS Marketing with Node.js and Vonage
How do I handle STOP keyword opt-outs for TCPA compliance?
Implement keyword detection in your webhook handler by checking if
req.body.text.trim().toUpperCase() === 'STOP'
. When detected, immediately mark the sender's number as opted-out in your database (using Prisma'supsert
or similar). New TCPA rules effective April 11, 2025 require you to stop all communications within 10 business days and honor opt-outs through "any reasonable means," not just the STOP keyword. Send a confirmation message within 5 minutes containing no promotional content.What Node.js version should I use for this Express SMS application?
Use Node.js 22.x (Active LTS until October 2025) or Node.js 20.x (maintenance mode) for production deployments. This guide uses Express.js 5.1.0, which requires Node.js 18+. Node.js 18.x reaches end-of-life on April 30, 2025, so upgrade to Node.js 22 for extended support (maintenance until April 2027).
How do I verify Vonage webhook signatures with JWT?
Vonage uses JWT Bearer Authorization (HMAC-SHA256) for webhook signing. Extract the JWT from the
Authorization: Bearer <token>
header, decode it using your signature secret from the dashboard (minimum 32 bits recommended), verify thepayload_hash
claim matches an SHA-256 hash of the request body to prevent replay attacks, and check theiat
(issued at) timestamp to reject stale tokens. The@vonage/server-sdk
may offer helper functions likeverifySignature
.What are the E.164 phone number format requirements for Vonage?
E.164 format requires: no leading
+
or00
, starts with country code, maximum 15 digits, and no trunk prefix0
after country code. Example: US number212 123 1234
becomes14155552671
. Vonage requires proper E.164 formatting for all API calls. Use validation libraries likegoogle-libphonenumber
or implement regex validation before sending.What are the alternatives to ngrok for webhook testing?
Top ngrok alternatives in 2025 include: Pinggy (unlimited bandwidth, UDP support, no signup required), Localtunnel (npm package, quick testing), Cloudflare Tunnel (no bandwidth limits on free plan), Zrok (open-source, zero-trust networking), and Tunnelmole (open-source, works with Node, Docker, Python). For CI/CD webhook testing, consider InstaTunnel or LocalXpose.
How do I handle SMS marketing campaign compliance in 2025?
Follow TCPA compliance requirements: obtain prior express written consent before sending marketing messages, include clear disclosures about message type and charges ("message and data rates may apply"), provide opt-out instructions with every message, process opt-outs within 10 business days, maintain 95%+ deliverability rates, send 2-4 messages per month maximum, follow 8 AM to 9 PM local time guidelines, and register for 10DLC for US messaging to improve deliverability (up to 60 msg/sec vs. 1 msg/sec unverified).
What Prisma version should I use for message tracking?
Use Prisma ORM 6.16.0 or later (latest as of 2025). The Rust-free architecture is production-ready with ~90% smaller bundle size, faster queries, and lower CPU footprint. The new Query Compiler replaces legacy query engine with TypeScript implementation. For edge runtimes like Vercel Edge, use the
prisma-client
generator withengineType: "client"
. Prisma 6.16.0 stabilizeddriverAdapters
andqueryCompiler
features.How do I implement retry logic for failed SMS sends?
Install
async-retry
(npm install async-retry
) and wrapvonage.messages.send()
with retry logic: setretries: 3
for 3 attempts,minTimeout: 1000
for initial 1-second delay,factor: 2
for exponential backoff. Important: Don't retry 4xx client errors (invalid number format, insufficient funds) – only retry network errors and 5xx server errors. Checkerr.response.status
to determine if error is retriable. Usebail()
to stop retrying non-recoverable errors.What database indexes should I create for SMS message tracking?
Create indexes on frequently queried columns in your Prisma schema:
@@index([vonageMessageUuid])
for webhook lookups,@@index([toNumber])
and@@index([fromNumber])
for recipient/sender queries,@@index([status])
for filtering by delivery status (submitted, delivered, failed), and@@index([submittedAt])
for time-based queries. Add composite index@@index([toNumber, status])
for efficient opt-out status checks.How do I handle SMS delivery receipts from Vonage?
Create a separate webhook endpoint (e.g.,
/webhooks/status
) to receive Delivery Receipts (DLRs). Extractmessage_uuid
,status
(delivered, failed, rejected),timestamp
,error.code
,price
, andcurrency
from the request body. Useprisma.message.update()
withwhere: { vonageMessageUuid: message_uuid }
to update the corresponding database record. Always respond with200 OK
quickly to prevent Vonage retries. Track statuses: submitted → delivered/failed/rejected.Next Steps for Production SMS Marketing Campaigns
Now that you've built your SMS foundation, enhance it with these production features:
Implement JWT Webhook Verification – Secure your
/webhooks/inbound
and/webhooks/status
endpoints with JWT signature validation using the HMAC-SHA256 secret from your dashboard. Verifypayload_hash
andiat
claims to prevent replay attacks.Add TCPA-Compliant Opt-Out System – Create
OptOut
Prisma model withphoneNumber
,optOutDate
, andmethod
fields. Process not just "STOP" but "any reasonable means" including informal messages. Implement 10-business-day deadline enforcement and 4-year record retention.Implement 10DLC Registration – Register your 10DLC Brand and Campaign through the Vonage dashboard to improve US deliverability rates (60 msg/sec vs. 1 msg/sec unverified). Provide business verification, campaign use case, and sample messages for carrier approval.
Add Structured Logging with Pino – Replace
console.log
with Pino for JSON-structured logs:logger.info({ webhook: 'inbound', from: number }, 'Received SMS')
. Configure log levels (info
,warn
,error
) and usepino-pretty
for development readability.Create Campaign Management System – Build Prisma models for
Campaign
,Recipient
, andCampaignMessage
. Track campaign progress, schedule sends, segment recipients, and monitor performance metrics (sent, delivered, failed, opt-out rates).Implement Rate Limiting – Use
express-rate-limit
middleware to protect webhook endpoints:app.use('/webhooks', rateLimit({ windowMs: 60000, max: 100 }))
. Prevent abuse and DoS attacks while allowing legitimate Vonage traffic.Add Delivery Analytics Dashboard – Query Prisma for aggregate metrics: total sent, delivery rate (
status: 'delivered'
), failure rate, average delivery time, opt-out percentage. Group by date range, campaign, or recipient segment.Configure Webhook Retry Handling – Vonage automatically retries failed webhooks with exponential backoff. Ensure idempotency by checking
vonageMessageUuid
uniqueness before creating database records. Useprisma.message.upsert()
to handle duplicate webhook deliveries.Implement HELP Keyword Auto-Response – Detect "HELP" keyword in inbound messages and automatically reply with support information: "For help, visit example.com/support or call 555-0100. Reply STOP to unsubscribe. Msg&data rates may apply."
Add Environment-Specific Configurations – Create separate
.env.development
,.env.staging
, and.env.production
files. Use different Vonage numbers, database URLs, and logging levels per environment. Implementdotenv-flow
for automatic environment loading.Additional Resources: