code examples

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

Send SMS with Node.js, Express, and Vonage

A step-by-step guide to building a Node.js/Express application for sending SMS messages using the Vonage Messages API.

This guide provides a step-by-step walkthrough for building a production-ready Node.js application using the Express framework to send SMS messages via the Vonage Messages API. We will cover project setup, core implementation, API endpoint creation, configuration, error handling, security considerations, and deployment.

By the end of this tutorial, you will have a functional Express API endpoint capable of accepting a phone number and message, and using Vonage to deliver that message as an SMS. This enables applications to programmatically send notifications, alerts, verification codes, or other communications directly to users' mobile devices.

Project Overview and Goals

What We'll Build: A simple REST API built with Node.js and Express. This API will expose a single endpoint (/send) that accepts a POST request containing a destination phone number and a message text. It will then use the Vonage Messages API (via the @vonage/server-sdk) to send the message as an SMS.

Problem Solved: Provides a backend service layer to abstract the complexities of interacting directly with an SMS provider API, enabling frontend applications or other services to easily trigger SMS sends through a simple HTTP request.

Technologies:

  • Node.js: A JavaScript runtime environment enabling server-side execution. Chosen for its asynchronous nature, large ecosystem (npm), and suitability for I/O-bound tasks like API interactions.
  • Express: A minimal and flexible Node.js web application framework. Chosen for its simplicity in setting up routes, middleware, and handling HTTP requests/responses.
  • Vonage Messages API: A unified API from Vonage for sending messages across various channels (SMS, MMS, WhatsApp, etc.). Chosen for its robustness, global reach, and developer-friendly SDK. We will use the @vonage/server-sdk Node.js library.
  • dotenv: A module to load environment variables from a .env file into process.env. Chosen for securely managing API keys and configuration outside the codebase.

System Architecture: (Simplified text representation)

+-------------+ +-----------------------+ +-----------------+ +--------------+ | User/Client | ----> | Node.js/Express API | ----> | Vonage SDK | ----> | Vonage API | | (e.g., Web | POST | (Listens on Port XXXX)| | (@vonage/server | | (Sends SMS) | | App, Mobile)| /send | - Validates Request | | -sdk) | | | | | | - Calls Vonage SDK | | - Authenticates | | | | | | - Returns Response | | - Formats Request| | | +-------------+ +-----------------------+ +-----------------+ +--------------+ | | | | +-------------------<------------------------------------+ | SMS Delivery +------------------------------------<--------------------------------------------+ User Receives SMS

Prerequisites:

  • Node.js and npm (or yarn): Installed on your system. Download from nodejs.org.
  • Vonage API Account: Sign up for free at Vonage API Dashboard. You'll receive some free credits for testing.
  • Vonage Application: You need to create a Vonage Application and generate a private key.
  • Vonage Phone Number: You need a Vonage virtual number capable of sending SMS, linked to your Vonage Application.
  • Basic JavaScript/Node.js Knowledge: Familiarity with JavaScript syntax and Node.js concepts.
  • Terminal/Command Line Access: For running commands.
  • (Optional) ngrok: Useful for testing webhooks if you extend this to receive SMS, but not strictly required for sending only. Download from ngrok.com.
  • (Optional) Postman or curl: For testing the API endpoint.

Final Outcome: A running Node.js Express application with a /send endpoint that successfully sends an SMS via Vonage when called with a valid phone number and message. The application will include basic error handling and secure configuration management.


1. Setting up the project

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

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

    bash
    mkdir vonage-sms-sender
    cd vonage-sms-sender
  2. Initialize Node.js Project: This command creates a package.json file, which tracks project details and dependencies. The -y flag accepts default settings.

    bash
    npm init -y

    Optional: Add "type": "module" to your package.json if you prefer using ES Module import/export syntax. If you do, replace require with import (e.g., import express from 'express';), module.exports with export default or named exports, and be mindful of differences like needing file extensions in imports and using import.meta.url for path resolution instead of __dirname. This guide uses the default CommonJS require syntax.

  3. Install Dependencies: We need Express for the web server, the Vonage Server SDK to interact with the API, and dotenv for environment variable management.

    bash
    npm install express @vonage/server-sdk dotenv --save
    • express: Web framework.
    • @vonage/server-sdk: Official Vonage library for Node.js (version 3+ uses Application ID/Private Key for Messages API).
    • dotenv: Loads environment variables from a .env file.
  4. Create Project Structure: Create the necessary files for our application.

    bash
    touch index.js .env .gitignore
    # Optional: mkdir middleware logger services utils
    • index.js: The main entry point for our application logic.
    • .env: Stores sensitive credentials and configuration (API keys, phone numbers). Never commit this file to version control.
    • .gitignore: Specifies files and directories that Git should ignore.
  5. Configure .gitignore: Add node_modules and .env to your .gitignore file to prevent committing dependencies and secrets.

    text
    # .gitignore
    
    node_modules/
    .env
    *.log
    private.key

    We also add private.key as we'll download this file from Vonage later and should not commit it.

  6. Configure .env File: Open the .env file and add the following placeholders. We will fill these in later.

    dotenv
    # .env
    
    # Server Configuration
    PORT=3000
    
    # Vonage API Credentials & Configuration
    VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID
    VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key
    # Alternative: Store key content directly if file path is not feasible (e.g., some PaaS)
    # VONAGE_PRIVATE_KEY_CONTENT="-----BEGIN PRIVATE KEY-----\nYOUR_KEY_CONTENT...\n-----END PRIVATE KEY-----"
    
    # Optional: API Key/Secret - Useful for Vonage CLI, Account/Number management APIs, or older APIs.
    # Not used for Messages API authentication with Application ID/Private Key.
    VONAGE_API_KEY=YOUR_VONAGE_API_KEY
    VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET
    
    VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Your Vonage sending number in E.164 format (e.g., +15551234567)
    
    # Test Recipient (Optional, needed for Trial Accounts)
    TEST_RECIPIENT_NUMBER=RECIPIENT_PHONE_NUMBER # Recipient number in E.164 format (e.g., +15559876543)
    
    # Optional: API Key for securing your own endpoint
    # INTERNAL_API_KEY=your-secret-api-key-here
    • Purpose: Separating configuration from code enhances security and makes deployment to different environments easier.
    • VONAGE_APPLICATION_ID / VONAGE_APPLICATION_PRIVATE_KEY_PATH (or _CONTENT): Used by the @vonage/server-sdk (v3+) to authenticate Messages API calls.
    • VONAGE_API_KEY / VONAGE_API_SECRET: Your main Vonage account credentials. Included here as they are often useful for other Vonage interactions (like CLI setup or using other Vonage APIs) even if not used for authenticating this specific send request.
    • VONAGE_NUMBER: The Vonage virtual number messages will be sent from.
    • PORT: The port your Express server will listen on.

2. Implementing core functionality

Now, let's write the code to initialize the Vonage SDK and create the function to send SMS messages.

  1. Load Environment Variables: At the very top of index.js, require and configure dotenv to load the variables from your .env file into process.env.

    javascript
    // index.js
    require('dotenv').config(); // Load environment variables from .env file
  2. Require Dependencies: Import the necessary modules: express and the Vonage class from the SDK.

    javascript
    // index.js
    require('dotenv').config();
    const express = require('express');
    const { Vonage } = require('@vonage/server-sdk');
    const path = require('path'); // To resolve the private key path if used
  3. Initialize Vonage SDK: Create an instance of the Vonage client using your Application ID and private key. Prioritize key content from environment variable if available, otherwise use the path.

    javascript
    // index.js
    // ... (requires)
    
    // --- Vonage SDK Initialization ---
    // Determine private key source: prefer content from env var, fallback to path
    const privateKeySource = process.env.VONAGE_PRIVATE_KEY_CONTENT
      ? process.env.VONAGE_PRIVATE_KEY_CONTENT.replace(/\\n/g, '\n') // Handle escaped newlines if needed
      : path.resolve(process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH);
    
    const vonage = new Vonage({
      applicationId: process.env.VONAGE_APPLICATION_ID,
      privateKey: privateKeySource
    });
    
    // --- Helper Function to Send SMS ---
    async function sendSms(recipient, messageText) {
      const fromNumber = process.env.VONAGE_NUMBER;
      const toNumber = recipient; // Expecting E.164 format
    
      try {
        console.log(`Attempting to send SMS from ${fromNumber} to ${toNumber}`);
        const response = await vonage.messages.send({
          message_type: ""text"",  // Specify message type as text
          text: messageText,     // The content of the SMS
          to: toNumber,          // Recipient phone number
          from: fromNumber,      // Your Vonage virtual number
          channel: ""sms""         // Specify the channel as SMS
        });
    
        console.log(`SMS submitted successfully to ${recipient}. Message UUID: ${response.message_uuid}`);
        return { success: true, message_uuid: response.message_uuid };
    
      } catch (error) {
        // Log detailed error information, especially Vonage API errors
        let errorMessage = `Error sending SMS to ${recipient}: ${error.message}`;
        let vonageErrorDetails = null;
    
        if (error.response && error.response.data) {
          vonageErrorDetails = error.response.data;
          errorMessage = `Vonage API Error sending SMS to ${recipient}: ${vonageErrorDetails.title || 'Unknown error'} - ${vonageErrorDetails.detail || JSON.stringify(vonageErrorDetails)}`;
          console.error(errorMessage, vonageErrorDetails);
        } else {
          console.error(errorMessage, error); // Log the full error object for other types
        }
    
        // Rethrow a structured error or a more generic one for the API layer
        const apiError = new Error(errorMessage);
        apiError.vonageDetails = vonageErrorDetails; // Attach details if available
        apiError.statusCode = error.response?.status || 500; // Preserve status code if possible
        throw apiError;
      }
    }
    
    // Export the function if needed elsewhere (e.g., for testing or separation)
    // module.exports = { sendSms }; // CommonJS export
    • Why async/await? The vonage.messages.send method returns a Promise. Using async/await makes handling asynchronous operations cleaner.
    • Private Key Handling: The code now checks for VONAGE_PRIVATE_KEY_CONTENT first, allowing deployment environments to provide the key content directly. It falls back to using the file path specified by VONAGE_APPLICATION_PRIVATE_KEY_PATH. Note the .replace(/\\n/g, '\n') which might be needed if your environment variable mechanism escapes newline characters.
    • Payload Structure: Standard JSON format with double quotes for keys and string values.
    • Error Handling: The try...catch block handles errors. It logs detailed info, especially parsing Vonage's structured error response (error.response.data). It then rethrows an error, potentially enriching it with Vonage details and status code, for the calling layer (API endpoint) to handle.

3. Building a complete API layer

Let's create the Express server and the API endpoint to trigger the sendSms function.

  1. Initialize Express App: Set up the basic Express application and configure middleware to parse JSON request bodies.

    javascript
    // index.js
    // ... (requires, Vonage init, sendSms function)
    
    // --- Express App Setup ---
    const app = express();
    const port = process.env.PORT || 3000; // Use PORT from .env or default to 3000
    
    // Middleware to parse JSON bodies
    app.use(express.json());
    // Middleware to parse URL-encoded bodies (optional but good practice)
    app.use(express.urlencoded({ extended: true }));
  2. Create /send Endpoint: Define a POST route at /send. This endpoint will expect a JSON body with to and message properties.

    javascript
    // index.js
    // ... (Express app setup)
    
    // --- API Endpoint ---
    app.post('/send', async (req, res) => {
      // 1. Basic Input Validation
      const { to, message } = req.body;
      if (!to || !message) {
        console.error('Validation Error: Missing "to" or "message" in request body');
        // Use return to stop execution after sending response
        return res.status(400).json({ success: false, message: 'Missing required fields: "to" and "message".' });
      }
    
      // Basic E.164 format check (starts with '+', followed by digits)
      // Consider using a library like 'libphonenumber-js' for robust validation
      if (!/^\+[1-9]\d{1,14}$/.test(to)) {
         console.error(`Validation Error: Invalid phone number format for "to": ${to}`);
         return res.status(400).json({ success: false, message: 'Invalid "to" phone number format. Use E.164 format (e.g., +15551234567).' });
      }
    
      try {
        // 2. Call the Core SMS Sending Function
        const result = await sendSms(to, message);
        console.log(`API call successful for sending SMS to ${to}`);
    
        // 3. Send Success Response (Flatter structure as suggested)
        return res.status(200).json({
            success: true,
            message: 'SMS submitted successfully.',
            message_uuid: result.message_uuid // Directly include the UUID
        });
        // Alternative nested structure:
        // return res.status(200).json({ success: true, message: 'SMS submitted successfully.', data: result });
    
      } catch (error) {
        // 4. Send Error Response
        console.error(`API Error: Failed to process /send request for ${to}:`, error.message);
    
        // Determine appropriate status code - use error's statusCode if available, else 500
        const statusCode = error.statusCode || 500;
    
        // Return a generic error message to the client, log details server-side
        return res.status(statusCode).json({
             success: false,
             message: 'Failed to send SMS.' // Generic message
             // Optionally include a non-sensitive error code or type here
             // error_code: 'SEND_FAILED'
        });
        // Avoid leaking internal details like error.message directly in production responses:
        // return res.status(500).json({ success: false, message: 'Failed to send SMS.', error: error.message }); // <-- Leaks details
      }
    });
    • Request Validation: Basic checks for presence and E.164 format. Production apps should use validation libraries (express-validator, joi, zod).
    • Authentication/Authorization: This endpoint is open. Secure it using middleware (see Section 7).
    • Response Format: Returns consistent JSON. The success response uses the suggested flatter structure. The error response returns a generic message and logs details server-side, avoiding leakage of internal error.message.
  3. Start the Server: Make the application listen on the configured port.

    javascript
    // index.js
    // ... (API endpoint)
    
    // --- Start Server ---
    // Check if the script is being run directly (and not required by a test runner)
    if (require.main === module) {
        app.listen(port, () => {
            console.log(`Server listening at http://localhost:${port}`);
            console.log(`Vonage Application ID configured: ${process.env.VONAGE_APPLICATION_ID ? 'Yes' : 'No'}`);
            console.log(`Attempting to send SMS from: ${process.env.VONAGE_NUMBER}`);
        });
    }
    
    // Export the app instance for potential use in integration tests
    module.exports = { app, sendSms }; // Export app and sendSms
    • Testability: The if (require.main === module) block prevents the server from auto-starting when the file is required by test files. We also export app and sendSms to make them accessible for testing.
  4. Testing with curl: Once the server is running (node index.js), test the endpoint:

    bash
    curl -X POST http://localhost:3000/send \
       -H "Content-Type: application/json" \
       -d '{
             "to": "YOUR_RECIPIENT_NUMBER_E164",
             "message": "Hello from Vonage and Node.js!"
           }'
    • Expected Success Response (200 OK):
      json
      {
        "success": true,
        "message": "SMS submitted successfully.",
        "message_uuid": "some-unique-message-identifier"
      }
    • Expected Validation Error Response (400 Bad Request):
      json
      {
        "success": false,
        "message": "Missing required fields: \"to\" and \"message\"."
      }

    or json { "success": false, "message": "Invalid \"to\" phone number format. Use E.164 format (e.g., +15551234567)." }

    • Expected Server/Vonage Error Response (e.g., 500 Internal Server Error):
      json
      {
        "success": false,
        "message": "Failed to send SMS."
      }
      (Check server logs for the detailed error like Vonage API Error: The requested resource does not exist...)

4. Integrating with necessary third-party services (Vonage)

This section details how to get the required credentials from Vonage and configure your application.

  1. Sign Up/Log In: Go to the Vonage API Dashboard and either sign up for a new account or log in.

  2. Get API Key and Secret:

    • Navigate to the main Dashboard page after logging in.
    • Your API Key and API Secret are displayed near the top.
    • Copy these values and paste them into your .env file for VONAGE_API_KEY and VONAGE_API_SECRET. Remember, these are useful for other Vonage tools/APIs but not for authenticating Messages API calls when using Application ID/Private Key.
  3. Create a Vonage Application: The Messages API requires authentication via an Application ID and a private key.

    • In the left-hand navigation menu, go to Applications > + Create a new application.
    • Enter an Application name (e.g., ""Node SMS Sender App"").
    • Click Generate public and private key. This will automatically download a private.key file. Save this file securely in your project's root directory (or note its content). Ensure .gitignore includes private.key.
    • Your Application ID will be displayed on this page. Copy it and paste it into your .env file for VONAGE_APPLICATION_ID.
    • Scroll down to Capabilities.
    • Toggle on the Messages capability.
    • Provide Inbound URL and Status URL. Even if unused in this guide, they are required. Use placeholders like https://example.com/webhooks/inbound and https://example.com/webhooks/status, or use an ngrok URL if testing webhooks. Ensure POST is selected for the HTTP Method.
    • Click Generate application.
  4. Purchase/Link a Vonage Number: You need a Vonage virtual number to send SMS from.

    • In the left-hand navigation menu, go to Numbers > Buy numbers.
    • Search for numbers by country and features (ensure SMS is selected). Choose a number and click Buy.
    • Alternatively, go to Numbers > Your numbers.
    • Find the number you want to use. Click the Gear icon (Manage) or Link button.
    • Select the Vonage Application you just created (""Node SMS Sender App"") from the dropdown menu to link the number to the application. This is crucial. Messages sent using Application ID/Private Key authentication must originate from a number linked to that application.
    • Copy the full number (E.164 format, e.g., +15551234567) and paste it into your .env file for VONAGE_NUMBER.
  5. Verify .env Variables: Double-check that all VONAGE_ variables in your .env file are correctly filled:

    • PORT
    • VONAGE_APPLICATION_ID
    • VONAGE_APPLICATION_PRIVATE_KEY_PATH (or VONAGE_PRIVATE_KEY_CONTENT)
    • VONAGE_API_KEY, VONAGE_API_SECRET (optional but recommended)
    • VONAGE_NUMBER
    • TEST_RECIPIENT_NUMBER (especially for trial accounts)
  6. (Trial Accounts Only) Add Test Numbers: If using a free trial Vonage account, you can typically only send SMS to verified numbers.

    • Go to the Vonage Dashboard > Sandbox & Test Numbers (or similar section).
    • Add the TEST_RECIPIENT_NUMBER (your personal phone) to the list. Verify it using the code sent via SMS/call.
    • Failure results in a ""Non-Whitelisted Destination"" error.

5. Implementing proper error handling, logging, and retry mechanisms

Enhance basic try...catch with better logging and resilience.

  1. Consistent Error Handling Strategy:

    • API Layer (/send): Catch errors from sendSms. Log internal details. Return generic, user-friendly errors with appropriate HTTP status codes (4xx for client issues, 5xx for server/Vonage issues). (Implemented in Section 3).
    • Core Function (sendSms): Catch specific Vonage errors. Log detailed info. Rethrow standardized errors for the API layer. (Improved in Section 2).
  2. Enhanced Logging (Example: Winston): Use a dedicated library for structured, leveled logging.

    • Install: npm install winston --save

    • Configure (logger.js):

      javascript
      // logger.js
      const winston = require('winston');
      
      const logger = winston.createLogger({
        level: process.env.LOG_LEVEL || 'info', // Control level via env var
        format: winston.format.combine(
          winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
          winston.format.errors({ stack: true }), // Log stack traces
          winston.format.splat(),
          winston.format.json() // Log in JSON format
        ),
        defaultMeta: { service: 'vonage-sms-sender' }, // Add service context
        transports: [
          // Log errors to error.log
          new winston.transports.File({ filename: 'error.log', level: 'error' }),
          // Log all levels to combined.log
          new winston.transports.File({ filename: 'combined.log' }),
        ],
      });
      
      // Log to console in non-production environments with colorization
      if (process.env.NODE_ENV !== 'production') {
        logger.add(new winston.transports.Console({
          format: winston.format.combine(
            winston.format.colorize(),
            winston.format.simple() // Simple format for console
          ),
        }));
      }
      
      module.exports = logger;
    • Use Logger in index.js: Replace console.log/error with logger.info/warn/error.

      javascript
      // index.js
      // ... requires ...
      const logger = require('./logger'); // Assuming logger.js is created
      
      // ... inside sendSms function ...
      async function sendSms(recipient, messageText) {
        // ...
        try {
          logger.info(`Attempting to send SMS from ${fromNumber} to ${toNumber}`, { recipient, from: fromNumber });
          const response = await vonage.messages.send({ /* ... payload ... */ });
          logger.info(`SMS submitted successfully to ${recipient}. Message UUID: ${response.message_uuid}`, { recipient, message_uuid: response.message_uuid });
          return { success: true, message_uuid: response.message_uuid };
        } catch (error) {
          const vonageErrorDetails = error.response?.data;
          const errorMessage = `Failed sending SMS to ${recipient}: ${error.message}`;
          logger.error(errorMessage, {
             recipient,
             error: error.message, // Main error message
             vonageDetails: vonageErrorDetails, // Include Vonage specific error if available
             stack: error.stack // Include stack trace
          });
          // Rethrow error for API layer (as implemented in Section 2)
          const apiError = new Error(`Failed to send SMS to ${recipient}.`); // Keep rethrown message generic
          apiError.vonageDetails = vonageErrorDetails;
          apiError.statusCode = error.response?.status || 500;
          throw apiError;
        }
      }
      
      // ... inside /send endpoint ...
      app.post('/send', async (req, res) => {
         // ... validation ...
         if (!to || !message) {
           logger.warn('Validation Error: Missing fields in request body', { body: req.body });
           return res.status(400).json({ /* ... */ });
         }
         if (!/^\+[1-9]\d{1,14}$/.test(to)) {
           logger.warn(`Validation Error: Invalid phone number format`, { to });
           return res.status(400).json({ /* ... */ });
         }
         // ... etc ...
      
         try {
           const result = await sendSms(to, message);
           logger.info(`API call successful for sending SMS`, { recipient: to, message_uuid: result.message_uuid });
           return res.status(200).json({ /* ... */ });
         } catch (error) {
           // Log the detailed error caught from sendSms
           logger.error(`API Error: Failed to process /send request`, {
               recipient: to,
               error: error.message,
               vonageDetails: error.vonageDetails, // Include details if attached
               statusCode: error.statusCode,
               stack: error.stack
           });
           // Return generic response (as implemented in Section 3)
           return res.status(error.statusCode || 500).json({ success: false, message: 'Failed to send SMS.' });
         }
      });
      
      // ... starting server ...
      if (require.main === module) {
          app.listen(port, () => {
              logger.info(`Server listening at http://localhost:${port}`);
              // ... other startup logs ...
          });
      }
  3. Retry Mechanisms (Example: async-retry): Handle transient network or API issues.

    • Install: npm install async-retry --save

    • Implement Retry in sendSms:

      javascript
      // index.js
      // ... requires ...
      const retry = require('async-retry');
      const logger = require('./logger');
      
      // ... Vonage init ...
      
      async function sendSms(recipient, messageText) {
        const fromNumber = process.env.VONAGE_NUMBER;
        const toNumber = recipient;
      
        try {
          // Wrap the Vonage call in retry logic
          const response = await retry(async (bail, attemptNumber) => {
            logger.info(`Attempt ${attemptNumber} to send SMS to ${recipient}`);
            try {
               const result = await vonage.messages.send({
                  message_type: "text",
                  text: messageText,
                  to: toNumber,
                  from: fromNumber,
                  channel: "sms"
               });
               // Success on this attempt
               return result;
            } catch (error) {
              const statusCode = error.response?.status;
              const vonageError = error.response?.data;
              logger.warn(`Attempt ${attemptNumber} failed for ${recipient}: ${error.message}`, { attempt: attemptNumber, recipient, error: error.message, statusCode, vonageError });
      
              // Decide whether to retry or bail (stop retrying)
              // DO NOT retry non-recoverable errors (4xx client errors like bad number, auth error)
              if (statusCode && statusCode >= 400 && statusCode < 500) {
                  logger.error(`Non-retryable error (status ${statusCode}) encountered for ${recipient}. Bailing out.`, { recipient, statusCode, vonageError });
                  // Use bail(error) to stop retrying and make retry() reject with this error
                  bail(error);
                  return; // Exit async function after bailing
              }
      
              // For other errors (network, 5xx server errors), throw to trigger retry
              throw error;
            }
          }, {
            retries: 3,          // Try initial call + 3 retries (total 4 attempts)
            factor: 2,           // Exponential backoff factor (waits 1s, 2s, 4s)
            minTimeout: 1000,    // Minimum wait time: 1 second
            maxTimeout: 5000,    // Maximum wait time between retries: 5 seconds
            onRetry: (error, attemptNumber) => {
              // This is called before making the next attempt
              logger.warn(`Retrying SMS send to ${recipient} (attempt ${attemptNumber}) due to error: ${error.message}`, { attempt: attemptNumber, recipient });
            }
          }); // End of retry block
      
          // If retry block succeeds (doesn't bail or throw after max retries)
          logger.info(`SMS submitted successfully to ${recipient} after attempt(s). Message UUID: ${response.message_uuid}`, { recipient, message_uuid: response.message_uuid });
          return { success: true, message_uuid: response.message_uuid };
      
        } catch (error) {
           // This catch block executes if retry() ultimately fails (bailed or max retries exceeded)
           const finalStatusCode = error.response?.status;
           const finalVonageError = error.response?.data;
           logger.error(`Failed to send SMS to ${recipient} after all retries: ${error.message}`, {
               recipient,
               finalError: error.message,
               finalStatusCode,
               finalVonageError,
               stack: error.stack
           });
      
           // Rethrow a final error for the API layer
           const apiError = new Error(`Failed to send SMS to ${recipient} after multiple attempts.`);
           apiError.vonageDetails = finalVonageError;
           apiError.statusCode = finalStatusCode || 500;
           throw apiError;
        }
      }
      // ... rest of index.js ...
    • Considerations: Carefully tune retry counts, timeouts, and the logic for identifying retryable errors (network errors, HTTP 5xx status codes) vs. non-retryable errors (HTTP 4xx).


6. Creating a database schema and data layer (Optional)

While not essential just for sending an SMS via API, storing message logs, status updates (via webhooks), or user data would require a database. This section is intentionally omitted as it significantly expands the scope beyond the core task of sending an SMS. If needed, consider technologies like PostgreSQL, MongoDB, or MySQL, along with an ORM/ODM like Sequelize or Mongoose.

Frequently Asked Questions

How to send SMS with Node.js and Express?

Use the Vonage Messages API with the @vonage/server-sdk and Express.js to create an API endpoint that handles sending SMS messages. This setup allows your Node.js application to easily send SMS messages programmatically.

What is the Vonage Messages API used for?

The Vonage Messages API is a versatile tool for sending messages through various channels, including SMS, MMS, and WhatsApp. In this tutorial, it's used to send text messages (SMS) from your Node.js application to user's mobile devices.

Why use Node.js for sending SMS messages?

Node.js is well-suited for this task due to its asynchronous nature, which is efficient for I/O operations like API calls. Its large package ecosystem (npm) provides the necessary tools, such as Express.js and the Vonage SDK, to streamline development.

When should I use the Vonage server SDK?

Use the @vonage/server-sdk when you need to integrate Vonage's communication services, like sending SMS messages, within your Node.js applications. The SDK simplifies interactions with the Vonage API, handles authentication, and provides convenient methods for sending different types of messages.

Can I use dotenv to store Vonage API credentials?

Yes, dotenv is recommended for storing sensitive information like API keys and secrets outside of your codebase. Create a .env file to store these values, and ensure this file is included in your .gitignore to prevent accidentally committing it to version control.

How to create a Vonage application for SMS?

In your Vonage dashboard, go to 'Applications', create a new application, enable the 'Messages' capability, generate your keys, and link a Vonage virtual number. This linked number is essential for sending SMS messages with the application.

What is the purpose of the private key in Vonage?

The private key, along with the Application ID, authenticates your application with the Vonage Messages API. Securely store the private key file (private.key) in your project directory, but ensure it's *not* committed to version control.

How to handle errors when sending SMS with Vonage?

Implement `try...catch` blocks to handle potential errors during the SMS sending process. Log detailed error information, especially the error responses from the Vonage API, for debugging. Return generic error messages to the client for security.

What is the role of Express in sending SMS messages?

Express.js simplifies the process of creating the API endpoint that your application will use to send SMS messages. It handles routing, middleware for request parsing, and provides a structure for sending responses back to the client.

How to structure the API endpoint for sending SMS messages?

Create a POST endpoint (e.g., '/send') that accepts 'to' (recipient number) and 'message' (message content) in the request body. Validate these inputs and then use the Vonage SDK to send the SMS based on these parameters.

How to install the necessary npm packages for this project?

Run `npm install express @vonage/server-sdk dotenv --save` in your terminal. This will install Express for the web server, the Vonage Server SDK for the API integration, and dotenv for managing environment variables.

Where can I find my Vonage API key and secret?

Your API Key and Secret are displayed on your Vonage API Dashboard after logging in. While these are not directly used to send messages in v3+ of the Vonage Node SDK, they are useful for other tasks like number management.

How to get a Vonage virtual number?

You can purchase a new Vonage virtual number or link an existing one to your application through the 'Numbers' section of the Vonage API Dashboard. Ensure you select a number with SMS capabilities.