code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Node.js

How to Send SMS Marketing Campaigns with Vonage in Node.js (Express Tutorial)

Learn how to build a Node.js SMS marketing campaign system with Vonage Messages API. Complete guide covering bulk messaging, delivery tracking, webhooks, rate limiting, and production deployment.

How to Send SMS Marketing Campaigns with Vonage in Node.js

Tech Stack: Node.js, Express, Vonage Messages API, dotenv

This comprehensive guide walks you through building a production-ready Node.js application for sending SMS marketing campaigns using the Vonage Messages API. Learn how to send individual and bulk SMS messages, track delivery status with webhooks, implement rate limiting, handle errors gracefully, and prepare your system for deployment. Perfect for building notification systems, promotional campaigns, transactional alerts, and customer engagement tools.


Project Overview and Goals

What We're Building:

We will create a Node.js Express server application that can:

  1. Send individual SMS messages via an API endpoint.
  2. Send bulk SMS messages (a basic campaign) to a list of recipients via an API endpoint.
  3. Receive delivery status updates from Vonage via webhooks.
  4. Utilize best practices for API key management, basic error handling, and logging.

Problem Solved:

This application provides a foundational backend for businesses needing to programmatically send targeted SMS messages for marketing, notifications, or alerts, leveraging the global reach and reliability of Vonage's network.

Technologies Used:

  • Node.js: A JavaScript runtime built on Chrome's V8 engine, ideal for building fast, scalable network applications.
  • Express: A minimal and flexible Node.js web application framework, providing a robust set of features for web and mobile applications.
  • Vonage Messages API: A powerful API enabling communication across multiple channels (SMS, MMS, WhatsApp, etc.). We will focus on its SMS capabilities.
  • @vonage/server-sdk: The official Vonage Node.js SDK for interacting with Vonage APIs.
  • dotenv: A zero-dependency module that loads environment variables from a .env file into process.env.
  • ngrok: A tool to expose local servers behind NATs and firewalls to the public internet over secure tunnels (essential for local webhook testing).

System Architecture:

+-----------------+ +---------------------+ +-----------------+ +-------------------+ | User / Client |----->| Node.js Express App |----->| Vonage Cloud |----->| Mobile Recipient | | (e.g., Postman) | | (API Endpoints) | | (Messages API) | | (Receives SMS) | +-----------------+ +----------^----------+ +--------v--------+ +-------------------+ | | (Status Update) | (Webhook Handler) | +-----------------------+
  1. A client (like Postman or another application) sends a request to our Node.js Express app's API endpoint (e.g., /send-sms, /send-campaign).
  2. The Express app uses the Vonage Node.js SDK to securely send the message details (recipient, sender ID, message content) to the Vonage Messages API.
  3. Vonage processes the request and delivers the SMS message to the recipient's mobile device.
  4. Vonage sends status updates (e.g., delivered, failed) back to a pre-configured webhook URL handled by our Express app.

Prerequisites:

  • Node.js and npm (or yarn): Installed on your development machine. Download from nodejs.org.
  • Vonage API Account: Create a free account at vonage.com.
  • Vonage API Key and Secret: Found on your Vonage API Dashboard.
  • Vonage Virtual Number: Purchase an SMS-capable number from the Vonage Dashboard.
  • ngrok: Installed and authenticated (a free account is sufficient). Download from ngrok.com.
  • Text Editor or IDE: Such as VS Code.
  • Basic understanding of JavaScript, Node.js, Express, and REST APIs.

1. Vonage Account Setup and Configuration

Before writing code, ensure your Vonage account is correctly configured.

  1. Sign Up/Log In: Access your Vonage API Dashboard.
  2. Get API Key and Secret:
    • Navigate to the API Settings page.
    • Your API key and API secret are displayed at the top. Keep these secure — they grant access to your account.
    • Purpose: These credentials authenticate your application's requests to the Vonage API.
  3. Purchase a Vonage Number:
    • Navigate to Numbers > Buy numbers.
    • Search for numbers using country, features (ensure SMS is selected), and type (Mobile, Landline, Toll-Free).
    • Purchase a number suitable for your campaign needs (consider local regulations and throughput requirements — see Section 8).
    • Note down the purchased number (e.g., 12015550123). This will be your FROM_NUMBER.
    • Purpose: This is the phone number recipients will see as the sender of your SMS messages.
  4. Set Default SMS API to Messages API:
    • Navigate back to API Settings.
    • Scroll down to SMS settings.
    • Ensure Messages API is selected as the "Default SMS Setting". Vonage has two SMS APIs; the Messages API is newer and supports multiple channels. Using the wrong default will cause unexpected webhook formats or errors.
    • Click Save changes.
  5. Create a Vonage Application (for Webhooks): Even though we'll use API Key/Secret for sending, an Application is needed to configure webhook URLs for status updates.
    • Navigate to Applications > Create a new application.
    • Give it a name (e.g., Node SMS Campaign App).
    • Click Generate public and private key. Save the private.key file securely (this key is required by the Vonage platform to create an Application entity, even if not used for authentication in this specific guide).
    • Enable the Messages capability.
    • You'll see fields for Inbound URL and Status URL. Leave these blank for now. We'll fill the Status URL later using ngrok.
    • Click Generate new application.
    • Note the Application ID generated for this application.
  6. Link Your Number to the Application:
    • Go back to the Applications list, find your new application, and click its name.
    • Scroll down to the Linked numbers section.
    • Click Link next to the Vonage number you purchased earlier.
    • Purpose: This tells Vonage which application's webhooks should handle messages sent to this number (Inbound URL) or status updates about messages sent from this number (Status URL).

2. Setting Up the Node.js Project

Let's initialize our Node.js project and install dependencies.

  1. Create Project Directory:

    bash
    mkdir vonage-sms-campaigns
    cd vonage-sms-campaigns
  2. Initialize npm Project:

    bash
    npm init -y
    • This creates a package.json file with default settings.
  3. Install Dependencies:

    bash
    npm install express @vonage/server-sdk dotenv
    • express: The web framework.
    • @vonage/server-sdk: The official Vonage SDK.
    • dotenv: For managing environment variables.
  4. Create Core Files:

    bash
    touch index.js .env .gitignore
  5. Configure .gitignore: Add standard Node.js ignores to prevent committing sensitive information or unnecessary files.

    plaintext
    # .gitignore
    
    # Dependencies
    node_modules/
    
    # Environment Variables
    .env
    
    # Logs
    logs
    *.log
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    
    # Optional Editor directories
    .vscode/
    .idea/
    
    # Optional Operating System files
    .DS_Store
    Thumbs.db
  6. Configure Environment Variables (.env): Create a .env file in the project root. Never commit this file to version control.

    plaintext
    # .env
    
    # Vonage API Credentials
    VONAGE_API_KEY=YOUR_API_KEY
    VONAGE_API_SECRET=YOUR_API_SECRET
    
    # Vonage Number (Sender ID)
    VONAGE_NUMBER=YOUR_VONAGE_NUMBER_INCLUDING_COUNTRY_CODE
    
    # Application Settings
    PORT=3000 # Or any port you prefer
    • Replace YOUR_API_KEY, YOUR_API_SECRET, and YOUR_VONAGE_NUMBER with the actual values from your Vonage dashboard (Step 1).
    • Purpose: Using environment variables keeps sensitive credentials out of your codebase, making it more secure and configurable across different environments (development, staging, production).
  7. Basic Server Setup (index.js):

    javascript
    // index.js
    require('dotenv').config(); // Load environment variables from .env file
    const express = require('express');
    const { Vonage } = require('@vonage/server-sdk');
    
    // --- Basic Configuration ---
    const app = express();
    const port = process.env.PORT || 3000;
    
    // --- Middleware ---
    // Enable parsing of JSON request bodies
    app.use(express.json());
    // Enable parsing of URL-encoded request bodies
    app.use(express.urlencoded({ extended: true }));
    
    // --- Vonage Client Initialization ---
    // Use API Key and Secret for authentication
    const vonage = new Vonage({
      apiKey: process.env.VONAGE_API_KEY,
      apiSecret: process.env.VONAGE_API_SECRET
    });
    
    // --- Basic Routes ---
    app.get('/', (req, res) => {
      res.send('SMS Campaign Server is running!');
    });
    
    // --- Start Server ---
    app.listen(port, () => {
      console.log(`Server listening on port ${port}`);
    });
    • This sets up a basic Express server, loads environment variables, and initializes the Vonage SDK using your API Key and Secret.

3. How to Send SMS Messages with Vonage Node.js SDK

Let's create API endpoints to send messages programmatically.

A. Sending a Single SMS Message

  1. Add the following route handler to index.js (before app.listen):

    javascript
    // index.js (add this section)
    
    // --- API Endpoints ---
    
    /**
     * @route POST /send-sms
     * @description Sends a single SMS message.
     * @param {string} to - Recipient phone number (E.164 format, e.g., 14155552671)
     * @param {string} text - The message content
     * @access Public (Add Auth in production!)
     */
    app.post('/send-sms', async (req, res) => {
      const { to, text } = req.body;
      const from = process.env.VONAGE_NUMBER;
    
      // Basic validation
      if (!to || !text) {
        return res.status(400).json({ error: 'Missing required fields: to, text' });
      }
      if (!from) {
         return res.status(500).json({ error: 'VONAGE_NUMBER environment variable not set.' });
      }
    
      console.log(`Attempting to send SMS from ${from} to ${to}: "${text}"`);
    
      try {
        const resp = await vonage.messages.send({
          message_type: "text",
          text: text,
          to: to,
          from: from,
          channel: "sms"
        });
    
        console.log("Message sent successfully:", resp); // { message_uuid: '...' }
        res.status(200).json({ success: true, message_uuid: resp.message_uuid });
    
      } catch (err) {
        console.error("Error sending SMS:", err);
    
        // Provide more specific error feedback if possible
        let statusCode = 500;
        let errorMessage = "Failed to send SMS.";
    
        if (err.response) {
           // Error from Vonage API
           statusCode = err.response.status || 500;
           // Use title or detail from Vonage's structured error response if available
           errorMessage = err.response.data?.title || err.response.data?.detail || err.message || errorMessage;
           console.error("Vonage API Error:", statusCode, err.response.data || err.message);
        } else if (err.request) {
           // Request was made but no response received
           errorMessage = "No response received from Vonage API.";
           console.error("Vonage Request Error:", err.request);
        } else {
           // Error setting up the request
           errorMessage = err.message || "An unknown error occurred during SMS sending setup.";
           console.error("SMS Sending Setup Error:", err.message);
        }
    
        res.status(statusCode).json({ success: false, error: errorMessage });
      }
    });
  2. Explanation:

    • The route listens for POST requests on /send-sms.
    • It expects to (recipient number) and text (message content) in the JSON request body.
    • It retrieves the from number from environment variables.
    • Basic validation checks if required fields are present.
    • vonage.messages.send() is called asynchronously (async/await).
      • message_type: "text": Specifies a plain text message.
      • text: The content of the SMS.
      • to: The recipient's phone number (ideally in E.164 format, e.g., 14155552671).
      • from: Your Vonage virtual number.
      • channel: "sms": Explicitly specifies the SMS channel.
    • A successful response from Vonage includes a message_uuid, which is returned in the JSON response.
    • The catch block handles potential errors during the API call, logs them, attempts to extract a meaningful error message from the Vonage response if available, and returns an appropriate error response.
  3. Testing with curl:

    • Start your server: node index.js
    • Open another terminal and run (replace placeholders):
      bash
      curl -X POST http://localhost:3000/send-sms \
           -H "Content-Type: application/json" \
           -d '{
                 "to": "RECIPIENT_PHONE_NUMBER",
                 "text": "Hello from Vonage via Node.js!"
               }'
    • Replace RECIPIENT_PHONE_NUMBER with a real phone number (including country code, e.g., 12125551234).
    • You should receive an SMS, and the terminal running the server should log success messages. The curl command should receive a JSON response like {"success":true,"message_uuid":"..."}.

B. Sending Bulk SMS Campaigns

This endpoint sends the same message to multiple recipients. Caution: Simple loops are not suitable for large campaigns due to rate limits. See Section 8 for details.

  1. Add the following route handler to index.js:

    javascript
    // index.js (add this section within // --- API Endpoints ---)
    
    /**
     * @route POST /send-campaign
     * @description Sends the same SMS message to multiple recipients.
     * @param {string[]} recipients - Array of phone numbers (E.164 format)
     * @param {string} text - The message content
     * @access Public (Add Auth in production!)
     */
    app.post('/send-campaign', async (req, res) => {
      const { recipients, text } = req.body;
      const from = process.env.VONAGE_NUMBER;
    
      // Basic validation
      if (!recipients || !Array.isArray(recipients) || recipients.length === 0) {
        return res.status(400).json({ error: 'Missing or invalid required field: recipients (must be a non-empty array)' });
      }
      if (!text) {
        return res.status(400).json({ error: 'Missing required field: text' });
      }
      if (!from) {
         return res.status(500).json({ error: 'VONAGE_NUMBER environment variable not set.' });
      }
    
      console.log(`Starting campaign from ${from} to ${recipients.length} recipients: "${text}"`);
    
      const results = [];
      let successCount = 0;
      let failureCount = 0;
    
      // --- WARNING: Rate Limit Consideration ---
      // This simple loop can easily hit Vonage rate limits for large lists.
      // Production systems MUST implement proper queuing and rate limiting.
      // See Section 8: Rate Limiting and Throughput.
      // For demonstration, we add a small delay. DO NOT rely on this for production scale.
      const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
      const INTER_MESSAGE_DELAY_MS = 1100; // Slightly over 1 sec for default 1/sec long code limit
    
      for (const to of recipients) {
        try {
          console.log(` - Sending to ${to}...`);
          const resp = await vonage.messages.send({
            message_type: "text",
            text: text,
            to: to,
            from: from,
            channel: "sms"
          });
    
          console.log(`   - Success (UUID: ${resp.message_uuid})`);
          results.push({ to: to, success: true, message_uuid: resp.message_uuid });
          successCount++;
    
        } catch (err) {
          console.error(`   - Error sending to ${to}:`, err); // Log the full error for debugging
          let errorMessage = `Failed to send SMS to ${to}.`;
          let statusCode = 500; // Default status code for errors in the loop
    
          // Try to get specific error details, similar to single send
          if (err.response) {
             statusCode = err.response.status || 500;
             errorMessage = err.response.data?.title || err.response.data?.detail || err.message || errorMessage;
             console.error(`   - Vonage API Error for ${to}:`, statusCode, err.response.data || err.message);
          } else if (err.request) {
             errorMessage = `No response received from Vonage API for ${to}.`;
             console.error(`   - Vonage Request Error for ${to}:`, err.request);
          } else {
             errorMessage = err.message || `An unknown error occurred sending to ${to}.`;
             console.error(`   - SMS Sending Setup Error for ${to}:`, err.message);
          }
    
          // We report individual failures but continue the campaign
          results.push({ to: to, success: false, error: errorMessage });
          failureCount++;
        }
        // Apply delay *after* each attempt (success or fail)
        // Remove or adjust significantly if using short codes or higher throughput numbers
        await delay(INTER_MESSAGE_DELAY_MS);
      }
    
      console.log(`Campaign finished. Success: ${successCount}, Failures: ${failureCount}`);
      // Respond after the loop finishes
      res.status(200).json({
        campaign_summary: {
          total_recipients: recipients.length,
          success_count: successCount,
          failure_count: failureCount,
        },
        results: results // Detailed results for each recipient
      });
    });
  2. Explanation:

    • Listens for POST on /send-campaign.
    • Expects recipients (an array of phone numbers) and text.
    • Iterates through the recipients array.
    • Calls vonage.messages.send() for each recipient.
    • Crucially: Includes a basic delay using setTimeout to demonstrate the need for pacing. This is NOT a robust production solution. Real applications need job queues (e.g., BullMQ, Kue) and intelligent rate limiting based on the Vonage number's throughput (see Section 8).
    • Error handling within the loop logs the specific error for each failed recipient and records the failure, but allows the campaign to continue for other recipients.
    • Tracks success/failure counts and individual results.
    • Returns a summary and detailed results after attempting all recipients.
  3. Testing with curl:

    • Restart your server: Ctrl+C and node index.js
    • Run (replace placeholders):
      bash
      curl -X POST http://localhost:3000/send-campaign \
           -H "Content-Type: application/json" \
           -d '{
                 "recipients": ["RECIPIENT_1", "RECIPIENT_2"],
                 "text": "Node.js Campaign Test!"
               }'
    • Replace RECIPIENT_1, RECIPIENT_2 with real phone numbers.
    • Observe the server logs — you'll see the delay between sends. Check if the SMS messages arrive. The curl response will summarize the outcome.

4. Implementing Delivery Status Webhooks

Vonage sends delivery status information to a webhook URL you configure.

  1. Start ngrok: Expose your local server to the internet.

    bash
    # Make sure your Node server is running on port 3000 first!
    ngrok http 3000
    • ngrok will display a Forwarding URL (e.g., https://abcdef123456.ngrok.io). Copy the https URL. This URL is temporary and changes each time you restart ngrok.
  2. Configure Status Webhook in Vonage Dashboard:

    • Go to Applications > Click your application name (Node SMS Campaign App).
    • In the Capabilities section, find the Messages capability.
    • Paste your ngrok HTTPS Forwarding URL into the Status URL field, appending a path like /webhooks/status. Example: https://abcdef123456.ngrok.io/webhooks/status
    • Click Save changes at the bottom of the page.
    • Purpose: This tells Vonage where to send POST requests containing status updates for messages sent by this application.
  3. Create Webhook Handler in index.js: Add this route before app.listen:

    javascript
    // index.js (add this section)
    
    // --- Webhook Handler ---
    
    /**
     * @route POST /webhooks/status
     * @description Receives message status updates from Vonage.
     * @access Public (Vonage needs to reach this)
     */
    app.post('/webhooks/status', (req, res) => {
      console.log("--- Vonage Status Webhook Received ---");
      console.log("Timestamp:", new Date().toISOString());
      console.log("Body:", JSON.stringify(req.body, null, 2)); // Log the full payload
    
      const { message_uuid, status, timestamp, error, usage } = req.body;
    
      // Process the status update (e.g., update database, log analytics)
      // Example: Log key information
      console.log(`Status Update for UUID ${message_uuid}: ${status} at ${timestamp}`);
      if (error) {
        console.error(`Error Code: ${error.code}, Reason: ${error.reason}`);
      }
       if (usage && usage.price) {
           console.log(`Message Cost: ${usage.price} ${usage.currency}`);
       }
    
      // Vonage expects a 200 OK response to acknowledge receipt.
      // Failure to send 200 OK will cause Vonage to retry the webhook.
      res.status(200).end();
    });
  4. Explanation:

    • Listens for POST requests on /webhooks/status (matching the path configured in Vonage).
    • Logs the received request body. The body contains valuable information like the message_uuid (linking it back to the sent message), the status (submitted, delivered, rejected, failed, accepted), timestamp, potential error details, and usage (cost).
    • Crucially: Sends a 200 OK response back to Vonage immediately to acknowledge receipt. If Vonage doesn't receive a 200 OK, it will retry sending the webhook, potentially leading to duplicate processing.
    • In a real application, you would parse this data and update your database, trigger alerts, or feed analytics systems.
  5. Testing Webhooks:

    • Ensure your Node server is running (node index.js).
    • Ensure ngrok is running and the Status URL in Vonage points to your ngrok URL.
    • Send a test SMS using the /send-sms endpoint (from Step 3A).
    • Watch the terminal running your Node server. Within a few seconds to minutes, you should see logs appearing from the /webhooks/status endpoint as Vonage sends updates (e.g., submitted, delivered).
    • You can also inspect requests in the ngrok web interface (usually http://127.0.0.1:4040).

5. Error Handling and Logging Best Practices

Robust error handling and logging are vital for production systems.

  1. Consistent Error Handling:

    • Our route handlers include try...catch blocks for API calls.
    • Parse Vonage API error responses (as shown in the /send-sms and /send-campaign examples) to provide more specific feedback than generic "Failed" messages. Check err.response.data for Vonage's structured error details like title and detail.
    • Implement centralized error handling middleware in Express for uncaught errors (beyond the scope of this basic guide, but recommended for production).
  2. Logging:

    • Use console.log for informational messages (e.g., "Sending SMS...", "Webhook received").
    • Use console.error for actual errors or failures.
    • Include relevant context in logs (timestamps, message UUIDs, recipient numbers where appropriate — be mindful of privacy).
    • Production Logging: For production, replace console.log/error with a dedicated logging library like Winston or Pino. These offer:
      • Log levels (debug, info, warn, error).
      • Structured logging (JSON format for easier parsing by log analysis tools).
      • Multiple transports (writing to files, databases, log management services like Datadog, Logstash).
    • Example (Conceptual - Winston):
      javascript
      // // Example: Setting up Winston (conceptual)
      // const winston = require('winston');
      // const logger = winston.createLogger({
      //   level: 'info',
      //   format: winston.format.json(),
      //   defaultMeta: { service: 'sms-campaign-service' },
      //   transports: [
      //     new winston.transports.Console({ format: winston.format.simple() }),
      //     new winston.transports.File({ filename: 'error.log', level: 'error' }),
      //     new winston.transports.File({ filename: 'combined.log' }),
      //   ],
      // });
      // // Replace console.log with logger.info, console.error with logger.error
  3. Retry Mechanisms (Vonage Side):

    • Vonage automatically retries sending SMS messages if temporary network issues occur.
    • Vonage retries sending webhooks if your endpoint doesn't respond with 200 OK. Ensure your webhook handler is reliable and responds quickly.
    • Client-Side Retries: For critical messages, your application might implement retries (with exponential backoff) if the initial vonage.messages.send() call fails due to network issues or temporary Vonage API unavailability (e.g., 5xx errors). Use libraries like async-retry for this. However, be careful not to retry errors that indicate permanent failure (e.g., invalid number, insufficient funds).

6. Database Schema and Data Layer (Conceptual)

While this guide doesn't implement a database, a production system would require one.

  • Purpose: Store campaign details, recipient lists, message status, costs, and potentially user data.
  • Example Schema (Relational - e.g., PostgreSQL):
    • campaigns: campaign_id (PK), name, message_text, sender_id, created_at, scheduled_at, status (e.g., 'draft', 'sending', 'completed', 'failed')
    • recipients: recipient_id (PK), phone_number (UNIQUE), first_name, last_name, subscribed, created_at
    • campaign_recipients: campaign_id (FK), recipient_id (FK), message_uuid (UNIQUE), status (e.g., 'pending', 'submitted', 'delivered', 'failed'), status_timestamp, error_code, cost, currency (Updated by webhook handler)
  • Data Access: Use an ORM (Object-Relational Mapper) like Sequelize or Prisma to interact with the database, handle migrations, and define models.
  • Webhook Integration: The /webhooks/status handler would parse the incoming status update, find the corresponding record in campaign_recipients using the message_uuid, and update its status, status_timestamp, error_code, cost, etc.

7. Adding Security Features

Security is paramount, especially when handling user data and API keys.

  1. API Key Security:

    • NEVER hardcode API keys or secrets in your source code.
    • Use environment variables (.env locally, secure configuration management in deployment).
    • Ensure .env is in your .gitignore.
    • Rotate API keys periodically.
  2. Input Validation:

    • Sanitize and validate all input coming from clients (request bodies, query parameters).
    • Use libraries like express-validator or joi to define schemas and validate data types, formats (e.g., E.164 for phone numbers), lengths, etc.
    • Example (express-validator - conceptual):
      bash
      npm install express-validator
      javascript
      // // Example Validation for /send-sms
      // const { body, validationResult } = require('express-validator');
      
      // app.post('/send-sms',
      //   body('to').isMobilePhone('any', { strictMode: true }).withMessage('Invalid phone number format (E.164 required)'),
      //   body('text').notEmpty().trim().escape().withMessage('Text cannot be empty'),
      //   (req, res) => {
      //     const errors = validationResult(req);
      //     if (!errors.isEmpty()) {
      //       return res.status(400).json({ errors: errors.array() });
      //     }
      //     // ... proceed with sending SMS ...
      //   }
      // );
  3. Authentication & Authorization:

    • Protect your API endpoints. Do not expose /send-sms or /send-campaign publicly without authentication.
    • Implement strategies like API Keys (for server-to-server communication), JWT (JSON Web Tokens for user sessions), or OAuth.
  4. Rate Limiting (Your API):

    • Protect your server from abuse and brute-force attacks by limiting the number of requests a client can make to your API endpoints.
    • Use libraries like express-rate-limit.
    • Example (express-rate-limit - conceptual):
      bash
      npm install express-rate-limit
      javascript
      // // Example: Basic rate limiting
      // 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'
      // });
      // // Apply to specific routes or globally
      // app.use('/send-sms', apiLimiter);
      // app.use('/send-campaign', apiLimiter);
  5. Webhook Security:

    • Vonage supports Signed Webhooks using JWT or signature verification. This ensures that incoming requests to your webhook handler genuinely originate from Vonage. Implementing this is highly recommended for production. Consult the official Vonage documentation on webhook security for implementation details.

8. Understanding Vonage Rate Limits and Throughput

Vonage imposes limits on how fast you can send messages. Ignoring these will lead to rejected messages and potential account issues.

  1. Default Limits:

    • Long Codes (Standard US/Canada Numbers): Typically 1 message per second (MPS) per number.
    • Toll-Free Numbers (US/Canada): Higher throughput, often around 30 MPS per number (check specific limits).
    • Short Codes (US/Canada): Highest throughput (often 100+ MPS), but require a separate, lengthy, and costly application process.
    • International Numbers: Limits vary significantly by country and number type. Check Vonage documentation or country-specific regulations.
  2. API Concurrency Limit: Vonage generally allows 30 concurrent API requests per account by default. Sending faster than your number's MPS limit or exceeding the concurrent request limit will result in errors (often a 429 Too Many Requests response).

  3. US A2P 10DLC Registration:

    • For sending Application-to-Person (A2P) SMS traffic to US numbers using standard 10-digit long codes (10DLC), registration of your brand and campaigns is mandatory. This process is done via The Campaign Registry (TCR), facilitated through the Vonage dashboard.
    • Sending any significant volume before successful registration will result in very low throughput and eventual blocking by US carriers.
    • Registration involves providing business details and campaign use case information. Throughput is assigned based on vetting scores and use case.
    • Process: Navigate to the Brands and Campaigns section in your Vonage Dashboard and follow the registration steps. This process requires time and has associated fees, so plan accordingly.
  4. Handling Limits in Code:

    • Simple loops are inadequate. The setTimeout delay in our /send-campaign example is fragile and inefficient for production.
    • Use a Job Queue: Implement a robust job queue system (e.g., BullMQ, Kue, Celery if using Python) with workers that process messages.
    • Rate Limit Workers: Configure your queue workers to respect the MPS limit of your Vonage number(s). Libraries like bottleneck can help manage rate limiting within your workers.
    • Monitor API Responses: Check for 429 errors and implement exponential backoff and retry logic in your workers.
    • Distribute Load: If using multiple Vonage numbers, distribute the sending load across them.

Frequently Asked Questions

How to send bulk SMS messages with Node.js and Vonage?

Use the Vonage Messages API with the @vonage/server-sdk in a Node.js Express app. Create an endpoint that accepts an array of recipient numbers and the message content. The endpoint should iterate through recipients and use the vonage.messages.send() method, but implement a queuing system like BullMQ or Kue for production to respect rate limits and ensure deliverability.

What is the Vonage Messages API used for in Node.js?

The Vonage Messages API allows you to send SMS, MMS, and WhatsApp messages programmatically from your Node.js applications. This guide specifically focuses on using the API's SMS capabilities to send marketing campaigns, notifications, or alerts.

Why does Vonage require API key and secret for sending SMS?

The API key and secret authenticate your Node.js application with the Vonage platform, verifying that you have permission to access and use the Messages API. Keep these credentials secure, preferably stored in environment variables (.env file) and never commit them to version control.

When should I use a job queue for SMS campaigns?

A job queue is essential for sending bulk SMS messages in production to manage Vonage's rate limits (typically 1 message per second for long codes). Queues like BullMQ or Kue allow you to control the sending rate and avoid exceeding limits, ensuring messages are delivered reliably.

Can I send SMS messages to international numbers using Vonage?

Yes, the Vonage Messages API supports sending SMS to international numbers. However, rate limits and regulations vary significantly by country, so consult the Vonage documentation for specific country information and ensure compliance with local laws.

How to handle Vonage SMS delivery status updates in Node.js?

Vonage sends delivery status updates via webhooks to a URL you configure in your Vonage application settings. Create a webhook handler endpoint (e.g., /webhooks/status) in your Express app that listens for POST requests and logs the status, timestamp, and any error details provided by Vonage.

What is the purpose of ngrok in Vonage SMS development?

ngrok creates a secure tunnel to your local development server, making it publicly accessible over the internet. This allows Vonage to send webhook status updates to your local server during development and testing, which is essential for validating webhook functionality.

How to set up a Vonage number for sending SMS?

Purchase an SMS-capable number from the Numbers section of your Vonage Dashboard. Be sure to select a number suitable for your campaign needs and location, considering local regulations and expected message volume.

What is the difference between Vonage's legacy SMS API and the Messages API?

Vonage has two SMS APIs: the legacy SMS API and the newer Messages API. The Messages API is recommended as it supports multiple channels (SMS, MMS, WhatsApp) and has a more modern, consistent interface. Ensure your default SMS API setting is set to 'Messages API' in your Vonage API settings.

How to manage environment variables in Node.js for Vonage API credentials?

Use the dotenv package to load environment variables from a .env file into process.env. Store your Vonage API key, secret, and virtual number in the .env file and access them within your application code using process.env.VARIABLE_NAME. Never commit this .env file to version control.

What is A2P 10DLC and why is it important for US SMS campaigns?

A2P 10DLC (Application-to-Person 10-Digit Long Code) is a system for registering businesses and campaigns that send SMS messages to US phone numbers. Registration is mandatory for using long codes effectively, improves deliverability, and avoids low throughput or message blocking.

How to secure a Node.js application sending SMS with Vonage?

Implement measures such as storing API keys in environment variables, validating and sanitizing user input, using HTTPS for webhook URLs, and implementing authentication and authorization for API endpoints to prevent misuse and unauthorized access.

What are some good logging libraries for a Node.js SMS application?

Winston and Pino are popular logging libraries for Node.js that offer features like log levels (debug, info, warn, error), structured logging, and multiple transports for sending logs to files, databases, or log management services.

Why is a 200 OK response crucial for Vonage webhook handlers?

Your webhook handler must respond with a 200 OK status code to acknowledge receipt of the webhook from Vonage. If Vonage doesn't receive a 200 OK, it will retry the webhook multiple times, potentially leading to duplicate processing of status updates and incorrect application behavior.