code examples

Sent logo
Sent TeamMar 8, 2026 / code examples / nodejs

Send SMS with Node.js, Express, and Vonage Messages API

Complete guide for building a production-ready Node.js application using Express to send SMS messages via Vonage Messages API

Send SMS with Node.js, Express, and Vonage Messages API

This guide provides a complete walkthrough for building a production-ready Node.js application using the Express framework to send SMS messages via the Vonage Messages API. We'll cover everything from project setup and configuration to core functionality, error handling, security, and deployment.

By the end of this guide, you will have a simple but robust API endpoint capable of accepting a phone number and message, and reliably sending an SMS using Vonage.

Project Overview and Goals

Goal: To create a simple REST API endpoint using Node.js and Express that sends an SMS message to a specified recipient number using the Vonage Messages API.

Problem Solved: Provides a reusable, server-side component for applications needing to send transactional or notification SMS messages programmatically.

Technologies Used:

  • Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its non-blocking I/O, large ecosystem (npm), and JavaScript ubiquity.
  • Express: A minimal and flexible Node.js web application framework. Chosen for its simplicity, robustness, and widespread adoption for building APIs.
  • Vonage Messages API: A powerful API for sending messages across various channels (SMS, MMS, WhatsApp, etc.). Chosen for its reliability, global reach, and comprehensive features for SMS communication. We specifically use the Messages API over the older SMS API for its modern features and multi-channel capabilities.
  • dotenv: A zero-dependency module that loads environment variables from a .env file into process.env. Chosen for securely managing API keys and configuration outside of the codebase.
  • Vonage Node.js Server SDK (@vonage/server-sdk): The official Vonage SDK simplifies interaction with the Vonage APIs.

System Architecture:

text
+-------------+      +------------------------+      +---------------------+      +-------------------+
| User/Client | ---> | Node.js/Express Server | ---> | Vonage Messages API | ---> | Recipient's Phone |
| (e.g., curl,|      | (API Endpoint /send-sms)|      | (Sends SMS)         |      | (Receives SMS)    |
| Postman)    |      +------------------------+      +---------------------+      +-------------------+
+-------------+             |
                            | Uses Vonage SDK
                            | Reads Credentials (.env)
                            +------------------------+
                            |   Vonage SDK           |
                            |   dotenv               |
                            +------------------------+

Prerequisites:

  • Node.js and npm (or yarn): Installed on your development machine. (Download Node.js)
  • Vonage API Account: Sign up for free. (Vonage Sign Up)
  • Vonage Virtual Number: You need at least one Vonage phone number capable of sending SMS. You can purchase one from the Vonage dashboard.
  • Vonage Application: You'll need to create a Vonage Application within your dashboard to obtain an Application ID and generate a private key.
  • (Optional) curl or Postman: For testing the API endpoint.

1. Setting up the Project

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

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

    bash
    mkdir vonage-sms-sender
    cd vonage-sms-sender
  2. Initialize npm Project: This creates a package.json file to manage your project's dependencies and scripts.

    bash
    npm init -y
  3. Install Dependencies: We need express for the web server, @vonage/server-sdk to interact with the Vonage API, and dotenv to manage environment variables.

    bash
    npm install express @vonage/server-sdk dotenv --save
    • express: Web framework.
    • @vonage/server-sdk: Vonage API client library.
    • dotenv: Loads environment variables from .env.
  4. Create Project Structure: Create the main application file and a file for environment variables.

    bash
    touch index.js .env .env.example .gitignore
    • index.js: Main application code.
    • .env: Stores your secret API credentials (will be ignored by Git).
    • .env.example: A template showing required environment variables (safe to commit).
    • .gitignore: Specifies files Git should ignore.
  5. Configure .gitignore: It's crucial never to commit your secrets. Add .env and node_modules to your .gitignore file:

    text
    # .gitignore
    
    # Dependencies
    node_modules/
    
    # Environment variables
    .env
    
    # Optional keys
    private.key
    
    # Logs
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    
    # Optional editor directories
    .vscode/
    .idea/
  6. Set up Environment Variables (.env.example): Define the structure of your required environment variables in .env.example. This serves as a template for anyone setting up the project.

    dotenv
    # .env.example
    
    # Port for the Express server
    PORT=3000
    
    # Vonage API Credentials
    # Found in your Vonage Application settings
    VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID
    
    # Provide EITHER the path to the private key file OR the key content directly
    # Path is generally preferred for local development
    VONAGE_PRIVATE_KEY_PATH=./private.key
    # Content is useful for environments where mounting files is hard (e.g., some PaaS)
    # VONAGE_PRIVATE_KEY_CONTENT="-----BEGIN PRIVATE KEY-----\nYOUR_KEY_CONTENT...\n-----END PRIVATE KEY-----"
    
    # Vonage virtual number to send SMS from (E.164 format recommended)
    VONAGE_FROM_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER
  7. Populate .env (DO NOT COMMIT THIS FILE): Copy .env.example to .env and fill in your actual credentials. You'll obtain these in the "Integrating with Vonage" section. Use either VONAGE_PRIVATE_KEY_PATH or VONAGE_PRIVATE_KEY_CONTENT.

    bash
    cp .env.example .env

    Edit .env and replace the placeholder values. Make sure the private.key file exists at the specified path if using VONAGE_PRIVATE_KEY_PATH.

2. Implementing Core Functionality (Sending SMS)

We'll encapsulate the Vonage SMS sending logic within a reusable function.

  1. Edit index.js: Set up the Express server, import modules, and initialize the Vonage client.

    javascript
    // index.js
    require('dotenv').config(); // Load environment variables from .env file
    const express = require('express');
    const { Vonage } = require('@vonage/server-sdk');
    
    // --- Configuration ---
    const app = express();
    const port = process.env.PORT || 3000;
    
    // --- Vonage Client Initialization ---
    let vonage;
    try {
        // Ensure required base environment variables are set
        if (!process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_FROM_NUMBER) {
            throw new Error('Missing VONAGE_APPLICATION_ID or VONAGE_FROM_NUMBER in .env');
        }
    
        // Determine private key source (path or content)
        let privateKey;
        if (process.env.VONAGE_PRIVATE_KEY_CONTENT) {
            privateKey = Buffer.from(process.env.VONAGE_PRIVATE_KEY_CONTENT, 'utf8');
            console.log('Initializing Vonage client using private key content from environment variable.');
        } else if (process.env.VONAGE_PRIVATE_KEY_PATH) {
            privateKey = process.env.VONAGE_PRIVATE_KEY_PATH;
             console.log('Initializing Vonage client using private key path from environment variable.');
        } else {
            throw new Error('Missing Vonage private key. Set either VONAGE_PRIVATE_KEY_PATH or VONAGE_PRIVATE_KEY_CONTENT environment variable.');
        }
    
        // Initialize Vonage SDK
        vonage = new Vonage({
            applicationId: process.env.VONAGE_APPLICATION_ID,
            privateKey: privateKey
        });
    
        console.log('Vonage client initialized successfully.');
    
    } catch (error) {
        console.error('Error initializing Vonage client:', error.message);
        console.error('Please check your .env file, ensure required variables are set, and the private key path/content is correct.');
        process.exit(1); // Exit if configuration is invalid
    }
    
    
    // --- Middleware ---
    app.use(express.json()); // Parse JSON request bodies
    app.use(express.urlencoded({ extended: true })); // Parse URL-encoded request bodies
    
    // --- Core SMS Sending Function ---
    /**
     * Sends an SMS message using the Vonage Messages API.
     * @param {string} to - The recipient phone number (E.164 format recommended).
     * @param {string} text - The message content.
     * @returns {Promise<object>} - A promise that resolves with the Vonage API response.
     * @throws {Error} - Throws an error if sending fails.
     */
    async function sendSms(to, text) {
        console.log(`Attempting to send SMS to: ${to}`);
        try {
            const resp = await vonage.messages.send({
                channel: 'sms',
                message_type: 'text',
                to: to,
                from: process.env.VONAGE_FROM_NUMBER, // Your Vonage virtual number
                text: text
            });
            console.log(`SMS submitted successfully to ${to}. Message UUID: ${resp.message_uuid}`);
            return resp; // Contains message_uuid
        } catch (err) {
            console.error(`Error sending SMS to ${to}:`, err.response ? err.response.data : err.message);
            // Rethrow a more specific error for the API layer to handle
            throw new Error(`Failed to send SMS. Vonage API Error: ${err.response ? JSON.stringify(err.response.data) : err.message}`);
        }
    }
    
    // --- API Routes (Defined in next section) ---
    // ...
    
    // --- Server Start ---
    app.listen(port, () => {
        console.log(`Server listening on http://localhost:${port}`);
    });
    
    module.exports = { app, sendSms }; // Export for potential testing

    Explanation:

    • require('dotenv').config(): Loads variables from .env into process.env.
    • express(): Creates an Express application instance.
    • Vonage Client Initialization:
      • Checks for required VONAGE_APPLICATION_ID and VONAGE_FROM_NUMBER.
      • Checks for either VONAGE_PRIVATE_KEY_CONTENT or VONAGE_PRIVATE_KEY_PATH.
      • If VONAGE_PRIVATE_KEY_CONTENT is found, it's used directly (as a Buffer).
      • Otherwise, VONAGE_PRIVATE_KEY_PATH is used (the SDK reads the file).
      • Initializes new Vonage(...) with the application ID and the determined private key (either content or path).
      • Includes robust error handling for missing configuration or initialization failures.
    • express.json(), express.urlencoded(): Middleware to parse incoming request bodies.
    • sendSms(to, text): An async function that uses vonage.messages.send to send the SMS.
      • channel: 'sms': Specifies SMS channel.
      • message_type: 'text': Specifies plain text message.
      • to: Recipient number.
      • from: Your Vonage number (from .env).
      • text: The message content.
    • Error Handling (sendSms): Uses a try...catch block. Logs success or failure. If Vonage returns an error (e.g., err.response.data), it logs the detailed error from the API. It rethrows a standardized error for the API route handler.

3. Building a Complete API Layer

Now, let's create the Express API endpoint to trigger the SMS sending function.

  1. Add API Route to index.js: Add the following code block before the app.listen call in index.js.

    API Endpoint: POST /send-sms

    • Description: Sends an SMS message.

    • Access: Public (consider adding authentication in production).

    • Request Body: JSON object containing:

      • to (string): The recipient phone number (E.164 format recommended).
      • message (string): The text message content.
      json
      {
        "to": "+1234567890",
        "message": "Hello from Vonage!"
      }
    • Success Response (200 OK):

      json
      {
        "success": true,
        "messageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "detail": "SMS submitted successfully."
      }
    • Error Response (400 Bad Request or 500 Internal Server Error):

      json
      {
        "success": false,
        "error": "Error description (e.g., Missing required fields, Invalid phone number format, Failed to send SMS)."
      }
    javascript
    // index.js (continued)
    
    // --- API Routes ---
    
    app.post('/send-sms', async (req, res) => {
        const { to, message } = req.body;
    
        // Basic Input Validation
        if (!to || !message) {
            console.warn('Validation failed: Missing `to` or `message` in request body.');
            return res.status(400).json({ success: false, error: 'Missing required fields: `to` and `message`.' });
        }
    
        // Basic Phone Number Format Check (Simple Example - NOT PRODUCTION READY)
        // WARNING: This regex is very basic and might not cover all valid E.164 formats or edge cases.
        // For production, use a dedicated library like `libphonenumber-js` for robust validation.
        if (!/^\+?[1-9]\d{1,14}$/.test(to)) {
             console.warn(`Validation failed: Invalid phone number format: ${to}`);
             return res.status(400).json({ success: false, error: 'Invalid phone number format. Use E.164 format (e.g., +1234567890).' });
        }
    
    
        try {
            const result = await sendSms(to, message);
            res.status(200).json({
                success: true,
                messageId: result.message_uuid,
                detail: "SMS submitted successfully."
            });
        } catch (error) {
            // Error already logged in sendSms function
            res.status(500).json({
                success: false,
                error: error.message || "An internal server error occurred while sending SMS."
            });
        }
    });
    
    // Add a simple health check endpoint
    app.get('/health', (req, res) => {
        res.status(200).json({ status: 'UP' });
    });
    
    // --- Server Start ---
    // ... (rest of the file)

    Explanation:

    • app.post('/send-sms', ...): Defines a route that listens for POST requests on the /send-sms path.
    • Input Validation: Checks if to and message are present in the JSON request body (req.body). Returns a 400 Bad Request error if missing. Includes a very basic regex check for phone number format (E.164 recommended). Crucially, this regex is insufficient for production use. A dedicated library like google-libphonenumber or libphonenumber-js should be used for proper validation in a real-world application.
    • Call sendSms: Calls the core function with the validated to and message.
    • Response Handling:
      • On success (try block): Returns a 200 OK status with success: true and the message_uuid received from Vonage.
      • On failure (catch block): Returns a 500 Internal Server Error status with success: false and the error message generated by sendSms.

4. Integrating with Vonage

This involves creating a Vonage Application and obtaining the necessary credentials.

  1. Log in to Vonage Dashboard: Go to https://dashboard.nexmo.com/ and log in.

  2. Navigate to Applications: In the left-hand navigation menu, go to "Applications" under "Build & Manage".

  3. Create a New Application:

    • Click the "+ Create a new application" button.
    • Give your application a name (e.g., "Node SMS Sender App").
    • Click "Generate public and private key". This will automatically download the private.key file. Save this file securely in the root of your project directory (the same place as index.js). Do not commit this file to Git.
    • Enable the Messages capability.
    • You'll see fields for "Inbound URL" and "Status URL". For sending SMS only, these are not strictly required to be functional endpoints, but the Vonage platform expects URLs. You can enter placeholder URLs like https://example.com/webhooks/inbound and https://example.com/webhooks/status. If you later want to receive status updates or inbound messages, you'll need to update these with real endpoints.
    • Click "Generate new application".
  4. Get Application ID: On the application details page, copy the Application ID.

  5. Link Your Vonage Number:

    • Scroll down to the "Link virtual numbers" section on the application details page.
    • Find the Vonage number you want to send SMS from.
    • Click the "Link" button next to it. This authorizes the application to use this number.
  6. Update .env File: Open your .env file and paste the credentials:

    dotenv
    # .env (Your actual values)
    PORT=3000
    VONAGE_APPLICATION_ID=YOUR_COPIED_APPLICATION_ID
    # Use EITHER VONAGE_PRIVATE_KEY_PATH OR VONAGE_PRIVATE_KEY_CONTENT
    VONAGE_PRIVATE_KEY_PATH=./private.key # Ensure this matches the location of your downloaded key
    # VONAGE_PRIVATE_KEY_CONTENT="PASTE_KEY_CONTENT_HERE" # Alternative if not using path
    VONAGE_FROM_NUMBER=YOUR_LINKED_VONAGE_NUMBER # e.g., +14155550100

    (Ensure only one private key variable is uncommented and correctly set)

5. Implementing Error Handling and Logging

We've already incorporated basic error handling and logging:

  • Initialization Errors: The code checks for missing environment variables and errors during Vonage client setup, exiting gracefully if critical configuration is missing.
  • API Request Validation: The /send-sms endpoint validates required input (to, message) and returns specific 400 errors. Includes a basic (non-production) phone format check.
  • Vonage API Errors: The sendSms function catches errors from the Vonage SDK. It logs detailed error information (err.response.data if available) and returns a user-friendly error message in the API response (500 status).
  • Logging: Uses console.log, console.warn, and console.error for different levels of information.

Further Enhancements (Production Considerations):

  • Structured Logging: Use a library like winston or pino for structured JSON logging, which is easier to parse and analyze in production monitoring tools (e.g., Datadog, Splunk).
  • Centralized Error Tracking: Integrate with services like Sentry or Bugsnag to automatically capture and report exceptions.
  • Retry Mechanisms: For transient network issues or temporary Vonage API unavailability, implement a retry strategy (e.g., exponential backoff) using libraries like async-retry. However, for SMS sending, be cautious about retrying automatically, as it could lead to duplicate messages if the initial request succeeded but the response was lost. It's often better to rely on Vonage's internal retries or flag the message for manual review/retry.
  • Robust Phone Validation: Replace the basic regex check with libphonenumber-js or google-libphonenumber.

6. Creating a Database Schema and Data Layer

This specific application focuses solely on sending an SMS via an API call and does not inherently require a database.

If you needed to store message history, track delivery statuses via webhooks, manage user data, or queue messages, you would introduce a database (e.g., PostgreSQL, MongoDB) and a data access layer (e.g., using an ORM like Prisma or Sequelize). This is beyond the scope of this basic sending guide.

7. Adding Security Features

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

  • Secrets Management:

    • .env and .gitignore: As implemented, sensitive credentials are stored in .env, which is listed in .gitignore to prevent accidental commits.
    • Private Key: The private.key file itself (if using the path method) should also be kept secure and listed in .gitignore. Access permissions on the server should restrict read access to the application user only.
    • Production Environments: Use dedicated secrets management solutions provided by your cloud provider (e.g., AWS Secrets Manager, Google Secret Manager, Azure Key Vault) or environment variable injection instead of .env files. The VONAGE_PRIVATE_KEY_CONTENT variable is suitable for these scenarios.
  • Input Validation and Sanitization:

    • Implemented: Basic validation checks for the presence of to and message. Basic format check for to (needs improvement for production).
    • Enhancement: Use robust libraries (like google-libphonenumber / libphonenumber-js for phone numbers, DOMPurify if message content could be HTML, or validator.js) for stricter validation and sanitization to prevent injection attacks or malformed data reaching the Vonage API.
  • Rate Limiting:

    • Not Implemented: Protect your API endpoint from abuse or accidental loops by implementing rate limiting.
    • How: Use middleware like express-rate-limit.
    bash
    npm install express-rate-limit
    javascript
    // Add near the top of index.js
    const rateLimit = require('express-rate-limit');
    
    const limiter = rateLimit({
        windowMs: 15 * 60 * 1000, // 15 minutes
        max: 100, // Limit each IP to 100 requests per windowMs
        standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
        legacyHeaders: false, // Disable the `X-RateLimit-*` headers
        message: 'Too many requests from this IP, please try again after 15 minutes'
    });
    
    // Apply the rate limiting middleware to API routes
    // Place this before your API route definitions
    app.use('/send-sms', limiter);
  • Authentication/Authorization:

    • Not Implemented: The current endpoint is public. In a real application, you would protect it.
    • How: Implement API key checking, JWT (JSON Web Tokens), OAuth, or session-based authentication depending on your application's needs. Add middleware to verify credentials before allowing access to the /send-sms route.
  • HTTPS: Always use HTTPS in production to encrypt data in transit. Use a reverse proxy like Nginx or Caddy, or platform services (like AWS ELB, Heroku) to handle TLS termination.

  • Helmet: Use the helmet middleware for Express to set various HTTP headers that improve security (e.g., Content-Security-Policy, X-Frame-Options).

    bash
    npm install helmet
    javascript
    // Add near the top of index.js
    const helmet = require('helmet');
    app.use(helmet());

8. Handling Special Cases

  • International Phone Numbers:
    • Recommendation: Always use the E.164 format (e.g., +14155550100, +442071838750) for the to number. Vonage generally expects this format for reliable international delivery.
    • Library: Use google-libphonenumber or libphonenumber-js to parse and validate numbers from various formats and convert them to E.164. The current basic regex is insufficient.
  • SMS Character Limits & Encoding:
    • Standard SMS messages (GSM-7 encoding) are limited to 160 characters.
    • Messages with non-standard characters (like emojis or certain accented letters) use UCS-2 encoding, reducing the limit to 70 characters per segment.
    • Longer messages are split into multiple segments (concatenated SMS). Vonage handles this automatically, but each segment is billed separately. Be mindful of message length to manage costs. You might add validation to limit message length on your server.
  • Vonage Trial Account Limitations:
    • Whitelisting: If using a Vonage trial account (before adding payment details), you can typically only send SMS messages to phone numbers that you have verified and added to your "test numbers" list in the Vonage dashboard (Settings -> Test numbers). Attempts to send to other numbers will result in an error (often "Non-Whitelisted Destination").
    • Branding: Trial messages might have "[FREE SMS DEMO, TEST MESSAGE]" prepended.

9. Implementing Performance Optimizations

For this simple API, performance is unlikely to be a major issue unless handling extremely high volume.

  • Asynchronous Operations: Node.js and the Vonage SDK are inherently asynchronous, preventing the server from blocking while waiting for the API call to complete. This is the most significant performance aspect already built-in.

  • Connection Pooling: The Vonage SDK likely handles underlying HTTP connection management efficiently.

  • Payload Size: Keep request/response payloads small. We are already doing this.

  • High Volume Considerations:

    • Queuing: If you need to send thousands of messages rapidly, pushing requests onto a message queue (like RabbitMQ or Redis BullMQ) and having separate worker processes handle the actual sending via sendSms can decouple the API response from the sending process and improve API responsiveness.
    • Concurrency Limits: Be mindful of Vonage API rate limits (check their documentation). Implement application-level throttling if necessary to avoid hitting these limits.
  • Load Testing: Use tools like k6, artillery, or ApacheBench (ab) to simulate traffic and identify potential bottlenecks under load.

    bash
    # Example using ab (ApacheBench) - sends 100 requests, 10 concurrently
    # Make sure the server is running!
    # Create a file payload.json: {"to": "+1YOUR_TEST_NUMBER", "message": "Load test"}
    ab -n 100 -c 10 -p payload.json -T 'application/json' http://localhost:3000/send-sms

10. Adding Monitoring, Observability, and Analytics

Ensure you can monitor the health and performance of your service.

  • Health Checks:
    • Implemented: A basic /health endpoint returning {"status": "UP"}. Monitoring systems (like Kubernetes liveness/readiness probes, AWS health checks, or uptime checkers) can poll this endpoint.
  • Logging: (As discussed in Section 5) Centralized logging is key for observing behavior and diagnosing issues. Consider structured logging for production.
  • Metrics:
    • Track Key Indicators: Use libraries like prom-client (for Prometheus) or integrate with APM (Application Performance Management) services (Datadog, New Relic).
    • Metrics to Track:
      • Request rate and latency for /send-sms.
      • Error rates (overall and specific error types).
      • Number of successful SMS submissions (sendSms calls).
      • Number of failed SMS submissions.
      • Vonage API call latency (if measurable via SDK or wrappers).
  • Error Tracking: (As discussed in Section 5) Services like Sentry provide real-time error alerting and aggregation.
  • Vonage Dashboard: Utilize the Vonage dashboard itself for analytics on message delivery rates, costs, and usage patterns.
  • Status Webhooks: For production, implement the status_url webhook specified when creating the Vonage Application. Vonage will send POST requests to this URL with updates on message delivery status (e.g., delivered, failed, rejected). This provides much better insight than just knowing the message was submitted.

11. Troubleshooting and Caveats

Common issues and things to watch out for:

  • Authentication Errors:
    • 401 Unauthorized or similar errors during sendSms: Usually means incorrect VONAGE_APPLICATION_ID or issues with the private key (VONAGE_PRIVATE_KEY_PATH or VONAGE_PRIVATE_KEY_CONTENT). Double-check the .env file. If using the path, ensure the private.key file exists at the specified path and is readable by the Node.js process. If using content, ensure it's correctly formatted (including newlines \n if pasting multi-line keys). Verify the Application ID is correct.
  • Initialization Errors: Check the console output when starting the server (node index.js) for errors related to missing environment variables or problems reading/parsing the private key.
  • "Non-Whitelisted Destination" Error:
    • Cause: Sending to a number not on your verified test number list while using a Vonage trial account.
    • Solution: Add the recipient number to your test list in the Vonage Dashboard (Settings -> Test numbers) or upgrade your Vonage account by adding billing details.
  • Invalid from Number:
    • Ensure VONAGE_FROM_NUMBER in .env is a valid Vonage number that you own and have linked to the Vonage Application being used (see Section 4, Step 5).
  • Incorrect API Endpoint/Method:
    • Ensure you are making a POST request to /send-sms (not GET).
    • Check for typos in the URL (http://localhost:3000/send-sms).
  • Missing Content-Type Header:
    • When sending JSON data via curl or other clients, ensure you set the Content-Type: application/json header. The express.json() middleware requires this.
  • Environment Variables Not Loaded:
    • Ensure require('dotenv').config() is called at the very top of index.js.
    • Verify the .env file exists in the project root and contains the correct variables.
  • Private Key File Issues (if using path):
    • Ensure VONAGE_PRIVATE_KEY_PATH in .env correctly points to the private.key file relative to where you run node index.js.
    • Check file permissions – the Node.js process needs read access to the key file.
  • Messages API vs. SMS API Confusion:
    • This guide uses the Messages API (vonage.messages.send). If you see code examples using vonage.sms.send(...) or vonage.message.sendSms(...), those likely refer to the older SMS API, which typically uses API Key/Secret authentication (new Vonage({ apiKey, apiSecret })) instead of Application ID/Private Key. Ensure your code and SDK initialization match the Messages API approach used here.

12. Deployment and CI/CD

Deploying a Node.js/Express application:

General Steps:

  1. Choose a Platform: Heroku, AWS (EC2, Lambda, Fargate, Elastic Beanstalk), Google Cloud (App Engine, Cloud Run, GKE), Azure (App Service, AKS), DigitalOcean App Platform, Vercel, Railway, etc.

  2. Environment Variables: Configure your production environment variables (PORT, VONAGE_APPLICATION_ID, VONAGE_FROM_NUMBER, NODE_ENV=production, and either VONAGE_PRIVATE_KEY_PATH or VONAGE_PRIVATE_KEY_CONTENT) using your chosen platform's mechanism (e.g., Heroku Config Vars, AWS Parameter Store/Secrets Manager). Do not commit .env or private.key to your repository. Provide the private key securely, often via the VONAGE_PRIVATE_KEY_CONTENT variable for PaaS environments.

  3. Build Step (Optional): If using TypeScript or a build process, run the build command.

  4. Install Production Dependencies: Ensure only necessary dependencies are installed (npm install --production or yarn install --production).

  5. Start Script: Modify your package.json start script to run the application in production mode:

    json
    // package.json
    "scripts": {
      "start": "node index.js",
      "dev": "nodemon index.js"
    }
  6. Procfile (Heroku/Similar): Create a Procfile in the root:

    text
    web: node index.js

Example: Deploying to Heroku

  1. Install Heroku CLI.

  2. heroku login

  3. heroku create your-app-name

  4. Set Config Vars (replace placeholders). It's usually easiest to provide the private key content directly:

    bash
    heroku config:set NODE_ENV=production
    heroku config:set VONAGE_APPLICATION_ID=YOUR_APP_ID
    # Paste the entire content of your private.key file, preserving newlines.
    # Use single quotes around the value if your shell requires it.
    heroku config:set VONAGE_PRIVATE_KEY_CONTENT='-----BEGIN PRIVATE KEY-----\nYOUR_MULTI_LINE_KEY_CONTENT...\n-----END PRIVATE KEY-----'
    heroku config:set VONAGE_FROM_NUMBER=YOUR_VONAGE_NUMBER
    # PORT is set automatically by Heroku

    (Note: The index.js code from Section 2 already handles reading VONAGE_PRIVATE_KEY_CONTENT)

  5. Commit Procfile and your code changes (ensure .env and private.key are in .gitignore).

  6. git push heroku main (or your branch name).

  7. heroku logs --tail to monitor.

CI/CD (Example using GitHub Actions):

Create .github/workflows/deploy.yml:

yaml
# .github/workflows/deploy.yml
# Basic example for deploying to Heroku on push to main branch

name: Deploy to Heroku

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Deploy to Heroku
        uses: akhileshns/heroku-deploy@v3.12.12
        with:
          heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
          heroku_app_name: "your-heroku-app-name"
          heroku_email: ${{ secrets.HEROKU_EMAIL }}
        env:
          HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
          HEROKU_EMAIL: ${{ secrets.HEROKU_EMAIL }}
  • Secrets: You need to add HEROKU_API_KEY and HEROKU_EMAIL as secrets in your GitHub repository settings (Settings -> Secrets and variables -> Actions).
  • Vonage Credentials: Your Vonage credentials should still be configured as Config Vars directly in Heroku (as shown in the Heroku deployment steps), not stored in GitHub secrets for the deployment workflow itself unless absolutely necessary and handled with extreme care.