This guide provides a step-by-step walkthrough for building a production-ready bulk SMS broadcast system using Node.js, Express, and the Vonage Messages API. You'll learn how to set up your environment, interact with the Vonage API to send messages concurrently while respecting rate limits, handle status updates via webhooks, and implement essential error handling and security measures.
This system enables efficient communication with large groups of users via SMS, ideal for notifications, marketing campaigns, or alerts. By the end, you'll have a functional API endpoint capable of initiating bulk SMS broadcasts and a basic structure for handling delivery statuses.
System Architecture:
+-------------+ +---------------------+ +-----------------+ +--------------+
| API Client | ----> | Node.js/Express API | ----> | Vonage Messages | ----> | User Phones |
| (e.g. curl) | | (Your Server) | | API | | (Recipients) |
+-------------+ +---------------------+ +-----------------+ +--------------+
| ^
| | (Status Updates)
v |
+---------------------+
| Webhook Handler |
| (/webhooks/status) |
+---------------------+
Prerequisites:
- A Vonage API account. Sign up here if you don't have one.
- Node.js (LTS version recommended) installed on your system.
npm
oryarn
package manager.- A Vonage phone number capable of sending SMS. You can get one from the Vonage Dashboard.
- ngrok installed for testing webhooks locally. A free account is sufficient.
- (Optional but Recommended) Vonage CLI installed (
npm install -g @vonage/cli
).
1. Setting Up the Project
First, let's create our project directory and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
mkdir vonage-bulk-sms cd vonage-bulk-sms
-
Initialize Node.js Project: Initialize a
package.json
file to manage dependencies and project metadata.npm init -y
-
Install Dependencies: We need several packages:
@vonage/server-sdk
: The official Vonage Node.js library to interact with their APIs.express
: A minimal and flexible Node.js web application framework for building the API and webhook handlers.dotenv
: To load environment variables from a.env
file for configuration.p-limit
: A utility to limit concurrency, crucial for managing Vonage API rate limits when sending bulk messages.
npm install @vonage/server-sdk express dotenv p-limit
-
Project Structure: Create the basic files and directories. Your project structure will look like this:
vonage-bulk-sms/ ├── node_modules/ ├── .env ├── .gitignore ├── index.js ├── package.json └── private.key (You will add this later)
- Create the
index.js
file:touch index.js
- Create the
.env
file:touch .env
- Create the
.gitignore
file:touch .gitignore
- Create the
-
Configure
.gitignore
: Addnode_modules
,.env
, andprivate.key
to your.gitignore
file to prevent committing sensitive credentials and dependencies to version control.node_modules/ .env private.key
Note: While adding
private.key
to.gitignore
is crucial, ensure you have a secure way to manage and distribute this key for deployment. -
Configure Environment Variables (
.env
): Open the.env
file and add the following placeholders. We will fill these in during the Vonage setup step.# Vonage Credentials (Messages API - Preferred) VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key # Vonage API Key/Secret (Alternative - Not used in primary example) # VONAGE_API_KEY=YOUR_VONAGE_API_KEY # VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET # Vonage Number and App Config VONAGE_FROM_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER PORT=3000 # Concurrency Limit for Bulk Sending VONAGE_CONCURRENCY_LIMIT=10
VONAGE_APPLICATION_ID
: Found in your Vonage Application settings (created below).VONAGE_APPLICATION_PRIVATE_KEY_PATH
: Path to the private key file downloaded when creating the Vonage Application. We assume it's in the project root.VONAGE_FROM_NUMBER
: The Vonage virtual number (in E.164 format, e.g.,14155550100
) you will send messages from.PORT
: The port your Express server will run on.VONAGE_CONCURRENCY_LIMIT
: How many concurrent requests to make to the Vonage API. Start low (e.g., 5-10) and adjust based on your account limits and performance. Vonage has general API limits (around 30 concurrent requests) and per-second sending limits (1/sec for long codes, up to 30/sec for Toll-Free/Short Codes, potentially higher with registration).
2. Integrating with Vonage
Now, let's set up the necessary components in your Vonage account and configure the SDK.
-
Create a Vonage Application: An Application acts as a container for your authentication credentials and webhook configurations.
- Log in to your Vonage API Dashboard.
- Navigate to 'Applications' -> 'Create a new application'.
- Give your application a name (e.g., 'Node Bulk SMS Broadcaster').
- Click 'Generate public and private key'. This will automatically download a
private.key
file. Save this file directly into your project's root directory (vonage-bulk-sms/
). The public key is stored by Vonage. - Enable the 'Messages' capability.
- For the 'Inbound URL' and 'Status URL', you'll need a publicly accessible URL. For local development, we'll use ngrok:
- Open a new terminal window.
- Run
ngrok http 3000
(assuming your app runs on port 3000 as defined in.env
). - Copy the
https://<your-unique-id>.ngrok.io
URL provided by ngrok. - Paste the ngrok URL into the Vonage dashboard fields, appending specific paths:
- Inbound URL:
https://<your-unique-id>.ngrok.io/webhooks/inbound
(We won't implement inbound receiving in detail, but it's good practice to set it). - Status URL:
https://<your-unique-id>.ngrok.io/webhooks/status
(This is crucial for tracking message delivery).
- Inbound URL:
- Set the HTTP Method to
POST
for both URLs.
- Click 'Generate new application'.
- On the next page, copy the Application ID and paste it into your
.env
file forVONAGE_APPLICATION_ID
.
-
Link Your Vonage Number:
- While still viewing your newly created application in the dashboard, scroll down to the 'Link virtual numbers' section.
- Find the Vonage number you want to send messages from and click 'Link'.
- Copy this number (including the country code, e.g.,
14155550100
) and paste it into your.env
file forVONAGE_FROM_NUMBER
.
-
Configure SMS Settings (Crucial): Vonage offers different APIs for SMS. We are using the Messages API. Ensure it's set as the default for your account:
- In the Vonage Dashboard, navigate to 'Settings'.
- Scroll down to the 'API keys' tab, find the 'SMS settings' section.
- Ensure 'Default SMS Setting' is set to Messages API. If not, select it and click 'Save changes'.
-
Update
.env
: Make sure your.env
file now contains the actualVONAGE_APPLICATION_ID
andVONAGE_FROM_NUMBER
you obtained. TheVONAGE_APPLICATION_PRIVATE_KEY_PATH
should point to theprivate.key
file in your project root (./private.key
).
3. Implementing the Core Broadcasting Logic
Let's write the core function to handle sending messages in bulk, respecting concurrency limits.
Open index.js
and add the following setup code:
// index.js
require('dotenv').config(); // Load environment variables from .env file
const express = require('express');
const { Vonage } = require('@vonage/server-sdk');
const pLimit = require('p-limit');
const fs = require('fs'); // To check for private key existence
// Basic Input Validation
const isValidE164 = (phoneNumber) => /^\+[1-9]\d{1,14}$/.test(phoneNumber);
// --- Vonage Setup ---
const vonageAppId = process.env.VONAGE_APPLICATION_ID;
const privateKeyPath = process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH;
const fromNumber = process.env.VONAGE_FROM_NUMBER;
// Critical configuration checks - exit if missing
if (!vonageAppId || !privateKeyPath || !fromNumber) {
console.error('Error: VONAGE_APPLICATION_ID, VONAGE_APPLICATION_PRIVATE_KEY_PATH, or VONAGE_FROM_NUMBER missing in .env file. Exiting.');
process.exit(1);
}
if (!fs.existsSync(privateKeyPath)) {
console.error(`Error: Private key file not found at path: ${privateKeyPath}. Exiting.`);
process.exit(1);
}
const vonage = new Vonage({
applicationId: vonageAppId,
privateKey: privateKeyPath,
});
// --- Concurrency Limiter ---
const concurrencyLimit = parseInt(process.env.VONAGE_CONCURRENCY_LIMIT, 10) || 5;
const limit = pLimit(concurrencyLimit);
console.log(`Concurrency limit set to: ${concurrencyLimit}`);
// --- Express App Setup ---
const app = express();
const port = process.env.PORT || 3000;
// Middlewares
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
// Simple logging middleware
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
// API Endpoint will be added here
// Webhook Handler will be added here
// --- Start Server ---
app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`);
// Log configuration status clearly at startup
console.log(` Vonage App ID: ${vonageAppId ? 'Configured' : 'MISSING!'}`);
console.log(` Private Key: ${fs.existsSync(privateKeyPath) ? 'Found' : 'MISSING / PATH INCORRECT!'}`);
console.log(` From Number: ${fromNumber ? fromNumber : 'MISSING!'}`);
console.log(` Concurrency: ${concurrencyLimit}`);
console.log('Application ready to receive requests.');
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\nShutting down server...');
// Perform cleanup if needed
process.exit(0);
});
Add the function responsible for iterating through recipients and sending messages using the concurrency limiter. Place this function definition before the app.listen
call in index.js
.
// index.js (Add this function definition)
/**
* Sends SMS messages to multiple recipients using the Vonage Messages API
* with concurrency limiting.
*
* @param {string[]} recipients - An array of phone numbers in E.164 format.
* @param {string} message - The text message content.
* @returns {Promise<object[]>} - A promise that resolves to an array of results for each send attempt.
*/
async function sendBulkSms(recipients, message) {
console.log(`Kicking off bulk send to ${recipients.length} recipients...`);
const results = [];
// Create an array of promises, each wrapped by the limiter
const sendPromises = recipients.map(recipient =>
limit(async () => {
// Validate number format before sending
if (!isValidE164(recipient)) {
console.warn(`Invalid phone number format skipped: ${recipient}`);
return { to: recipient, status: 'skipped', reason: 'Invalid E.164 format' };
}
console.log(` -> Sending to ${recipient}...`);
try {
const resp = await vonage.messages.send({
message_type: 'text',
text: message,
to: recipient,
from: fromNumber,
channel: 'sms',
// Optional: Add client_ref for tracking. This ID is echoed back in status webhooks, helping correlate sends and statuses.
// client_ref: `bulk_${Date.now()}_${recipient}`
});
console.log(` Message dispatched to ${recipient}. UUID: ${resp.message_uuid}`);
results.push({ to: recipient, status: 'submitted', message_uuid: resp.message_uuid });
return { to: recipient, status: 'submitted', message_uuid: resp.message_uuid }; // Return result for Promise.allSettled
} catch (err) {
// Log specific Vonage errors if available
let errorMessage = err.message;
if (err.response?.data) {
console.error(` Error sending to ${recipient}: ${JSON.stringify(err.response.data)}`);
errorMessage = err.response.data.title || err.response.data.detail || errorMessage;
} else {
console.error(` Error sending to ${recipient}:`, err);
}
results.push({ to: recipient, status: 'failed', error: errorMessage });
return { to: recipient, status: 'failed', error: errorMessage }; // Return result for Promise.allSettled
}
})
);
// Wait for all limited promises to settle (complete or fail)
const settledResults = await Promise.allSettled(sendPromises);
console.log(`Bulk send process completed.`);
// Optionally refine results based on settledResults if needed,
// but the `results` array populated inside the limit function is usually sufficient.
// settledResults provides { status: 'fulfilled' | 'rejected', value: ..., reason: ... }
return results; // Return the array of results pushed within the limit function
}
pLimit
Usage: We wrap each call tovonage.messages.send
insidelimit(async () => { ... })
.p-limit
ensures that no more thanconcurrencyLimit
of these async functions run simultaneously.- Error Handling: We use
try...catch
within the limited function to handle errors for individual messages without stopping the entire batch. We log errors and record the failure status. - Results: The function returns an array containing the outcome (
submitted
,failed
,skipped
) for each recipient. Promise.allSettled
: While we usePromise.allSettled
to wait for all operations, theresults
array is populated within thelimit
function callback to capture results as they happen.allSettled
is mainly used here to ensure we wait for all concurrent tasks to finish before thesendBulkSms
function returns.- Validation: Basic E.164 format validation is added.
4. Building the API Layer
Let's create an Express endpoint to receive broadcast requests.
Add the following code in index.js
, replacing the // API Endpoint will be added here
comment:
// index.js
// --- API Endpoints ---
app.post('/broadcast', async (req, res) => {
const { recipients, message } = req.body;
// --- Basic Input Validation ---
if (!Array.isArray(recipients) || recipients.length === 0) {
return res.status(400).json({ success: false, message: 'Missing or invalid `recipients` array in request body.' });
}
if (typeof message !== 'string' || message.trim() === '') {
return res.status(400).json({ success: false, message: 'Missing or invalid `message` string in request body.' });
}
// Limit payload size (optional but recommended)
if (recipients.length > 10000) { // Example limit
return res.status(400).json({ success: false, message: 'Too many recipients. Maximum allowed: 10000.' });
}
console.log(`Received broadcast request for ${recipients.length} recipients.`);
try {
// Use setImmediate or process.nextTick to allow the response to be sent quickly
// before the potentially long-running bulk send operation completes.
// This makes the API feel more responsive.
setImmediate(async () => {
try {
await sendBulkSms(recipients, message);
// Optionally: Log completion or notify another system here
console.log('Background bulk send processing finished for initial request.');
} catch (backgroundError) {
console.error('Error during background bulk send processing:', backgroundError);
// Implement more robust error reporting if needed (e.g., monitoring service)
}
});
// Respond immediately indicating the job has been accepted
res.status(202).json({
success: true,
message: `Broadcast accepted for ${recipients.length} recipients. Processing initiated.`,
// Note: Detailed results are not returned here due to async processing.
// Status webhooks should be used for tracking individual message outcomes.
});
} catch (error) {
console.error('Error initiating broadcast:', error);
res.status(500).json({ success: false, message: 'Internal Server Error initiating broadcast.' });
}
});
// Webhook Handler will be added here
// ... (rest of the code: app.listen, etc.)
- Validation: Checks if
recipients
is a non-empty array andmessage
is a non-empty string. Adds a basic size limit. - Asynchronous Processing: We use
setImmediate
to runsendBulkSms
in the background after sending the202 Accepted
response. This prevents the API request from timing out during long broadcasts. For true production robustness, consider a dedicated job queue (like BullMQ with Redis). - Response: Returns
202 Accepted
immediately, signifying the request is received and processing has started. It does not wait for all messages to be sent. Individual results should be tracked via status webhooks or logs.
Testing the Endpoint (curl
):
Once the server is running (node index.js
), you can test the endpoint using curl
:
curl -X POST http://localhost:3000/broadcast \
-H ""Content-Type: application/json"" \
-d '{
""recipients"": [""+14155550101"", ""+14155550102"", ""+447700900001""],
""message"": ""Hello from your Node.js Bulk Broadcaster!""
}'
- Replace the example phone numbers with valid test numbers in E.164 format.
- You should receive a
202 Accepted
response:{ ""success"": true, ""message"": ""Broadcast accepted for 3 recipients. Processing initiated."" }
- Check your server console logs to see the messages being dispatched. Check the test phones for received SMS.
5. Handling Webhooks for Status Updates
Vonage sends updates about message delivery (e.g., delivered
, failed
, rejected
) to the Status URL you configured. Let's create a handler for these.
Add the following code in index.js
, replacing the // Webhook Handler will be added here
comment:
// index.js
// --- Webhook Handler ---
app.post('/webhooks/status', (req, res) => {
console.log('Received Status Webhook:');
console.log(JSON.stringify(req.body, null, 2)); // Log the full payload
const { message_uuid, status, to, timestamp, error, client_ref } = req.body;
// Process the status update here
// Examples:
// 1. Log specific fields
console.log(` [Webhook] UUID: ${message_uuid}, To: ${to}, Status: ${status}, Timestamp: ${timestamp}`);
if (error) {
// Log specific error details if present
console.error(` [Webhook] Error: Code=${error?.code}, Reason=${error?.reason}`);
}
if (client_ref) {
console.log(` [Webhook] Client Ref: ${client_ref}`);
}
// 2. Store status in a database (requires DB setup)
// e.g., updateDatabase(message_uuid, status, error, timestamp);
// 3. Trigger other actions based on status (e.g., notify admin on failure)
// if (status === 'failed' || status === 'rejected') {
// notifyAdmin(`Message ${message_uuid} to ${to} failed: ${error?.reason}`);
// }
// --- IMPORTANT: Acknowledge the webhook ---
// Vonage expects a 200 OK response quickly to prevent retries.
// Do complex processing asynchronously if needed.
res.status(200).end();
});
// ... (rest of the code: app.listen, etc.)
- Logging: Logs the entire incoming webhook payload for inspection and specific fields including error code/reason. Uses optional chaining (
?.
) for safer access to nested error properties. - Processing: Includes comments on potential actions like logging specific fields, updating a database, or triggering alerts. Implement the logic relevant to your application needs.
- Acknowledgement: Crucially, it sends a
200 OK
response immediately. If processing takes time, do it asynchronously after sending the response.
Testing Webhooks:
- Ensure ngrok is still running and forwarding to your local server (
ngrok http 3000
). - Ensure the correct ngrok URL is configured as the Status URL in your Vonage Application settings.
- Send a broadcast using the
/broadcast
endpoint. - Watch your server console logs. You should see "Received Status Webhook:" followed by JSON data for each message status update (e.g.,
submitted
,delivered
,failed
). Look for the[Webhook]
log lines. - You can also inspect requests in the ngrok web interface (usually
http://127.0.0.1:4040
).
6. Error Handling and Logging Strategy
We've built basic error handling, let's consolidate the strategy.
- API Endpoint (
/broadcast
):- Handles validation errors (400 Bad Request).
- Handles internal errors during initiation (500 Internal Server Error).
- Uses
setImmediate
for background processing, logging errors that occur during the bulk send separately in the console. For production, log these to a file or monitoring service.
- Bulk Send Function (
sendBulkSms
):- Uses
try...catch
for each individualvonage.messages.send
call within thepLimit
wrapper. - Logs detailed errors (including Vonage API error responses if available).
- Records 'failed' status in the results array for individual message failures.
- Uses
- Webhook Handler (
/webhooks/status
):- Logs incoming payloads and specific fields like error code/reason.
- Includes basic
try...catch
(implicitly via Express error handling, though explicit is safer for complex logic) around processing logic. - Always sends
200 OK
unless there's a fundamental server issue, to prevent Vonage retries. Log processing errors for later investigation.
- Logging:
- Uses
console.log
,console.warn
,console.error
for basic logging. - Recommendation: For production, replace
console.*
with a structured logging library likewinston
orpino
. Configure log levels (info, warn, error) and output destinations (file, console, log management service).
- Uses
- Retry Mechanisms:
- This guide doesn't implement automatic retries for failed SMS sends (e.g., due to temporary network issues or Vonage errors).
- You could enhance
sendBulkSms
using a library likeasync-retry
around thevonage.messages.send
call, specifically retrying on certain error codes (e.g., 5xx errors from Vonage) with exponential backoff. Be cautious not to retry permanent failures (like invalid numbers).
7. Security Considerations
Securing your application is vital.
- Environment Variables: Never commit
.env
orprivate.key
to Git. Use secure methods for managing secrets in deployment environments (e.g., environment variables provided by the platform, secrets managers like AWS Secrets Manager, HashiCorp Vault). - Input Validation: Robustly validate all incoming data (
recipients
array format,message
content/length). Sanitize output if displaying message content elsewhere. Consider libraries likejoi
orexpress-validator
. - Authentication: The current API has no authentication. Add authentication/authorization before exposing it. Options:
- Simple API Key: Check for a secret key in the
Authorization
header. Easy but less secure if the key leaks. - JWT (JSON Web Tokens): Implement a login flow that issues tokens, then validate tokens on requests.
- OAuth: For integration with other services.
- Simple API Key: Check for a secret key in the
- Rate Limiting (API Endpoint): Protect your
/broadcast
endpoint from abuse. Useexpress-rate-limit
to limit requests per IP address.npm install express-rate-limit
// index.js (near the top, after express init) const rateLimit = require('express-rate-limit'); const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again after 15 minutes', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // Apply to specific routes or all routes // Make sure this is placed *before* the app.post('/broadcast', ...) definition app.use('/broadcast', apiLimiter); // Apply only to the broadcast route
- Rate Limiting (Vonage): Remember the
p-limit
implementation manages concurrency towards the Vonage API itself. - 10DLC Compliance (US Traffic): If sending Application-to-Person (A2P) SMS to US numbers, you must register your brand and campaign via the Vonage dashboard under 'Brands and Campaigns'. Failure to comply results in poor deliverability or blocking. This process assigns throughput limits specific to your use case. Contact Vonage support for guidance.
8. Performance and Scaling
- Concurrency: The
VONAGE_CONCURRENCY_LIMIT
in.env
controls how many requests run in parallel. Tune this based on testing and your Vonage account limits (start low). - Throughput: Be aware of Vonage's messages-per-second (MPS) limits (1 MPS for Long Codes, up to 30 MPS for Toll-Free/Short Codes, potentially higher via 10DLC).
p-limit
controls concurrency, not rate. True rate limiting (e.g., ensuring exactly 1 SMS per second) requires more complex queuing or token bucket algorithms. For most bulk use cases, managing concurrency is sufficient. - Queuing: For very large broadcasts or higher reliability, implement a dedicated job queue (e.g., BullMQ with Redis, RabbitMQ). The API endpoint adds jobs to the queue, and separate worker processes consume the queue, calling
sendBulkSms
. This decouples the API from the sending process. - Multiple Numbers: Vonage allows using multiple 'from' numbers. You could adapt
sendBulkSms
to cycle through a pool of numbers, potentially increasing overall throughput (subject to API limits). This adds complexity. - Contact Vonage: For significantly high volumes (>30 MPS), discuss options like dedicated Short Codes or requesting increased API concurrency limits with Vonage Sales or Support (check the dashboard for preferred support channels, or try
<support@vonage.com>
).
9. Testing and Verification
- Unit Tests: Use a testing framework like
jest
ormocha
/chai
. Mock the@vonage/server-sdk
to testsendBulkSms
logic without making real API calls. Test validation, concurrency handling (using mock timers), and error paths. - Integration Tests: Use
supertest
to make HTTP requests to your running Express server. Test the/broadcast
endpoint with valid and invalid inputs. Test the/webhooks/status
endpoint by mocking requests from Vonage. - Manual Verification:
- Start the server (
node index.js
). - Ensure ngrok is running (
ngrok http 3000
). - Verify Vonage Application Status URL points to the correct ngrok URL.
- Send a small test broadcast via
curl
to 2-3 real test phone numbers you control. - Check Server Logs: Look for request logs, "Sending to...", "Message dispatched...", and any errors.
- Check Phones: Confirm SMS messages are received.
- Check Server Logs (Webhooks): Look for "Received Status Webhook:" logs with
delivered
status (orfailed
if testing failure cases). Check the[Webhook]
log lines for details. - Check ngrok UI (
http://127.0.0.1:4040
): Inspect requests to/broadcast
and/webhooks/status
. - Check Vonage Dashboard: Review API logs and message search for details.
- Start the server (
10. Deployment
Deploying involves running your Node.js application on a server or platform.
- Choose a Platform: Options include Heroku, AWS (EC2, Lambda, Elastic Beanstalk), Google Cloud (App Engine, Cloud Run), DigitalOcean (App Platform, Droplets), etc.
- Environment Variables: Configure all environment variables (
VONAGE_APPLICATION_ID
,VONAGE_APPLICATION_PRIVATE_KEY_PATH
,VONAGE_FROM_NUMBER
,PORT
,VONAGE_CONCURRENCY_LIMIT
, any API keys you added) securely within your chosen platform's interface. Do not hardcode them or commit.env
. - Private Key: Securely upload or provide the
private.key
file to your deployment environment. Ensure theVONAGE_APPLICATION_PRIVATE_KEY_PATH
environment variable points to its location on the server. - Webhook URL: Update the Status URL (and Inbound URL) in your Vonage Application settings to use your permanent deployed application URL (e.g.,
https://your-app-name.herokuapp.com/webhooks/status
). ngrok is only for local development. - Build Step (if applicable): If using TypeScript or a build process, ensure it runs during deployment.
- Start Command: Configure your platform to run the application using
node index.js
(or your package.jsonstart
script). - CI/CD: Set up a Continuous Integration/Continuous Deployment pipeline (e.g., GitHub Actions, GitLab CI, Jenkins) to automate testing and deployment on code changes.
11. Troubleshooting and Caveats
- Authentication Errors:
Authentication failed
: Double-checkVONAGE_APPLICATION_ID
and that theprivate.key
file exists at the path specified byVONAGE_APPLICATION_PRIVATE_KEY_PATH
and is readable by the Node process. Ensure the key hasn't been corrupted.- Ensure you generated keys for an Application and are using the Application ID, not your main account API Key/Secret, with the
Vonage({...})
constructor shown.
- Rate Limiting/Throttling:
- Vonage API responds with
429 Too Many Requests
: DecreaseVONAGE_CONCURRENCY_LIMIT
in.env
. Contact Vonage if you consistently need higher throughput than allowed. - Your own API
/broadcast
returns429
: Check yourexpress-rate-limit
configuration if you implemented it.
- Vonage API responds with
- Message Failures (
failed
/rejected
status):- Check the
error
object in the status webhook payload. This object often contains nested fields likecode
(e.g.,error.code
) andreason
(e.g.,error.reason
) providing specific details (e.g.,Invalid Account Id
,Partner quota exceeded
,Illegal Number
,Absent Subscriber
). - Ensure numbers are in E.164 format (
+
followed by country code and number, e.g.,+14155550123
).
- Check the