code examples
code examples
Vonage SMS API Node.js Tutorial: Build Marketing Campaigns with Express (2025 Guide)
Learn how to build SMS marketing campaigns with Vonage Messages API, Node.js, and Express. Complete tutorial with webhooks, TCPA compliance, rate limiting, bulk messaging, and production deployment best practices.
Vonage SMS Node.js Tutorial: Send & Receive Messages with Express API (2025)
Learn how to build SMS marketing campaigns with Vonage Messages API, Node.js, and Express. This comprehensive 2025 tutorial teaches you to send bulk SMS messages, handle webhooks for two-way messaging, implement TCPA-compliant opt-out handling, and deploy production-ready SMS platforms.
By completing this Vonage SMS API tutorial, you'll build a functional marketing messaging platform that:
- Sends SMS messages programmatically via the Vonage Messages API
- Receives incoming SMS messages through webhooks
- Logs message statuses and tracks delivery
- Implements TCPA-compliant opt-out handling (required for marketing campaigns)
- Respects API rate limits (30 requests/second)
This solution addresses common needs for SMS notifications, alerts, two-way communication, and marketing campaigns while maintaining compliance with US regulations.
Frequently Asked Questions
How do I send SMS messages with Vonage API in Node.js?
Install the @vonage/server-sdk package, initialize the Vonage client with your API credentials and application ID, then use vonage.messages.send() with channel set to 'sms'. The complete code example in Section 3 shows the full implementation including authentication with private keys and error handling.
What's the difference between Vonage SMS API and Messages API?
The Messages API is the newer, unified API supporting SMS, MMS, WhatsApp, and other channels. It uses application-based authentication with private keys and provides richer webhook data. The older SMS API uses API key/secret authentication only. Set your default to Messages API in the Vonage Dashboard under Account → API settings.
How much does Vonage SMS pricing cost per message in the US?
Vonage charges $0.0075 per outbound SMS and $0.0069 per inbound SMS in the US (as of 2025). A complete two-way conversation costs approximately $0.0144. Volume discounts available through account managers for high-volume senders. Check the official Vonage pricing page for current rates.
What are Vonage API rate limits for SMS?
Vonage defaults to 30 requests per second (2,592,000 SMS per day). The Messages API sandbox limits to 1 message per second and 100 messages per month. Exceeding limits returns HTTP 429 or status code 1 (Throttled). Implement rate limiting with libraries like p-limit as shown in Section 8.
How do I set up SMS webhooks with Vonage Messages API in Node.js?
Create an Express server with POST endpoints at /webhooks/inbound (for incoming messages) and /webhooks/status (for delivery status). Expose your local server with ngrok or Cloudflare Tunnel, then configure these URLs in your Vonage Application settings. Always return HTTP 200 to acknowledge webhook receipt. See Section 4 for complete implementation.
Is TCPA compliance required for Vonage SMS marketing campaigns?
Yes, if you're sending marketing messages to US recipients. The FCC's April 11, 2025 Opt-Out Rule requires honoring opt-outs within 10 business days and recognizing diverse opt-out language beyond "STOP" (like "Leave me alone" or "Don't text me"). Penalties range from $500-$1,500 per violation. Section 7 provides full compliance requirements and implementation code.
What's better for Vonage webhooks: ngrok or Cloudflare Tunnel?
For development, both work. ngrok offers quicker setup but free tier limits to 1 GB/month bandwidth with URL changes on restart. Cloudflare Tunnel provides unlimited bandwidth, free for up to 50 users, built-in DDoS protection, and more stable URLs. For production, use neither – deploy to a hosting platform with a persistent domain. See Section 5 for detailed comparison and setup instructions.
How do I implement opt-out handling for SMS marketing with Vonage?
Detect opt-out keywords in your /webhooks/inbound handler (STOP, UNSUBSCRIBE, "leave me alone," etc.), store the opt-out in your database with timestamp, remove the number from marketing lists, and send a confirmation message within 5 minutes. Maintain records for 4 years per TCPA requirements. Section 7 includes a complete code example.
How do I send bulk SMS messages with Vonage API without rate limiting errors?
Yes, use the p-limit library to throttle concurrent requests to 30 per second (matching Vonage's default limit). The bulk-send implementation in Section 8 shows how to send to multiple recipients while respecting rate limits and handling individual message errors gracefully.
What Node.js version works with Vonage Messages API?
Vonage recommends Node.js LTS (Long Term Support) versions. As of 2025, @vonage/server-sdk@3.24.1 supports Node.js 14+, with Node.js 18 LTS or 20 LTS recommended for production deployments. Check the official Vonage SDK documentation for current compatibility matrix.
What You'll Build: SMS Marketing Platform with Vonage API
What You're Building: A Node.js application using the Express framework with two main functions:
- A script to send SMS messages using the Vonage Messages API
- An Express server to receive incoming SMS messages via webhooks
Problem Solved: Enables programmatic SMS communication for automated messages, user replies, inbound messages, and marketing campaigns with regulatory compliance.
Technologies Used:
- Node.js: JavaScript runtime for server-side applications. Chosen for asynchronous operations, npm ecosystem, and I/O-bound tasks like API interactions.
- Express: Minimal Node.js web framework. Chosen for simplicity in setting up servers and handling HTTP webhook requests.
- Vonage Messages API: Unified API for multi-channel messaging (SMS, MMS, WhatsApp). Chosen for robust features, global reach, and developer-friendly SDK.
- Vonage Node.js SDK (
@vonage/server-sdk@3.24.1): Simplifies Vonage API interaction. Current version actively maintained with regular updates. - ngrok or Cloudflare Tunnel: Tools to expose local servers to the internet. Essential for testing webhooks during development.
dotenv: Loads environment variables from.envfiles, keeping credentials secure.
Vonage SMS Pricing (US):
- Outbound SMS: $0.0075 per message
- Inbound SMS: $0.0069 per message
- Total conversation cost: ~$0.0144 per send/receive pair
Source: Vonage Communications APIs Pricing, January 2025
System Architecture:
graph LR
subgraph Your Application
A[Node.js Send Script] -- Sends SMS --> B(Vonage Node SDK);
C[Express Server] -- Listens on Port 3000 --> D{Webhook Endpoint (/webhooks/inbound)};
D -- Receives Inbound SMS --> E[Log Incoming Message];
F[Express Server] -- Listens on Port 3000 --> G{Webhook Endpoint (/webhooks/status)};
G -- Receives Status Updates --> H[Log Message Status];
end
subgraph Internet / Vonage Platform
B -- API Call --> I(Vonage Messages API);
I -- Delivers SMS --> J(User's Phone);
J -- Sends Reply SMS --> K(Vonage Virtual Number);
K -- Forwards via Webhook --> L(ngrok Tunnel);
I -- Sends Status via Webhook --> L;
end
subgraph Developer Machine
L -- Forwards Traffic --> C;
end
style Your Application fill:#f9f,stroke:#333,stroke-width:2px
style J fill:#ccf,stroke:#333,stroke-width:2px- Sending: Your Node.js script uses the Vonage SDK to call the Messages API, which delivers the SMS.
- Receiving: A user replies, Vonage receives it on your virtual number, sends the message data via an HTTP POST request (webhook) to your ngrok URL, ngrok forwards it to your local Express server, and your server handles the data. Status updates follow a similar path.
Prerequisites:
- Node.js and npm: LTS version recommended. Download Node.js
- Vonage API Account: Sign up at Vonage. Free trial available.
- Vonage API Key and Secret: Find on the Vonage API Dashboard
- Vonage Virtual Phone Number: Purchase from Vonage Dashboard (Numbers > Buy numbers). Ensure SMS capability in your target region.
- ngrok or Cloudflare Tunnel: Choose one:
- ngrok: Free tier includes 1 GB/month bandwidth. Download ngrok
- Cloudflare Tunnel: Free for up to 50 users, unlimited bandwidth. Cloudflare Zero Trust
- Vonage CLI (Optional but Recommended): Install via
npm install -g @vonage/cli. Useful for managing applications and numbers.
Rate Limits: Vonage APIs default to 30 requests/second (2,592,000 SMS/day). Messages API sandbox limited to 1 message/second, 100 messages/month. Exceeding limits returns status code 1 (Throttled).
Source: Vonage API Support, Rate Limits Documentation
1. Set Up Your Vonage SMS Node.js Project
Create your project directory, initialize Node.js, and install dependencies.
-
Create Project Directory: Open your terminal and create a new directory:
bashmkdir vonage-sms-app cd vonage-sms-app -
Initialize Node.js Project: Create a
package.jsonfile with default settings:bashnpm init -y -
Install Dependencies: Install required packages:
bashnpm install @vonage/server-sdk express dotenv@vonage/server-sdk: Official Vonage library for Node.js (v3.24.1 current)express: Web server framework for webhooksdotenv: Loads environment variables from.envintoprocess.env
-
Create Project Files:
bash# For Linux/macOS touch send-sms.js server.js .env .gitignore # For Windows (Command Prompt) type nul > send-sms.js type nul > server.js type nul > .env type nul > .gitignore # For Windows (PowerShell) New-Item send-sms.js -ItemType File New-Item server.js -ItemType File New-Item .env -ItemType File New-Item .gitignore -ItemType File -
Configure
.gitignore: It's crucial to prevent sensitive information and unnecessary files from being committed to version control (like Git). Add the following lines to your.gitignorefile:text# Environment variables .env # Node dependencies node_modules/ # Log files *.log # OS generated files .DS_Store Thumbs.db # Vonage private key private.key *.key -
Set Up Environment Variables (
.env): Open the.envfile and add placeholders for your Vonage credentials and configuration. We will obtain these values in the next steps. Never commit this file to version control.dotenv# Vonage API Credentials (from Vonage Dashboard) VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Credentials (created in the next section) VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root # Vonage Number and Recipient VONAGE_FROM_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER TO_NUMBER=RECIPIENT_PHONE_NUMBER # e.g., 14155550100 # Server Port PORT=3000VONAGE_API_KEY,VONAGE_API_SECRET: Found directly on your Vonage dashboard homepage.VONAGE_APPLICATION_ID,VONAGE_PRIVATE_KEY_PATH: Will be generated when creating a Vonage Application.VONAGE_FROM_NUMBER: The Vonage virtual number you purchased (use E.164 format, e.g.,12015550101).TO_NUMBER: The destination phone number for sending test messages (use E.164 format).PORT: The local port your Express server will listen on.
Important: Remember to replace these placeholder values (
YOUR_API_KEY,YOUR_API_SECRET, etc.) with your actual credentials and numbers obtained from the Vonage dashboard before running the application.
Project Structure:
Your project directory should now look like this:
vonage-sms-app/
├── node_modules/
├── .env
├── .gitignore
├── package-lock.json
├── package.json
├── send-sms.js
└── server.js
# (private.key will be added later)
This structure separates the sending logic (send-sms.js) from the receiving logic (server.js) and keeps configuration secure in .env.
2. Configure Vonage Messages API for SMS Marketing
Configure Vonage to use the Messages API and create a Vonage Application for SMS interactions.
-
Set Default SMS API to Messages API: Vonage offers two SMS APIs (legacy SMS API and newer Messages API) with different webhook formats. Set Messages API as default:
- Log in to the Vonage API Dashboard
- Navigate to Account → API settings
- Scroll to SMS settings
- Under
Default SMS Setting, select Messages API - Click Save changes
-
Create a Vonage Application:
- In the Vonage Dashboard, navigate to Applications → Create a new application
- Enter an Application name (e.g.,
NodeJS SMS App Tutorial) - Click Generate public and private key. This automatically downloads a
private.keyfile. Save this file in the root of yourvonage-sms-appproject directory. The public key is stored by Vonage. - Enable Messages capability by toggling it on
- You'll see fields for Inbound URL and Status URL. We need a publicly accessible URL here. We'll use ngrok for this during development. For now, enter temporary placeholders like
https://example.com/webhooks/inboundandhttps://example.com/webhooks/status. We will update these later once ngrok is running. - Scroll down and click Generate new application
- You will be taken to the application's page. Copy the Application ID displayed near the top
-
Update
.envFile: Now, update your.envfile with the actual values:- Paste your
VONAGE_API_KEYandVONAGE_API_SECRETfrom the dashboard homepage - Paste the
VONAGE_APPLICATION_IDyou just copied - Ensure
VONAGE_PRIVATE_KEY_PATHis set to./private.key(assuming you saved the downloaded key file in the project root) - Fill in
VONAGE_FROM_NUMBERwith your Vonage virtual number - Fill in
TO_NUMBERwith your personal phone number for testing
- Paste your
-
Link Your Vonage Number to the Application: Incoming messages to your Vonage number need to be routed to the correct application's webhook. For this SMS tutorial, linking the number under ""Messaging"" is the critical step.
- Navigate to Numbers → Your numbers in the dashboard
- Find your Vonage virtual number
- Click the Edit icon (pencil) next to the number
- In the Messaging section's dropdown, select Application and choose your newly created application (e.g.,
NodeJS SMS App Tutorial). This ensures incoming SMS messages trigger your application's inbound webhook - (Optional for this tutorial) In the Voice section, you could also link the application if you intended to handle voice calls, but it's not required for SMS-only functionality
- Click Save
Configuration is complete. We have told Vonage to use the Messages API, created an application with security keys, and linked our number to route SMS messages to this application's webhooks.
3. Send SMS Messages with Vonage Node.js SDK
Write code to send SMS messages using the Vonage Node.js SDK.
-
Edit
send-sms.js:javascript// send-sms.js 'use strict'; // Load environment variables from .env file require('dotenv').config(); // Import the Vonage Server SDK const { Vonage } = require('@vonage/server-sdk'); const { MESSAGES_SANDBOX_URL } = require('@vonage/server-sdk'); // Use if sandbox needed const fs = require('fs'); // Require file system module to read the private key // --- Configuration --- const vonageApiKey = process.env.VONAGE_API_KEY; const vonageApiSecret = process.env.VONAGE_API_SECRET; const vonageAppId = process.env.VONAGE_APPLICATION_ID; const privateKeyPath = process.env.VONAGE_PRIVATE_KEY_PATH; const fromNumber = process.env.VONAGE_FROM_NUMBER; const toNumber = process.env.TO_NUMBER; // Basic validation if (!vonageApiKey || !vonageApiSecret || !vonageAppId || !privateKeyPath || !fromNumber || !toNumber) { console.error('Error: Missing required environment variables. Check your .env file.'); process.exit(1); // Exit if configuration is incomplete } // Read the private key file let privateKey; try { privateKey = fs.readFileSync(privateKeyPath); } catch (err) { console.error(`Error reading private key file at ${privateKeyPath}:`, err); process.exit(1); } // --- Initialize Vonage Client --- // Initialize Vonage Client using Application ID and Private Key for Messages API features (including status webhooks). const vonage = new Vonage({ apiKey: vonageApiKey, // Still useful for some account-level operations, but Auth primarily via AppID/Key for Messages apiSecret: vonageApiSecret, // As above applicationId: vonageAppId, privateKey: privateKey, // Uncomment below for sandbox testing (requires specific setup) // apiHost: MESSAGES_SANDBOX_URL }); // --- Define Message Content --- const messageText = `Hello from Vonage and Node.js! (${new Date().toLocaleTimeString()})`; // --- Send SMS Function --- async function sendSms() { console.log(`Attempting to send SMS from ${fromNumber} to ${toNumber}...`); try { const resp = await vonage.messages.send({ channel: 'sms', message_type: 'text', // Type of message (text, image, etc.) to: toNumber, // Recipient phone number from: fromNumber, // Your Vonage virtual number text: messageText, // The content of the SMS // client_ref: 'my-internal-tracking-id-123' // Optional: For tracking purposes }); console.log('SMS submitted successfully!'); console.log('Message UUID:', resp.message_uuid); } catch (err) { console.error('Error sending SMS:'); // Log detailed error information if available if (err.response) { console.error('Status:', err.response.status); console.error('Status Text:', err.response.statusText); console.error('Data:', err.response.data); // Consult the Vonage API error documentation for details on specific error codes within err.response.data. // Link: https://developer.vonage.com/api-errors/messages } else { console.error(err); } } } // --- Execute Sending --- sendSms();Code Explanation:
require('dotenv').config();: Loads variables from.envintoprocess.envrequire('@vonage/server-sdk'): Imports the Vonage SDKfs.readFileSync(privateKeyPath): Reads the content of your private key file. It's crucial that the content of the key is passednew Vonage({...}): Initializes the Vonage client. For the Messages API involving applications and webhooks (like status updates), providingapplicationIdandprivateKeyis required for authenticationvonage.messages.send({...}): This is the core function for sending messages via the Messages APIchannel: 'sms': Specifies the communication channelmessage_type: 'text': Indicates a standard text messageto,from,text: Define the recipient, sender (your Vonage number), and message content
async/awaitwithtry...catch: Handles the asynchronous nature of the API call and provides basic error logging. We log themessage_uuidon success, which is useful for tracking. Thecatchblock attempts to log detailed error information from the API response
-
Run the Send Script: Ensure your
.envfile contains valid credentials, then run:bashnode send-sms.jsYou should see output confirming SMS submission and a Message UUID. The recipient receives the SMS within seconds.
4. Receive SMS Messages with Webhooks in Express
Set up the Express server to receive incoming SMS messages from Vonage.
-
Edit
server.js:javascript// server.js 'use strict'; // Load environment variables require('dotenv').config(); // Import Express const express = require('express'); const { json, urlencoded } = express; // Import body parsing middleware // --- Configuration --- const port = process.env.PORT || 3000; // Use port from .env or default to 3000 // --- Initialize Express App --- const app = express(); // --- Middleware --- // Enable parsing of JSON request bodies app.use(json()); // Enable parsing of URL-encoded request bodies (standard for webhooks) app.use(urlencoded({ extended: true })); // --- Webhook Endpoints --- // Inbound Message Webhook (POST /webhooks/inbound) app.post('/webhooks/inbound', (req, res) => { console.log('--- Inbound Message Received ---'); console.log('Timestamp:', new Date().toISOString()); console.log('Request Body:', JSON.stringify(req.body, null, 2)); // Pretty print JSON // Extract key information (adjust based on actual payload structure from Messages API) const from = req.body.from?.number || req.body.from; // Handle potential variations const to = req.body.to?.number || req.body.to; const text = req.body.message?.content?.text || req.body.text; const messageUuid = req.body.message_uuid || req.body.message?.message_uuid; // Check both possible locations const timestamp = req.body.timestamp; console.log(`\nMessage Details:`); console.log(` From: ${from}`); console.log(` To: ${to}`); console.log(` Text: ""${text}""`); // Keep quotes for clarity console.log(` Message UUID: ${messageUuid}`); console.log(` Received At: ${timestamp}`); // --- IMPORTANT --- // Vonage expects a 200 OK response to acknowledge receipt of the webhook. // Failure to send a 200 OK will cause Vonage to retry sending the webhook. res.status(200).send('OK'); // Alternatively, use res.status(200).end(); if no body is needed. }); // Message Status Webhook (POST /webhooks/status) app.post('/webhooks/status', (req, res) => { console.log('--- Message Status Received ---'); console.log('Timestamp:', new Date().toISOString()); console.log('Request Body:', JSON.stringify(req.body, null, 2)); // Pretty print JSON // Extract key status information from Messages API status webhook const messageUuid = req.body.message_uuid; const status = req.body.status; const timestamp = req.body.timestamp; const to = req.body.to?.number || req.body.to; const from = req.body.from?.number || req.body.from; const errorCode = req.body.error?.code; const errorReason = req.body.error?.reason; console.log(`\nStatus Details:`); console.log(` Message UUID: ${messageUuid}`); console.log(` To: ${to}`); console.log(` From: ${from}`); console.log(` Status: ${status}`); console.log(` Timestamp: ${timestamp}`); if (errorCode) { console.log(` Error Code: ${errorCode}`); console.log(` Error Reason: ${errorReason}`); } // Acknowledge receipt res.status(200).send('OK'); }); // --- Root Endpoint (Optional: for testing server is running) --- app.get('/', (req, res) => { res.send(`Vonage SMS Webhook Server is running on port ${port}. Listening for POST requests on /webhooks/inbound and /webhooks/status.`); }); // --- Start Server --- app.listen(port, () => { console.log(`Server listening at http://localhost:${port}`); console.log(`Webhook endpoints ready:`); console.log(` Inbound SMS: POST http://localhost:${port}/webhooks/inbound`); console.log(` Status Updates: POST http://localhost:${port}/webhooks/status`); });Code Explanation:
express(): Creates an Express application instanceapp.use(json())andapp.use(urlencoded({ extended: true })): Middleware essential for parsing incoming request bodies. Webhooks often send data as JSON or URL-encoded form dataapp.post('/webhooks/inbound', ...): Defines a route handler forPOSTrequests to/webhooks/inbound. This is where Vonage will send data for incoming SMS messages- We log the entire request body (
req.body) for inspection - We extract key fields like sender (
from), recipient (to), message content (text), and unique ID (message_uuid). The exact structure might vary slightly based on the Messages API payload, so logging the whole body is helpful for debugging res.status(200).send('OK'): Crucially, we send a200 OKHTTP status code back to Vonage. This acknowledges successful receipt. If Vonage doesn't receive a 200, it assumes delivery failed and will retry, potentially causing duplicate processing on your end
- We log the entire request body (
app.post('/webhooks/status', ...): Defines a route handler forPOSTrequests to/webhooks/status. This is where Vonage sends updates about the delivery status of outgoing messages you sent (e.g.,submitted,delivered,failed,rejected)- We log the body and extract relevant status information
- Again, sending
200 OKis mandatory
app.listen(port, ...): Starts the Express server, making it listen for connections on the specified port
5. Exposing the Local Server with ngrok or Cloudflare Tunnel
Your local server (http://localhost:3000) isn't publicly accessible. Choose a tunneling solution:
Option A: ngrok (Quick Setup)
-
Start Your Express Server:
bashnode server.js -
Start ngrok: In a second terminal:
bashngrok http 3000 -
Copy the ngrok Forwarding URL:
ngrokwill display output similar to this:Session Status online Account Your Name (Plan: Free) Version x.x.x Region United States (us-cal-1) Web Interface http://127.0.0.1:4040 Forwarding https://<random-string>.ngrok-free.app -> http://localhost:3000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00Copy the
https://<random-string>.ngrok-free.appURL. This is your public URL. Note: Remember,ngrokprovides a temporary URL suitable only for development and testing. For a production application, you will need a stable, publicly accessible URL provided by your hosting environment. Freengrokaccounts also generate a new random URL each time you restartngrok.ngrok Free Tier Limits (2025):
- 1 GB/month bandwidth
- URL changes on restart
- Limited to 1 domain
Source: ngrok pricing documentation
Option B: Cloudflare Tunnel (Production-Ready Alternative)
-
Install cloudflared:
bash# macOS (Homebrew) brew install cloudflare/cloudflare/cloudflared # Linux wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb sudo dpkg -i cloudflared-linux-amd64.deb # Windows (Chocolatey) choco install cloudflared -
Authenticate:
bashcloudflared tunnel login -
Create and Run Tunnel:
bashcloudflared tunnel --url http://localhost:3000 -
Copy the Cloudflare URL: Copy the
https://<random-string>.trycloudflare.comURL from the output.Cloudflare Tunnel Benefits:
- Free for up to 50 users
- Unlimited bandwidth
- Built-in DDoS protection
- More stable than ngrok free tier
Source: Cloudflare Zero Trust documentation
Update Vonage Application Webhook URLs
For either option:
- Go to your Vonage Application settings (Applications → Your Application)
- Click Edit
- Update webhook URLs with your tunnel URL:
- Inbound URL:
https://<your-tunnel-url>/webhooks/inbound - Status URL:
https://<your-tunnel-url>/webhooks/status
- Inbound URL:
- Click Save changes
Your local server now receives webhooks from Vonage via your chosen tunnel.
6. Verification and Testing
Test both sending and receiving functionality.
-
Test Sending (Again):
- Run
node send-sms.jsin a terminal - Check your phone for the SMS
- Check the terminal where
server.jsis running. You should see logs under--- Message Status Received ---showing the status updates for the message you just sent (e.g.,submitted,delivered)
- Run
-
Test Receiving:
- Using the phone that received the SMS (or any phone), send an SMS message to your Vonage virtual number (
VONAGE_FROM_NUMBERin your.env) - Watch the terminal where
server.jsis running - You should see logs under
--- Inbound Message Received ---containing the details of the message you just sent from your phone
- Using the phone that received the SMS (or any phone), send an SMS message to your Vonage virtual number (
-
Inspect with ngrok Web Interface:
- Open
http://127.0.0.1:4040(the Web Interface URL shown when startingngrok) in your browser - You can see detailed logs of the HTTP requests being forwarded by
ngrok, including headers and bodies, which is very useful for debugging webhook issues
- Open
7. Implement TCPA Compliance for SMS Marketing (Required for US Campaigns)
If you're sending marketing messages or promotional SMS to US recipients, comply with the Telephone Consumer Protection Act (TCPA) and FCC regulations to avoid costly violations.
Critical FCC Opt-Out Rule (Effective April 11, 2025)
New Requirements:
-
10-Day Processing Window: Honor opt-out requests within 10 business days (reduced from 30 days)
-
Expanded Opt-Out Language: Recognize diverse opt-out phrases beyond "STOP":
- "Leave me alone"
- "Don't text me anymore"
- "Please stop"
- "Unsubscribe"
- "Remove me"
- Any message with clear opt-out intent
-
Confirmation Message Rules:
- Send one confirmation message within 5 minutes of opt-out
- Exclude promotional content from confirmation
- If no response, assume full opt-out
-
Record Retention: Maintain opt-out documentation for 4 years (TCPA statute of limitations)
Penalties for Non-Compliance:
- $500-$1,500 per violation (per message)
- No requirement to prove actual damages
- Class-action lawsuit risk
Implementation Example:
Add this to your /webhooks/inbound handler in server.js:
// Enhanced inbound handler with TCPA opt-out detection
app.post('/webhooks/inbound', async (req, res) => {
console.log('--- Inbound Message Received ---');
const from = req.body.from?.number || req.body.from;
const text = (req.body.message?.content?.text || req.body.text || '').toLowerCase().trim();
// TCPA-compliant opt-out detection
const optOutKeywords = [
'stop', 'stopall', 'unsubscribe', 'cancel', 'end', 'quit',
'leave me alone', "don't text me", 'opt out', 'opt-out', 'remove me'
];
const isOptOut = optOutKeywords.some(keyword => text.includes(keyword));
if (isOptOut) {
console.log(`⚠️ OPT-OUT REQUEST from ${from}`);
// TODO: Store opt-out in database with timestamp
// TODO: Remove from marketing lists
// TODO: Send confirmation message (within 5 minutes)
// Example confirmation (implement with your sendSms function):
// await sendConfirmation(from, "You've been unsubscribed. Reply START to resubscribe.");
}
res.status(200).send('OK');
});Best Practices:
- Database Integration: Store opt-outs with timestamps for audit trail
- Cross-Channel Application: Apply opt-outs across SMS, email, and phone
- Staff Training: Ensure team recognizes opt-out intent in manual reviews
- Regular Audits: Review opt-out processes quarterly
Sources:
- FCC Opt-Out Rule (Effective April 11, 2025)
- TCPA Compliance Guidelines, ActiveProspect
- Bryan Cave Leighton Paisner legal analysis
8. Handle Vonage API Rate Limits for Bulk SMS Campaigns
Respect Vonage's API rate limits to avoid throttling errors.
Vonage Rate Limits:
- Default: 30 requests/second (2,592,000 messages/day)
- Sandbox: 1 request/second, 100 messages/month
- Exceeded limit response: HTTP 429 or Status code 1 (Throttled)
Source: Vonage API Support Documentation
Implementation with p-limit:
Install the rate limiting library:
npm install p-limitUpdate your sending logic for bulk campaigns:
// bulk-send-sms.js
'use strict';
require('dotenv').config();
const { Vonage } = require('@vonage/server-sdk');
const pLimit = require('p-limit');
const fs = require('fs');
// Initialize Vonage client (same as send-sms.js)
const privateKey = fs.readFileSync(process.env.VONAGE_PRIVATE_KEY_PATH);
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: privateKey,
});
// Rate limiting: 30 concurrent requests max (matches Vonage's 30 req/sec limit)
const limit = pLimit(30);
async function sendBulkSMS(recipients, messageText) {
const tasks = recipients.map(recipient =>
limit(() =>
vonage.messages.send({
channel: 'sms',
message_type: 'text',
to: recipient,
from: process.env.VONAGE_FROM_NUMBER,
text: messageText,
})
.then(resp => ({
success: true,
recipient,
message_uuid: resp.message_uuid
}))
.catch(err => ({
success: false,
recipient,
error: err.message
}))
)
);
const results = await Promise.all(tasks);
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
console.log(`\nBulk Send Complete:`);
console.log(` ✓ Successful: ${successful}`);
console.log(` ✗ Failed: ${failed}`);
return results;
}
// Example usage
const recipients = ['+14155551234', '+14155555678', '+14155559012'];
const message = 'Hello from our marketing campaign! Reply STOP to unsubscribe.';
sendBulkSMS(recipients, message)
.then(results => console.log('All sends completed'))
.catch(err => console.error('Bulk send error:', err));This implementation prevents exceeding Vonage's rate limits while maximizing throughput.
9. Error Handling and Logging (Enhanced)
The guide uses basic console.log for demonstration. For production:
- Robust Logging: Use libraries like Winston or Pino for structured logging (e.g., JSON format), different log levels (debug, info, warn, error), and configurable outputs (console, file, external logging services)
- SDK Error Handling: The
try...catchblock insend-sms.jscatches errors during the API call. Inspectingerr.response.dataoften provides specific Vonage error codes and messages. Refer to the Vonage API documentation for error code details (link added in the code comments) - Webhook Errors: Ensure your webhook endpoints always return a
200 OK. If your internal processing fails (e.g., database error), log the error thoroughly but still return 200 to Vonage to prevent retries. Implement a separate mechanism (like a queue or background job) to handle failed webhook processing later - Input Validation: Sanitize and validate data received in webhooks (
req.body) before processing it to prevent errors or security issues. Libraries likejoiorexpress-validatorcan help
10. Security Considerations
- Credentials: Never hardcode API keys, secrets, or private keys in your source code. Use environment variables (
.env) and ensure.envand*.keyare in your.gitignore. In production, use secure secret management systems provided by your hosting platform (e.g., AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) - Private Key File Permissions: Ensure your
private.keyfile has restrictive permissions (e.g.,chmod 600on Linux/macOS) so only the owner can read it. This prevents other users on the system from accessing your key - Webhook Security: While not implemented here for simplicity, production webhooks should be secured. Vonage supports Signed Webhooks using JWT. Your server would verify the signature of incoming requests using your Vonage signature secret to ensure they genuinely came from Vonage. See Vonage Signed Webhooks documentation
- Rate Limiting: Protect your webhook endpoints from abuse by implementing rate limiting (e.g., using
express-rate-limit) - Input Sanitization: Always sanitize text input from SMS messages before displaying it or storing it to prevent Cross-Site Scripting (XSS) or other injection attacks if this data is ever rendered in a web context
11. Troubleshooting and Caveats
- Incorrect Credentials: Verify
VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_ID, and the content/path ofVONAGE_PRIVATE_KEY_PATHin.env. Ensuredotenvis loading correctly (e.g.,require('dotenv').config()is called early) - ngrok Issues:
- Ensure
ngrokis running and forwarding to the correct port (3000) - Ensure the
ngrokURL in the Vonage Application settings is correct (HTTPS) and includes the/webhooks/inboundor/webhooks/statuspath - Free
ngrokURLs change on restart – update Vonage settings if you restartngrok - Firewalls might block
ngroktraffic - Remember
ngrokis for development/testing; use a stable public URL in production
- Ensure
- Number Not Linked: Ensure your Vonage number is correctly linked to your Vonage Application under the Messaging settings in the dashboard
- API Default Setting: Verify the
Default SMS Settingin Vonage account settings is set toMessages API. Using the wrong API setting will result in webhook payloads your code doesn't expect - Missing
200 OK: If webhooks seem to arrive multiple times or Vonage reports failures, ensure your/webhooks/inboundand/webhooks/statushandlers are reliably sendingres.status(200).send('OK')orres.status(200).end(). Check thengrokweb interface (http://127.0.0.1:4040) for the responses your server sent - Alphanumeric Sender IDs: Sending
froma name (e.g.,MyApp) instead of a number is supported in some countries but not others (like the US/Canada). Check Vonage country-specific guidelines. Use your Vonage number for maximum compatibility - Message Encoding: Special characters or emoji might require
type: 'unicode'in thesendSmsoptions (though the Messages API generally handles this well). Test thoroughly - Rate Limits: Vonage applies rate limits to API calls and message sending. Check their documentation if you encounter throttling errors (often HTTP
429)
12. Deploy Your SMS Marketing Platform to Production
Deploy the server.js component to production:
- Hosting: Heroku, AWS (EC2, Lambda + API Gateway), Google Cloud (App Engine, Cloud Run), Azure (App Service), or DigitalOcean (App Platform)
- Environment Variables: Use platform-specific secret management (AWS Secrets Manager, Azure Key Vault). Never deploy
.envfiles - Persistent URL: Replace ngrok/Cloudflare with production domain for Vonage webhook URLs
- Process Management: Use PM2 for Node.js process reliability (auto-restart, clustering, logging)
- CI/CD: Automate testing and deployment with GitHub Actions, GitLab CI, or Jenkins
- Database: Store message history and opt-outs with PostgreSQL or MongoDB. Use Prisma or Sequelize ORM
- Monitoring: Integrate Sentry, Datadog, or similar for error tracking and performance monitoring
13. Next Steps: Scale Your SMS Marketing Platform
You've built a Node.js SMS messaging application using Express and Vonage Messages API with TCPA compliance and rate limiting – a complete foundation for SMS marketing campaigns and two-way communication platforms.
Potential Enhancements:
- Automated Chatbot Replies: Implement natural language processing to respond intelligently to incoming messages
- Database Integration: Store message logs, user data, conversation history, and opt-out records with PostgreSQL or MongoDB
- Admin Dashboard: Build a web UI to manage campaigns, view analytics, and export reports
- Advanced Monitoring: Integrate Sentry for error tracking, Datadog for performance monitoring, and Grafana for custom dashboards
- Webhook Security: Implement JWT signature verification for production webhook authentication
- Complete SMS Marketing Platform: Build enterprise features:
- Campaign scheduling with cron jobs or AWS EventBridge
- Bulk messaging with real-time progress tracking and retry logic
- Opt-out management dashboard with automated compliance auditing
- Analytics and reporting: delivery rates, opt-out rates, engagement metrics, conversion tracking
- A/B testing capabilities for message optimization
- Drip campaign automation with conditional logic
- Segmentation engine for targeted messaging
- SMS shortlink tracking with UTM parameters
SMS Marketing Campaign Checklist:
Before launching production campaigns:
- ☐ Implement TCPA-compliant opt-out detection and handling
- ☐ Maintain consent records with timestamps for all recipients
- ☐ Configure opt-out database with 4-year retention policy
- ☐ Set up rate limiting (30 requests/second maximum)
- ☐ Test expanded opt-out keywords ("STOP," "UNSUBSCRIBE," "Leave me alone," etc.)
- ☐ Deploy to production with persistent webhook URLs (not ngrok)
- ☐ Configure monitoring, logging, and error alerting
- ☐ Calculate campaign costs: ~$0.0144 per conversation × expected volume
- ☐ Train team on TCPA compliance and FCC regulations
- ☐ Set up automated compliance reporting
Related Resources:
- Vonage Messages API Documentation – Official API reference
- Vonage Node.js SDK GitHub – Source code and examples
- FCC TCPA Compliance Guide – Official regulatory guidance
- Cloudflare Tunnel Setup – Webhook exposure alternative
- PM2 Process Manager – Production Node.js deployment
- Express.js Best Practices – Security hardening guide
Related Tutorials
Expand your Vonage SMS implementation with these guides:
- Vonage SMS scheduling with Node.js – Implement time-delayed message delivery
- WhatsApp Business API with Vonage – Multi-channel messaging integration
- SMS authentication and 2FA with Vonage – Secure login flows with Vonage Verify API
- MMS multimedia messaging with Vonage – Send images and videos via Messages API
- SMS delivery analytics dashboard – Track campaign performance with real-time metrics