code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

How to Send SMS with Vonage API in Node.js and Express (2025 Guide)

Learn how to send SMS programmatically using Vonage API with Node.js and Express. Step-by-step tutorial covering setup, authentication, error handling, and production deployment.

Send SMS with Vonage API in Node.js: Complete Express Tutorial

Last Updated: January 2025

Framework Note: This tutorial demonstrates SMS implementation using Node.js with Express. The Vonage SMS API works with any Node.js framework. For framework-specific implementations like Next.js App Router, Server Actions, or database integration with Supabase, you can adapt the core SMS sending logic shown here to your preferred stack.

Build a production-ready Node.js application using Express to send SMS messages programmatically via the Vonage SMS API (formerly Nexmo). This comprehensive tutorial walks you through everything from initial project setup with the @vonage/server-sdk to implementing essential features like authentication, error handling, security best practices, and production deployment.

By the end of this guide, you'll have a working Express REST API endpoint that sends SMS messages to any phone number worldwide. This foundation is perfect for building SMS-enabled features like two-factor authentication (2FA), transactional notifications, delivery alerts, appointment reminders, or verification codes.

Project Overview and Goals

  • Goal: Create a REST API endpoint using Node.js and Express that sends SMS messages via the Vonage SMS API.

  • Problem Solved: Enable programmatic SMS sending without managing complex telecom infrastructure or carrier integrations.

  • Technologies:

    • Node.js: Asynchronous JavaScript runtime environment.
    • Express: Minimalist web framework for Node.js, used to build the API endpoint.
    • Vonage Node.js SDK (@vonage/server-sdk): Official SDK that simplifies interaction with the Vonage SMS API and other Vonage services.
    • dotenv: Manages environment variables for configuration and secrets.
  • Architecture: A client (like curl, Postman, or another application) sends an HTTP POST request to your Express API. The Express application validates the request, uses the Vonage SDK (configured with API credentials) to interact with the Vonage SMS API, which then delivers the message to the recipient's mobile device.

    +--------+ +-----------------+ +------------+ +----------+ | Client | -----> | Express API | -----> | Vonage API | -----> | Recipient| | (curl/ | | (Node.js) | | (SMS) | | (Phone) | | App) | | - Validate | +------------+ +----------+ +--------+ | - Use SDK | | - Handle Response | +-----------------+ | v +-----------------+ | Vonage SDK | | (@vonage/server-| | sdk) | +-----------------+

    (Note: For published documentation, consider replacing this ASCII diagram with a graphical version like SVG or PNG for better clarity.)

  • Prerequisites:

    • Node.js and npm (or yarn) installed. Recommended: Node.js v20.x LTS or v22.x LTS for optimal compatibility and long-term support. Node.js v18 reached end-of-life on April 30, 2025. Download Node.js
    • A Vonage API account. Sign up for free (comes with free credit for testing).
    • A text editor or IDE (e.g., VS Code).
    • A tool for making HTTP requests (e.g., curl or Postman).
    • Current Package Versions (as of January 2025):
      • @vonage/server-sdk: v3.25.x (latest version: 3.25.1, released September 2025)
      • express: v4.19.x, v4.20.x, or v5.1.0 (released March 31, 2025)
      • dotenv: v16.4.x

1. Set Up Your Node.js Project

Initialize your Node.js project and install the Vonage SDK and required dependencies.

  1. Create Your Project Directory: Open your terminal and create a new directory for the project, then navigate into it.

    bash
    mkdir vonage-sms-guide
    cd vonage-sms-guide
  2. Initialize Your Node.js Project: Create a package.json file to manage your project dependencies and scripts.

    bash
    npm init -y
  3. Install Your Dependencies: Install Express for the web server, the Vonage SDK to interact with the API, and dotenv to handle environment variables securely. Running npm install without specific versions will install the latest stable releases.

    bash
    npm install express @vonage/server-sdk dotenv

    For production applications, pin to specific versions for reproducibility:

    bash
    npm install express@^4.19.2 @vonage/server-sdk@^3.25.0 dotenv@^16.4.5
    • express: The web framework.
    • @vonage/server-sdk: The official Vonage SDK for Node.js.
    • dotenv: Loads environment variables from a .env file into process.env.

    Version Compatibility Note: The @vonage/server-sdk v3.x requires Node.js v14 or higher. For production use, prefer Node.js v20 or v22 LTS versions.

  4. Enable ES Modules (Optional but Recommended): Use ES Modules (import/export) for modern JavaScript practice. Open your package.json file and add the following line:

    json
    {
      "name": "vonage-sms-guide",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "@vonage/server-sdk": "^3.25.0",
        "dotenv": "^16.4.5",
        "express": "^4.19.2"
      },
      "type": "module"
    }
  5. Create Your .gitignore File: Prevent sensitive files (like .env and node_modules) from being committed to version control. Create a file named .gitignore in the root directory:

    text
    # .gitignore
    
    # Dependencies
    node_modules/
    
    # Environment variables
    .env
    
    # Logs
    logs
    *.log
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    pnpm-debug.log*
    lerna-debug.log*
    
    # Optional Editor directories/files
    .vscode/*
    !.vscode/settings.json
    !.vscode/tasks.json
    !.vscode/launch.json
    !.vscode/extensions.json
    *.sublime-workspace
  6. Create Your Project Files: Create the main application file and the environment configuration file.

    bash
    touch index.js .env

    Your project structure should now look like this:

    vonage-sms-guide/ ├── .gitignore ├── .env ├── index.js ├── package.json ├── package-lock.json (or yarn.lock) └── node_modules/

2. Implement Core SMS Sending Functionality

Configure the Vonage SDK client and create the core function to send SMS messages programmatically.

  1. Configure Your Environment Variables: Open the .env file. Replace the placeholder values (YOUR_API_KEY, YOUR_API_SECRET, YOUR_VONAGE_VIRTUAL_NUMBER, MyApp) with your actual credentials and chosen sender ID obtained from your Vonage Dashboard (see Section 4).

    dotenv
    # .env
    
    # Vonage API Credentials - REPLACE with your actual credentials from Vonage Dashboard
    VONAGE_API_KEY=YOUR_API_KEY
    VONAGE_API_SECRET=YOUR_API_SECRET
    
    # Sender Information (Use EITHER a purchased Vonage number OR an Alphanumeric Sender ID)
    # Option 1: Use a Vonage Number (Recommended for reliability & two-way)
    # Uncomment and replace YOUR_VONAGE_VIRTUAL_NUMBER with your purchased number in E.164 format (e.g., 14155550100)
    # VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER
    
    # Option 2: Use an Alphanumeric Sender ID (e.g., "MyApp", check country regulations)
    # Replace "MyApp" with your desired brand name (up to 11 chars) or comment this out if using VONAGE_NUMBER
    VONAGE_BRAND_NAME=MyApp
    • VONAGE_API_KEY / VONAGE_API_SECRET: Found in your Vonage Dashboard (see Section 4). You must replace the placeholders.
    • VONAGE_NUMBER OR VONAGE_BRAND_NAME: This is the 'from' identifier for your SMS.
      • Using a purchased VONAGE_NUMBER (in E.164 format, e.g., 14155550100) is more reliable for SMS delivery, enables two-way messaging, and is required in some regions like the United States. Replace YOUR_VONAGE_VIRTUAL_NUMBER if using this option.
      • Using a VONAGE_BRAND_NAME (Alphanumeric Sender ID, up to 11 characters) is possible in some countries but often requires pre-registration and doesn't support replies. Replace MyApp with your desired sender name if using this option. Be aware of limitations and country-specific rules (see Section 8).
  2. Initialize Vonage SDK and Create Send Function: Open index.js and add the following code:

    javascript
    // index.js
    import express from 'express';
    import { Vonage } from '@vonage/server-sdk';
    // Imports 'dotenv' and immediately loads variables from .env into process.env
    import 'dotenv/config';
    
    // --- Vonage Client Initialization ---
    // Ensure API Key and Secret are loaded from environment variables
    if (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET) {
      console.error('__ Missing Vonage API Key or Secret. Please check your .env file or environment variables.');
      process.exit(1); // Exit if credentials are missing
    }
    
    const vonage = new Vonage({
      apiKey: process.env.VONAGE_API_KEY,
      apiSecret: process.env.VONAGE_API_SECRET,
    });
    
    // Determine the sender ('from') based on environment variables
    const fromNumber = process.env.VONAGE_NUMBER || process.env.VONAGE_BRAND_NAME;
    if (!fromNumber) {
      console.error('__ Missing VONAGE_NUMBER or VONAGE_BRAND_NAME in .env file or environment variables. Please set one.');
      process.exit(1);
    }
    console.log(`__ SMS will be sent from: ${fromNumber}`);
    
    // --- Core SMS Sending Function ---
    async function sendSms(recipient, message) {
      console.log(`Attempting to send SMS to ${recipient}...`);
      try {
        // Note: This uses the legacy SMS API endpoint via the SDK (`vonage.sms.send()`).
        // For newer features (like combining SMS/WhatsApp/etc.), explore the Messages API (`vonage.messages.send()`).
        const responseData = await vonage.sms.send({
          to: recipient,
          from: fromNumber,
          text: message,
        });
    
        // Check the status directly from the first message in the response array
        if (responseData.messages[0].status === '0') {
          console.log(`_ Message sent successfully to ${recipient}. Message ID: ${responseData.messages[0]['message-id']}`);
          return { success: true, data: responseData.messages[0] };
        } else {
          // Handle errors returned by the Vonage API
          const errorCode = responseData.messages[0].status;
          const errorText = responseData.messages[0]['error-text'];
          console.error(`_ Message failed with error code ${errorCode}: ${errorText}`);
    
          // Provide more specific user-friendly messages for common errors
          let userMessage = `Message failed: ${errorText}`;
          if (errorCode === '15') { // Non-Whitelisted Destination
            userMessage = 'Sending failed: The destination number is not on the allowed list for this trial account. Please add it via the Vonage Dashboard settings.';
          } else if (errorCode === '2') { // Missing parameters
            userMessage = 'Sending failed: Missing required parameters in the request to Vonage.';
          } else if (errorCode === '9') { // Partner quota exceeded
             userMessage = 'Sending failed: Insufficient account balance.';
          }
          // For a complete list of possible status codes, refer to the Vonage SMS API documentation:
          // https://developer.vonage.com/en/messaging/sms/guides/troubleshooting-sms
          return { success: false, message: userMessage, errorDetails: responseData.messages[0] };
        }
      } catch (err) {
        // Handle potential network errors or exceptions thrown by the SDK
        console.error('_ Error sending SMS via Vonage SDK:', err);
        return { success: false, message: 'An unexpected error occurred while trying to send the SMS.', errorDetails: err };
      }
    }
    
    // --- (Express API Layer will be added below) ---
    • We import necessary modules. import 'dotenv/config' automatically loads variables from .env into process.env.
    • We initialize the Vonage client using the API key and secret from process.env. Crucially, we add checks to ensure these variables are present.
    • We determine the fromNumber based on which environment variable (VONAGE_NUMBER or VONAGE_BRAND_NAME) is set, prioritizing the number if both are present.
    • The sendSms function takes the recipient number and message text.
    • It uses vonage.sms.send(), part of the SDK interacting with Vonage's standard SMS API.
    • It uses async/await for cleaner handling of the promise returned by the SDK.
    • It checks the status in the response: '0' means success. Any other status indicates an error reported by the Vonage API per the official Vonage troubleshooting documentation.
    • We log success or failure details and return a structured object { success: boolean, data/message: ..., errorDetails: ... }. We include specific user-friendly messages for common errors and link to the official documentation for all codes.
    • A try...catch block handles potential network errors or SDK-level exceptions.

3. Build Your Express API Layer

Create the Express server and configure the REST API endpoint for sending SMS messages.

Add the following code to the bottom of index.js:

javascript
// index.js (continued)

// --- Express API Setup ---
const app = express();
const PORT = process.env.PORT || 3000; // Use port from environment or default to 3000

// Middleware to parse JSON and URL-encoded request bodies
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// --- API Endpoint: POST /send-sms ---
app.post('/send-sms', async (req, res) => {
  console.log('Received request body:', req.body); // Log incoming request for debugging

  // Basic Input Validation
  const { to, text } = req.body;
  if (!to || !text) {
    console.log('Validation failed: Missing "to" or "text" in request body.');
    return res.status(400).json({ success: false, message: 'Missing required fields: "to" (recipient phone number) and "text" (message content).' });
  }

  // Phone Number Format Check (Basic - Improve for Production)
  // WARNING: This is a very basic regex and NOT suitable for production.
  // Use a robust library like 'libphonenumber-js' for proper E.164 validation and parsing.
  if (!/^\+?[1-9]\d{1,14}$/.test(to)) {
     console.log(`Validation failed: Invalid phone number format for "${to}".`);
     // Recommend E.164 format (+countrycode)(number) e.g., +1234567890
     return res.status(400).json({ success: false, message: 'Invalid recipient phone number format. Please use E.164 format (e.g., +1234567890).' });
  }

  // Call the core sending function
  const result = await sendSms(to, text);

  // Send Response based on the outcome of sendSms
  if (result.success) {
    // Success: Return 200 OK with Vonage response data
    res.status(200).json(result);
  } else {
    // Failure: Determine appropriate status code
    // 400 for client-side errors identifiable from Vonage response (like non-whitelisted number)
    // 500 for server-side errors or unexpected issues
    const statusCode = result.errorDetails?.status === '15' ? 400 : 500;
    res.status(statusCode).json(result);
  }
});

// --- Health Check Endpoint ---
app.get('/health', (req, res) => {
  // Simple endpoint to check if the service is running
  res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});


// --- Start the Server ---
app.listen(PORT, () => {
  console.log(`__ Server listening at http://localhost:${PORT}`);
  console.log(`   Send SMS endpoint: POST http://localhost:${PORT}/send-sms`);
  console.log(`   Health check:      GET http://localhost:${PORT}/health`);
  // Log partial key for confirmation that environment variables are loaded (avoid logging full secret!)
  console.log(`   Using API Key:     ${process.env.VONAGE_API_KEY?.substring(0, 4)}...`);
});
  • We initialize the Express application.
  • We define the PORT (defaulting to 3000 if not set in .env).
  • We add express.json() and express.urlencoded() middleware to parse incoming request bodies.
  • The POST /send-sms route is defined:
    • It performs basic validation to ensure to and text fields are present in the JSON request body.
    • It includes a rudimentary regex check for phone number format, strongly recommending E.164 and advising a proper library like libphonenumber-js for production use.
    • It calls our sendSms function with the validated data.
    • It sends a JSON response back to the client based on the success property of the result from sendSms, setting appropriate HTTP status codes (200 for success, 400 for specific client errors like invalid number, 500 for other failures).
  • A simple /health endpoint is added – useful for monitoring.
  • Finally, app.listen starts the server and logs helpful information, including a partial API key to confirm loading.

4. Configure Vonage API Credentials

Obtain your Vonage API credentials and configure your SMS sender ID for message delivery.

  1. Sign Up or Log In: Go to the Vonage API Dashboard and sign up or log in.

  2. Find Your API Key and Secret: Once logged in, your API Key and API Secret are usually displayed prominently on the main dashboard page or within the "API settings" section. Look for values labeled API key and API secret.

  3. Update Your .env File: Paste the copied key and secret into your .env file, replacing the YOUR_API_KEY and YOUR_API_SECRET placeholders.

    dotenv
    # .env
    VONAGE_API_KEY=abc123def456 # <-- Paste your Key here
    VONAGE_API_SECRET=XYZ789ghijk # <-- Paste your Secret here
    # ... rest of the file (VONAGE_NUMBER or VONAGE_BRAND_NAME)

    Save the file after pasting your actual credentials.

  4. Configure Your Sender ID (VONAGE_BRAND_NAME or VONAGE_NUMBER): Decide how your messages will appear to recipients and configure the corresponding line in .env:

    • If using VONAGE_BRAND_NAME: Ensure the value in .env is appropriate (e.g., MyApp, replacing the default placeholder). Be aware of country restrictions and potential registration needs (see Section 8).

    • If using VONAGE_NUMBER:

      • You need to purchase a Vonage virtual number. In the dashboard, navigate to "Numbers" > "Buy numbers". Search for and purchase a number with SMS capabilities in your desired country.
      • Once purchased, go to "Numbers" > "Your numbers". Copy the number shown, making sure it's in E.164 format (e.g., 14155550100).
      • Uncomment the VONAGE_NUMBER line in your .env file and paste the purchased number there, replacing YOUR_VONAGE_VIRTUAL_NUMBER. Comment out the VONAGE_BRAND_NAME line.
      dotenv
      # .env
      # ... API Key/Secret ...
      VONAGE_NUMBER=14155550100 # <-- Paste your purchased number here
      # VONAGE_BRAND_NAME=MyApp
  5. (CRITICAL for Trial Accounts) Add Test Numbers:

    • Important for trial accounts: Free trial Vonage accounts can only send SMS to verified test numbers. You must add and verify recipient phone numbers in your dashboard before testing. Attempting to send to non-verified numbers will result in a "Non-Whitelisted Destination" error (Status Code 29 per Vonage SMS documentation).
    • To add test numbers:
      • In the Vonage Dashboard, navigate to your user menu (often top right) → Settings.
      • Scroll down to find the "Test Numbers" section.
      • Click "Add test number".
      • Enter the phone number you want to send test messages to (e.g., your own mobile number) in E.164 format (including the + and country code).
      • Vonage will send a verification code via SMS or voice call to that number. Enter the code in the dashboard to confirm ownership.
      • Repeat for any other numbers you need to test with during the trial period.

5. Handle Errors, Logging, and Retries

Enhance error handling and logging for production readiness.

  • Error Handling:

    • The sendSms function uses try...catch to handle SDK/network errors.
    • It checks the status code from the Vonage API response for specific sending failures. According to Vonage's official SMS API error documentation, status code 0 indicates success, while non-zero values indicate errors such as:
      • 1 - Throttled
      • 2 - Missing parameters
      • 3 - Invalid parameters
      • 4 - Invalid credentials
      • 9 - Partner quota violation (insufficient funds)
      • 15 - Invalid sender address (non-whitelisted destination in trial accounts)
      • 29 - Non-whitelisted destination
    • User-friendly messages are generated for common, actionable errors.
    • The API endpoint returns appropriate HTTP status codes (200, 400, 500) based on the outcome.
  • Logging:

    • We use console.log and console.error for basic operational logging.
    • Recommendation: For production, use a dedicated logging library like Winston or Pino. These allow for:
      • Structured Logging (JSON): Easier parsing and analysis by log management systems.
      • Log Levels: Differentiating between debug, info, warning, and error messages.
      • Log Destinations: Outputting logs to files, databases, or external logging services (e.g., Datadog, Splunk, ELK Stack).
    javascript
    // Conceptual example using a logger (replace console.log/error)
    // import logger from './logger'; // Assuming logger setup elsewhere
    // logger.info(`Attempting to send SMS to ${recipient}`, { recipient });
    // logger.error(`Message failed with error code ${errorCode}: ${errorText}`, { errorCode, errorText, details: responseData.messages[0] });
  • Retry Mechanisms:

    • Vonage generally handles retries for delivering the SMS to the carrier network. Retrying the API call from your application needs careful consideration.
    • When to Retry: Consider retrying only for transient errors like network timeouts or temporary Vonage service unavailability (indicated by specific 5xx error codes, if documented by Vonage).
    • When NOT to Retry: Do not retry on definitive failures like 'Invalid Credentials' (4), 'Insufficient Funds' (9), 'Non-Whitelisted Destination' (15, 29), or invalid input parameters (2, 3, 6). Retrying these will not succeed and will waste resources/API calls.

6. Database Integration for SMS Logging (Optional)

Learn when and how to add database functionality for SMS audit logging and tracking.

  • Database Integration:
    • This specific application, designed solely as an API endpoint to trigger SMS sending, does not inherently require a database.
    • However, in a more comprehensive application, you would likely integrate this SMS sending capability and use a database for:
      • Audit Logging: Storing a record of every SMS sent (recipient, sender, message body, timestamp, Vonage message ID, final status, cost) for tracking, debugging, and compliance.
      • User Association: Linking sent messages to specific users within your application.
      • Message Templating: Storing reusable message templates.
      • Queueing: For high-volume sending, messages might be added to a database queue (or a dedicated message queue like RabbitMQ/Redis) and processed by background workers to avoid overwhelming the API or delaying web requests.
    • If a database is needed:
      1. Choose Database: Select a suitable database (e.g., PostgreSQL, MySQL, MongoDB).
      2. Design Schema: Define tables/collections (e.g., sms_logs with columns like id, recipient_number, sender_id, message_body, vonage_message_id, status_code, status_text, cost, sent_at, updated_at).
      3. Select Data Access Tool: Use an ORM (Object-Relational Mapper) like Prisma, Sequelize (SQL), or an ODM (Object-Document Mapper) like Mongoose (MongoDB) to simplify database interactions in Node.js.
      4. Implement Logic: Write functions within your application to create, read, update, and delete records related to SMS messages.

7. Add Security Features

Secure your API endpoint and credentials.

  • Input Validation and Sanitization:
    • We implemented basic presence checks for to and text.

    • Robust Phone Number Validation: Crucially, replace the basic regex check with a dedicated library like libphonenumber-js. This library, a JavaScript port of Google's libphonenumber, can parse, format, and validate phone numbers for different regions, ensuring they conform to the E.164 standard expected by Vonage.

      javascript
      // Example with libphonenumber-js:
      import { parsePhoneNumber } from 'libphonenumber-js';
      
      try {
        const phoneNumber = parsePhoneNumber(to, { defaultCountry: 'US' });
        if (!phoneNumber || !phoneNumber.isValid()) {
          return res.status(400).json({
            success: false,
            message: 'Invalid phone number.'
          });
        }
        // Use phoneNumber.number for E.164 format
      } catch (error) {
        return res.status(400).json({
          success: false,
          message: 'Unable to parse phone number.'
        });
      }
    • Message Content (text) Sanitization: While the risk of code injection directly via SMS is low compared to web contexts (SMS doesn't execute scripts), consider sanitization if:

      • The text originates from untrusted user input.
      • The text might be stored and later displayed in a web interface (prevent XSS).
      • Basic sanitization might involve trimming leading/trailing whitespace. Complex HTML sanitization (e.g., using dompurify if rendering in HTML) is usually unnecessary unless the message content has downstream uses beyond simple SMS delivery.
    • Length Validation: Enforce a maximum length for the text field to prevent abuse and manage costs associated with multi-part messages. Standard SMS using GSM-7 encoding supports 160 characters; Unicode messages support 70 characters per segment.

  1. Secrets Management:

    • .env File: Use .env for local development only. Never commit the .env file to version control (Git). Ensure .env is listed in your .gitignore file.
    • Production Environment: Do not deploy the .env file. Use the environment variable management system provided by your deployment platform (e.g., Heroku Config Vars, AWS Secrets Manager, Google Secret Manager, Azure Key Vault, Docker environment variables). These services provide secure storage and injection of secrets into your application environment.
  2. Rate Limiting:

    • Protect your /send-sms endpoint from abuse (e.g., denial-of-service attacks, spamming attempts) by limiting the number of requests a single client (IP address) can make within a certain time window.
    • Use middleware like express-rate-limit.
    bash
    npm install express-rate-limit
    javascript
    // index.js (near the top, after app = express())
    import rateLimit from 'express-rate-limit';
    
    // Configure rate limiter
    const smsLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes window
      max: 50, // Limit each IP to 50 requests per windowMs
      standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
      legacyHeaders: false, // Disable the `X-RateLimit-*` headers
      message: { success: false, message: 'Too many SMS requests created from this IP, please try again after 15 minutes.' },
      // Apply specifically to the send-sms route
    });
    
    // Apply the middleware to the /send-sms route
    app.use('/send-sms', smsLimiter);
  3. HTTPS:

    • Always use HTTPS in production. This encrypts the data (including recipient numbers and message content) between the client and your API server. Your deployment platform or a reverse proxy (like Nginx) typically handles TLS/SSL certificate management and termination.
  4. Authentication/Authorization (If Necessary):

    • The current example creates a public endpoint. If this API should only be used by specific clients (e.g., other internal services, authenticated users), implement an authentication mechanism:
      • API Keys: Generate unique keys for client applications and require them in a header (e.g., X-API-Key). Validate the key on the server.
      • JWT (JSON Web Tokens): If users trigger the SMS, protect the endpoint using JWTs issued upon user login.
      • OAuth: For third-party integrations.

8. Handle Special Cases

Understand SMS delivery nuances and regional considerations.

  • SMS Character Limits & Encoding (GSM-7 vs. Unicode):
    • Standard SMS using GSM-7 encoding supports up to 160 characters per message.
    • Unicode (UCS-2) encoding is required for emojis and non-Latin characters, reducing the limit to 70 characters per segment.
    • Messages exceeding these limits are automatically split into multiple segments (concatenated SMS). Each segment is billed separately, so monitor message length to control SMS costs and optimize delivery.
  • International Numbers & E.164 Formatting:
    • Always strive to validate, store, and send phone numbers in the international E.164 format: + followed by country code and the subscriber number (e.g., +447700900000 for UK, +12125550100 for US).
    • Using a library like libphonenumber-js helps parse various input formats into E.164. Using this standard format ensures correct routing by Vonage and carriers.
  • Sender ID Restrictions (VONAGE_BRAND_NAME vs. VONAGE_NUMBER):
    • Alphanumeric Sender IDs (VONAGE_BRAND_NAME): Support varies greatly by country.
    • Virtual Numbers (VONAGE_NUMBER): Using a Vonage-provided number (Long Code, Toll-Free) is generally the most reliable and compatible method globally, especially for two-way communication and delivery to regions like North America.
    • 10DLC Registration (US): As of January 2025, all 10-digit long codes used for A2P messaging in the US must be registered with The Campaign Registry (TCR). This registration includes brand and campaign registration requirements.
    • Action: Always consult Vonage's country-specific SMS documentation regarding Sender ID regulations before choosing VONAGE_BRAND_NAME.
  • Delivery Receipts (DLRs):
    • This guide focuses only on sending an SMS (submitting it to Vonage). Vonage then attempts delivery to the carrier.
    • To get confirmation of whether the message was actually delivered to the recipient's handset (or if it failed), you need to implement Delivery Receipt (DLR) webhooks.
    • DLR error codes are documented in Vonage's SMS Delivery Error Codes guide, which lists codes from 0 (delivered) to 99 (general error), including specific failures like absent subscriber, anti-spam rejection, and network errors.
    • This involves:
      1. Setting a webhook URL in your Vonage account settings.
      2. Creating another endpoint in your Express application (e.g., POST /sms-status) to receive HTTP POST requests from Vonage containing status updates (e.g., delivered, failed, expired) for messages you sent.
      3. Processing these updates (e.g., updating your database log).
  • Opt-Out Handling (Compliance):
    • Messaging regulations (like TCPA in the US, GDPR in Europe) require providing recipients with a way to opt-out (e.g., replying STOP).
    • If using a Vonage number capable of receiving SMS (VONAGE_NUMBER), you must:
      1. Configure an inbound message webhook in Vonage to point to another endpoint in your application (e.g., POST /inbound-sms).
      2. Implement logic in that endpoint to detect keywords like STOP, UNSUBSCRIBE, CANCEL.
      3. Maintain an opt-out list (e.g., in your database) and check against it before sending any future messages to prevent sending to opted-out users. Vonage may offer some built-in subscription management features as well.

9. Optimize Performance

Scale your SMS sending application effectively.

  • Asynchronous Nature: Node.js is non-blocking by default. Using async/await with the Vonage SDK's promise-based methods ensures your API call doesn't block the server while waiting for Vonage's response. This is crucial for handling concurrent requests efficiently.
  • Vonage API Latency: The primary factor influencing the response time of your /send-sms endpoint will be the network latency and processing time of the Vonage API itself. This is external to your application.
  • SDK Connection Management: The @vonage/server-sdk handles underlying HTTP(S) connections. Modern SDKs typically use connection pooling to reuse connections, reducing the overhead of establishing new connections for each request.
  • Caching: Caching is generally not applicable for the act of sending unique SMS messages. You might cache configuration or user data if retrieved from a database, but not the SMS sending operation itself.
  • Load Testing: If you anticipate high traffic, use load testing tools (e.g., k6, artillery, ApacheBench (ab)) to simulate concurrent users hitting your /send-sms endpoint.
    • Monitor response times (average, p95, p99).
    • Track error rates (4xx, 5xx).
    • Check resource utilization (CPU, memory) on your server.
  • Horizontal Scaling: If a single server instance cannot handle the load, deploy multiple instances of your application behind a load balancer. Ensure your application is stateless or manages state externally (e.g., using Redis for sessions if needed) to work correctly in a scaled environment.
  • Queueing for High Throughput: For sending very large volumes of SMS messages rapidly, directly calling the Vonage API within the HTTP request handler might become a bottleneck or lead to timeouts. Implement a queueing system:
    1. The /send-sms endpoint quickly validates the request and adds the message details (recipient, text) to a message queue (e.g., RabbitMQ, Redis Streams, AWS SQS).
    2. Separate background worker processes read messages from the queue and call the sendSms function to interact with the Vonage API.
    3. This decouples the API request from the actual sending process, improving API responsiveness and resilience.

Frequently Asked Questions

Q: What's the difference between Vonage SMS API and Messages API? A: The SMS API is designed specifically for SMS/MMS messaging, while the Messages API supports multiple channels (SMS, WhatsApp, Facebook Messenger, Viber). This tutorial uses the SMS API via vonage.sms.send(). For multi-channel messaging, explore vonage.messages.send().

Q: How much does it cost to send SMS with Vonage? A: Vonage SMS pricing varies by destination country. Most messages cost between $0.01-$0.10 per SMS segment. Check the Vonage pricing page for specific rates. New accounts receive free credit for testing.

Q: Can I send SMS to international numbers? A: Yes, Vonage supports SMS delivery to over 200 countries. Ensure phone numbers are in E.164 format with the correct country code. Pricing and delivery rates vary by country.

Q: Why am I getting "Non-Whitelisted Destination" errors? A: Trial accounts can only send SMS to verified test numbers. Add and verify recipient numbers in your Vonage Dashboard under Settings > Test Numbers before testing.

Q: How do I receive SMS replies with Node.js? A: Configure an inbound webhook URL in your Vonage Dashboard, then create a POST endpoint (e.g., /webhook/inbound) in your Express app to receive incoming SMS data from Vonage.

Frequently Asked Questions

How to send SMS with Node.js and Express?

Use the Vonage Node.js SDK and Express to create an API endpoint. This endpoint receives recipient details and message content, then leverages the Vonage SMS API to send the message. The tutorial provides a detailed setup guide and code examples using the '@vonage/server-sdk' library.

What is the Vonage Node.js SDK?

The Vonage Node.js SDK ('@vonage/server-sdk') simplifies interaction with Vonage APIs within Node.js applications. It provides methods for various Vonage services, including sending SMS messages, making voice calls, and managing other communication channels.

Why use dotenv for Vonage API credentials?

Dotenv is used for managing environment variables. This keeps sensitive information like API keys and secrets out of your codebase. It loads these from a '.env' file, improving security and portability.

When should I use a Vonage number for sending SMS?

Using a Vonage virtual number as the sender is generally recommended for better reliability, two-way communication, and compliance with regulations in regions like North America. It's crucial for receiving replies and essential in certain countries.

Can I use an alphanumeric sender ID with Vonage?

Yes, in some countries. This uses a brand name (up to 11 characters) as the sender. However, country-specific regulations vary significantly. Many require pre-registration or disallow it entirely for application-to-person (A2P) messaging.

How to handle errors when sending SMS with Vonage?

The tutorial demonstrates error handling using try-catch blocks and status code checking from the Vonage API response. User-friendly messages should be provided for common error scenarios, along with specific HTTP status codes for various failures.

What is the rate limit for Vonage SMS API?

The tutorial doesn't specify Vonage's default rate limits. You can implement your own rate limiting using middleware like 'express-rate-limit' to protect your API from abuse and manage costs, customizing limits as needed.

How to validate phone numbers for Vonage SMS?

A basic regex is provided in the tutorial, but for production, use a library like 'libphonenumber-js'. This ensures proper E.164 formatting and handles international number variations reliably.

Why is HTTPS essential for a Vonage SMS API?

HTTPS encrypts communication between clients and your API. This protects sensitive data such as recipient phone numbers and message content. Always use HTTPS in production for security.

How to set up Vonage test numbers?

If you're on a Vonage trial account, go to your Dashboard settings, find "Test Numbers," and add the numbers you'll send test messages to. Verify ownership by entering the code Vonage sends to the added number.

What database schema is recommended for SMS logs?

The tutorial suggests an 'sms_logs' table with fields like 'id', 'recipient_number', 'sender_id', 'message_body', 'vonage_message_id', 'status_code', 'status_text', 'cost', 'sent_at', and 'updated_at' for comprehensive tracking.

How to receive SMS delivery receipts with Vonage?

Set up a webhook URL in your Vonage account settings, then create an endpoint in your Express app to receive POST requests from Vonage containing status updates (e.g., 'delivered', 'failed'). Process these updates in your app, such as by logging them in your database.

How to handle SMS opt-outs for compliance?

Configure an inbound message webhook in your Vonage settings to point to your app. Implement logic to identify keywords like STOP, UNSUBSCRIBE, and maintain a list of opted-out users to comply with regulations.

How to optimize performance of the Node.js SMS API?

The tutorial emphasizes the asynchronous nature of Node.js for efficient request handling. Load testing, horizontal scaling, and queuing are recommended for higher throughput, especially with large volumes of messages.