code examples

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

Send SMS Messages with Node.js and Vonage

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

This guide provides a step-by-step walkthrough for building a simple Node.js application using the Express framework to send SMS messages via the Vonage SMS API. We'll cover everything from project setup to basic deployment considerations.

By the end of this tutorial, you'll have a functional API endpoint that accepts a phone number and message, sends the SMS using Vonage, and returns a confirmation. While this guide provides a solid foundation, remember that additional considerations around error handling, security, and scalability are necessary for true production deployment.

Technologies Used:

  • Node.js: A JavaScript runtime environment for building server-side applications.
  • Express: A minimal and flexible Node.js web application framework used to create the API endpoint.
  • Vonage Server SDK for Node.js: The official library for interacting with Vonage APIs.
  • dotenv: A module to load environment variables from a .env file, keeping sensitive credentials secure.
  • Vonage API: The communication platform service used for sending SMS messages.

Project Overview and Goals

Goal: To create a simple, reliable REST API endpoint that allows an application to send SMS messages programmatically using Vonage.

Problem Solved: This provides a basic building block for applications needing SMS notification capabilities, such as sending verification codes, alerts, or simple messages to users.

System Architecture:

(Note: An image depicting the client-server-Vonage interaction would be beneficial here.)

The basic flow is: Client (e.g., Web App) sends an HTTP POST request with phone number and message to the Node.js/Express API Server. The server uses the Vonage Node.js SDK to call the Vonage API. The Vonage API handles sending the SMS to the Recipient's Mobile Phone. The server returns a JSON response to the client indicating success or failure.

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 credit for testing.
  • A Text Editor or IDE: Such as VS Code, Sublime Text, or WebStorm.
  • Terminal/Command Prompt: For running commands.
  • A Test Phone Number: A phone number you can send SMS messages to for verification. If using a trial Vonage account, this number must be registered as a test number (see Step 3).
  • (Optional) API Client: Like Postman or curl 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 Node.js Project: This command creates a package.json file to manage project details and dependencies. The -y flag accepts the default settings.

    bash
    npm init -y
  3. Install Dependencies: We need Express for the web server, the Vonage SDK to interact with the API, and dotenv to manage environment variables. Install the latest versions compatible with your Node.js environment.

    bash
    npm install express @vonage/server-sdk dotenv
  4. Enable ES Modules and Configure package.json: To use modern import syntax, open your package.json file. Add ""type"": ""module"" and ensure you have a start script. Your package.json should look similar to this (versions might differ):

    json
    {
      ""name"": ""vonage-sms-sender"",
      ""version"": ""1.0.0"",
      ""description"": """",
      ""main"": ""index.js"",
      ""scripts"": {
        ""start"": ""node index.js"",
        ""test"": ""echo \""Error: no test specified\"" && exit 1""
      },
      ""keywords"": [],
      ""author"": """",
      ""license"": ""ISC"",
      ""dependencies"": {
        ""@vonage/server-sdk"": ""^3.14.1"",
        ""dotenv"": ""^16.4.5"",
        ""express"": ""^4.19.2""
      },
      ""type"": ""module""
    }

    Note: The dependency versions listed above are examples. npm install will fetch the latest compatible versions based on the ^ prefix or the current latest at the time of installation. Always check for updates and compatibility. Why type: ""module""? This tells Node.js to treat .js files as ES modules, enabling the use of import/export syntax instead of the older require/module.exports.

  5. Create Project Files: We'll need three core files:

    • index.js: The main entry point for our Express application.
    • lib.js: A module to encapsulate the Vonage SMS sending logic.
    • .env: To store sensitive API credentials securely.
    bash
    touch index.js lib.js .env
  6. Configure Git Ignore: Create a .gitignore file to prevent committing sensitive information like the .env file and node_modules directory.

    bash
    touch .gitignore

    Add the following lines to .gitignore:

    text
    # .gitignore
    
    # Dependencies
    node_modules
    
    # Environment variables
    .env
    
    # Logs
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    
    # Optional Editor directories
    .vscode
    .idea

Your project structure should now look like this:

vonage-sms-sender/ ├── .env ├── .gitignore ├── index.js ├── lib.js ├── node_modules/ ├── package-lock.json └── package.json

2. Configuring Vonage Credentials

Before writing code, you need your Vonage API Key, API Secret, and a Vonage phone number (or registered test number).

  1. Log in to Vonage Dashboard: Access your Vonage API Dashboard.

  2. Find API Key and Secret: On the main dashboard page (or under ""API settings""), you'll find your API key and API secret. Copy these values.

  3. Get a Vonage Number:

    • Navigate to ""Numbers"" > ""Buy numbers"" to purchase a virtual number with SMS capabilities suitable for your region.
    • Trial Account Limitation: If you are on a free trial account, you cannot purchase numbers initially. Instead, you must register the phone number(s) you intend to send messages to. Navigate to ""Account"" > ""Test numbers"". Add your personal phone number here and verify it using the code Vonage sends. You can only send messages to these registered test numbers until you upgrade your account.
  4. Set Environment Variables: Open the .env file you created and add your credentials and configuration. Replace the placeholder values with your actual data.

    dotenv
    # .env
    
    # Server configuration
    PORT=3000
    
    # Vonage API Credentials
    VONAGE_API_KEY=YOUR_API_KEY
    VONAGE_API_SECRET=YOUR_API_SECRET
    
    # Vonage Number or Sender ID
    # Use your purchased Vonage virtual number (e.g., 14155550100)
    # OR a registered Sender ID (e.g., ""MyBrand"") if supported and configured
    # OR leave it as a recognisable name like ""MyApp"" if using test numbers (sender ID might be overridden)
    VONAGE_VIRTUAL_NUMBER=YOUR_VONAGE_NUMBER_OR_SENDER_ID
    • PORT: The port your Express server will listen on.
    • VONAGE_API_KEY: Your API key from the Vonage dashboard.
    • VONAGE_API_SECRET: Your API secret from the Vonage dashboard.
    • VONAGE_VIRTUAL_NUMBER: The number or sender ID that will appear as the sender of the SMS. Use your purchased Vonage number. If using only test numbers on a trial account, this might be overridden by Vonage, but setting a value like your app name (""MyApp"") is still good practice. Ensure this value is not the test recipient number.

    Security: The .env file contains sensitive credentials. Never commit this file to version control (like Git). The .gitignore entry ensures this.

3. Implementing Core Functionality

Now, let's write the code to handle sending SMS messages.

a) SMS Sending Logic (lib.js)

This module initializes the Vonage client and exports a function to send SMS messages using modern async/await with the SDK's promise-based methods.

javascript
// lib.js
import { Vonage } from '@vonage/server-sdk';
import 'dotenv/config'; // Load environment variables from .env

// Initialize Vonage client
const vonage = new Vonage({
  apiKey: process.env.VONAGE_API_KEY,
  apiSecret: process.env.VONAGE_API_SECRET,
});

// Retrieve the sender number/ID from environment variables
const sender = process.env.VONAGE_VIRTUAL_NUMBER;

/**
 * Sends an SMS message using the Vonage API.
 * @param {string} recipient - The phone number to send the SMS to (E.164 format recommended, e.g., +14155550101).
 * @param {string} message - The text content of the SMS message.
 * @returns {Promise<object>} A promise that resolves with the Vonage API response data on success.
 * @throws {Error} Throws an error with details if sending fails.
 */
export const sendSms = async (recipient, message) => {
  console.log(`Attempting to send SMS from ${sender} to ${recipient}`);

  try {
    const responseData = await vonage.sms.send({ to: recipient, from: sender, text: message });

    // Check the status of the first message in the response
    // Vonage API sends back an array of messages status
    if (responseData.messages[0]['status'] === ""0"") {
      console.log(""SMS submitted successfully:"", responseData.messages[0]);
      return responseData; // Resolve with the full response data
    } else {
      // Handle Vonage API errors (e.g., invalid number, insufficient funds)
      const errorCode = responseData.messages[0]['status'];
      const errorText = responseData.messages[0]['error-text'];
      console.error(`SMS failed with status ${errorCode}: ${errorText}`);
      // Throw an error to be caught by the calling function
      throw new Error(`Message failed - Status ${errorCode}: ${errorText}`);
    }
  } catch (err) {
    // Handle network errors or SDK initialization errors or errors thrown above
    console.error(""Error sending SMS:"", err);
    // Re-throw the error or throw a more specific error
    throw new Error(`Vonage Error: ${err.message || 'Unknown error during SMS sending'}`);
  }
};
  • Why dotenv/config? Importing this at the top ensures that process.env is populated with values from your .env file before the Vonage client is initialized or the sender variable is read.
  • Async/Await: We declare sendSms as async and use await directly on vonage.sms.send(), as the modern Vonage SDK returns Promises. This simplifies the code compared to manual Promise wrapping.
  • Error Handling: The try...catch block handles both network/SDK errors and specific Vonage API errors (where status is not ""0""). Errors are thrown to be handled by the calling route handler in index.js. Logging provides visibility.

b) API Layer (index.js)

This file sets up the Express server and defines the API endpoint /send.

javascript
// index.js
import express from 'express';
import 'dotenv/config'; // Load environment variables
import { sendSms } from './lib.js'; // Import our SMS sending function

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 }));

/**
 * @route POST /send
 * @desc Sends an SMS message via Vonage
 * @access Public (consider adding authentication in production)
 * @body { ""phone"": ""string"", ""message"": ""string"" } - Phone number (E.164 format) and message text.
 */
app.post('/send', async (req, res) => {
  const { phone, message } = req.body;

  // Basic Input Validation
  if (!phone || !message) {
    console.warn('Validation Error: Missing phone or message in request body');
    return res.status(400).json({
      success: false,
      message: 'Missing required fields: ""phone"" and ""message"".',
    });
  }

  // Add more robust phone number validation if needed (e.g., regex for E.164)

  try {
    console.log(`Received request to send SMS to ${phone}`);
    const result = await sendSms(phone, message); // Use await with the async function
    console.log('SMS send operation successful:', result);

    // You might want to simplify the response for the client
    res.status(200).json({
      success: true,
      message: 'SMS submitted successfully.',
      details: {
        messageId: result.messages[0]['message-id'],
        to: result.messages[0]['to'],
        remainingBalance: result.messages[0]['remaining-balance'],
        messagePrice: result.messages[0]['message-price'],
      }
    });
  } catch (error) {
    console.error('Error in /send endpoint:', error);
    // Send back the error message captured from the thrown error in sendSms
    res.status(500).json({
      success: false,
      message: 'Failed to send SMS.',
      error: error.message || 'An unknown error occurred.',
    });
  }
});

// Basic root route for health check or info
app.get('/', (req, res) => {
  // Display the current date dynamically
  res.send(`Vonage SMS Sender API running. Use POST /send to send messages. Current date: ${new Date().toDateString()}`);
});

// Start the server
app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`);
  console.log(`API Endpoint: http://localhost:${PORT}/send`);
});
  • Middleware: express.json() is essential for parsing incoming JSON request bodies. express.urlencoded() is for parsing form data.
  • Route Handler: The async keyword enables await. We call sendSms and await its resolution.
  • Input Validation: A basic check ensures phone and message are present. Production applications should have more robust validation (e.g., checking phone number format using libraries like libphonenumber-js).
  • Error Handling: The try...catch block now catches errors thrown by the sendSms function, providing a consistent way to handle failures and respond to the client.
  • Response Structure: Clear success and error responses are sent back to the client in JSON format, including relevant details or error messages.

4. Testing the API Endpoint

Now, let's run the server and test the /send endpoint.

  1. Start the Server: Open your terminal in the project directory and run:

    bash
    npm start

    Or directly:

    bash
    node index.js

    You should see the output:

    Server listening on port 3000 API Endpoint: http://localhost:3000/send
  2. Send a Test Request: Use curl in another terminal window or an API client like Postman. Replace YOUR_TEST_PHONE_NUMBER with the number you registered in your Vonage dashboard (including the country code, e.g., +12125551234).

    bash
    curl -X POST http://localhost:3000/send \
         -H ""Content-Type: application/json"" \
         -d '{
               ""phone"": ""YOUR_TEST_PHONE_NUMBER"",
               ""message"": ""Hello from Node.js and Vonage!""
             }'
  3. Check the Response:

    • Successful Response (HTTP 200):

      json
      {
        ""success"": true,
        ""message"": ""SMS submitted successfully."",
        ""details"": {
          ""messageId"": ""some-message-id-string"",
          ""to"": ""YOUR_TEST_PHONE_NUMBER_WITHOUT_PLUS"",
          ""remainingBalance"": ""1.87650000"",
          ""messagePrice"": ""0.03330000""
        }
      }

      You should also receive the SMS on your test phone shortly. Check the server console logs for detailed output from lib.js.

    • Validation Error Response (HTTP 400): If you forget phone or message:

      json
      {
        ""success"": false,
        ""message"": ""Missing required fields: \""phone\"" and \""message\"".""
      }
    • Vonage API Error Response (HTTP 500): If you use a non-whitelisted number on a trial account, provide invalid credentials, or have insufficient funds:

      json
      {
        ""success"": false,
        ""message"": ""Failed to send SMS."",
        ""error"": ""Message failed - Status 6: The message failed because the number was not whitelisted""
      }

      Check the server console logs for the specific Vonage error status and text captured by the catch block.

5. Error Handling and Logging

  • Current Implementation: We have improved error handling using async/await with try...catch in both the route handler and lib.js. Errors are thrown and caught, providing clearer error propagation. Errors are logged to the console using console.log, console.warn, and console.error.
  • Production Considerations:
    • Structured Logging: Use a dedicated logging library like Pino or Winston for structured JSON logs, different log levels (info, warn, error, debug), and outputting logs to files or log management services.
    • Centralized Logging: Send logs to services like Datadog, Logz.io, or ELK stack for easier searching and analysis.
    • Detailed Error Tracking: Integrate with error tracking services like Sentry or Bugsnag to capture and aggregate application errors.
    • Retry Mechanisms: For transient network issues or temporary Vonage API unavailability, implement a retry strategy with exponential backoff (e.g., using libraries like async-retry). This wasn't included here for simplicity but is crucial for robust applications.

6. Security Considerations

  • API Key Security: Keep your .env file secure and never commit it. Use environment variables in your deployment environment.
  • Input Validation: Sanitize and validate all inputs rigorously. Ensure the phone number is in a valid format (e.g., using libphonenumber-js). Check message length and content if necessary. Use libraries like express-validator for more complex validation rules.
  • Rate Limiting: Protect your API endpoint from abuse by implementing rate limiting (e.g., using express-rate-limit) to restrict the number of requests from a single IP address or user within a specific time window.
  • Authentication/Authorization: The current endpoint is public. In a real application, protect it with authentication (e.g., API keys passed in headers, JWT tokens) to ensure only authorized clients can send SMS messages.
  • HTTPS: Always run your application behind HTTPS in production to encrypt traffic between the client and your server.

7. Deployment

  • Local Execution: npm start or node index.js is suitable for development.
  • Production Process Management: Use a process manager like PM2 to keep your Node.js application running reliably, manage logs, enable clustering for performance, and handle automatic restarts.
    bash
    npm install pm2 -g # Install globally
    pm2 start index.js --name vonage-sms-api # Start the app
    pm2 list # List running apps
    pm2 logs vonage-sms-api # View logs
    pm2 stop vonage-sms-api # Stop the app
  • Platform as a Service (PaaS): Platforms like Heroku, Render, Google App Engine, or AWS Elastic Beanstalk simplify deployment. You typically push your code via Git, and the platform handles building, deploying, and scaling. Remember to configure your environment variables (VONAGE_API_KEY, etc.) through the platform's dashboard or CLI.
  • Containers (Docker): Package your application into a Docker container for consistent deployments across different environments. You can then deploy this container to services like AWS ECS, Kubernetes (EKS, GKE, AKS), or Docker Swarm. A Dockerfile would be needed.
  • CI/CD: Implement a Continuous Integration/Continuous Deployment pipeline (using tools like GitHub Actions, GitLab CI, Jenkins) to automate testing and deployment whenever changes are pushed to your repository.

8. Troubleshooting and Caveats

  • Non-Whitelisted Destination (Error Status 6): This is the most common error for trial accounts. Ensure the phone number you are sending to is registered and verified under ""Account"" > ""Test numbers"" in the Vonage Dashboard.
  • Invalid Credentials (Error Status 4): Double-check your VONAGE_API_KEY and VONAGE_API_SECRET in your .env file and ensure they are correctly loaded (check for typos, ensure dotenv/config is imported early).
  • Invalid Sender Address (from) (Error Status 15): The VONAGE_VIRTUAL_NUMBER in your .env file might be incorrect, not owned by your account, or not SMS-capable. Ensure it's a valid Vonage number associated with your account or an approved Sender ID.
  • .env Not Loading: Make sure import 'dotenv/config'; is executed before process.env variables are accessed (typically the first import in lib.js and index.js). Ensure the .env file is in the root directory where you run node index.js.
  • Missing Dependencies: If you get errors like Cannot find module 'express', ensure you ran npm install. Check package.json and node_modules.
  • Incorrect Content-Type: When testing with curl or Postman, ensure the Content-Type header is set to application/json.
  • Rate Limits: Vonage applies rate limits to API requests. If you send too many messages too quickly, you might receive errors (check the specific error code in the response). Implement delays or queues if sending bulk messages.

9. Verification Checklist

  • Project initialized (npm init -y).
  • Dependencies installed (express, @vonage/server-sdk, dotenv).
  • ""type"": ""module"" added to package.json.
  • .env file created with PORT, VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_VIRTUAL_NUMBER.
  • .gitignore created and includes node_modules and .env.
  • Vonage API Key and Secret are correct in .env.
  • VONAGE_VIRTUAL_NUMBER is correctly set in .env.
  • If using a trial account, the recipient phone number is added and verified in Vonage Test Numbers.
  • lib.js created, initializes Vonage SDK, and exports sendSms async function using SDK promises.
  • index.js created, sets up Express, imports sendSms, defines POST /send route.
  • /send route includes basic validation for phone and message.
  • /send route uses try...catch and handles errors thrown from sendSms.
  • Server starts successfully (npm start or node index.js).
  • Test request to /send using curl or Postman returns a successful (200) response.
  • SMS message is received on the test phone number.
  • Console logs show appropriate messages for request handling and SMS status/errors.

Next Steps

You now have a basic but functional API for sending SMS messages. To enhance this project, consider:

  • Adding Authentication: Secure your API endpoint (e.g., API Key auth, JWT).
  • Implementing Robust Input Validation: Use libraries like express-validator or joi for thorough validation and sanitization of phone numbers and messages.
  • Integrating a Structured Logging Service: For better monitoring and analysis in production (Pino, Winston).
  • Adding Unit and Integration Tests: Use frameworks like Jest or Mocha to ensure code quality and prevent regressions.
  • Implementing Retry Logic: For handling transient network or API errors gracefully.
  • Exploring Inbound SMS: Use Vonage Webhooks to receive incoming SMS messages in your application.
  • Using the Vonage Messages API: For sending messages via other channels like WhatsApp or Facebook Messenger, and for more advanced features like scheduled sending or delivery receipts via webhook.
  • Building a Frontend: Create a simple web interface to interact with your API.
  • Containerizing the Application: Create a Dockerfile for easier deployment.

Remember to consult the official Vonage Server SDK for Node.js documentation and the Vonage API documentation for more advanced features and details.

Frequently Asked Questions

How to send SMS messages with Node.js?

Use the Vonage SMS API and the Vonage Server SDK for Node.js. This allows you to create a server-side application that can send text messages programmatically. The provided tutorial walks through setting up a simple Express.js server and sending messages via a dedicated API endpoint.

What is Vonage SMS API used for?

The Vonage SMS API enables sending text messages from your applications. It's useful for various purposes, including sending verification codes, alerts, notifications, and simple messages to users. This tutorial shows how to integrate it with a Node.js application.

Why does my Vonage SMS fail to send?

Several reasons can cause SMS failures, such as invalid API credentials, an incorrect 'from' number, insufficient funds in your account, or the recipient's number not being whitelisted if you're using a trial account. Refer to the troubleshooting section of the tutorial for solutions.

When should I use dotenv in Node.js?

Use dotenv during development to store environment variables like API keys securely outside your code. This is essential for keeping sensitive information safe and managing configuration between different environments. Import `dotenv/config` early in your files to load the variables.

How to set up a Vonage SMS project in Node?

Start by creating a new directory, initializing a Node.js project with `npm init -y`, installing necessary packages (`express`, `@vonage/server-sdk`, `dotenv`), and creating core project files (`index.js`, `lib.js`, `.env`). The article provides detailed steps and example code.

What is the Vonage virtual number?

This is the number or sender ID that appears as the sender of your SMS messages. Obtain this number from your Vonage dashboard after purchasing or, for trial accounts, configure a registered test number. It's crucial for identification and routing.

How to handle Vonage API errors in Node?

Implement `try...catch` blocks around the `sendSms` function to gracefully handle network issues and errors returned by the Vonage API. Log the error details to the console or a logging service for debugging. Consider retry mechanisms for transient issues.

How to secure my Vonage SMS API endpoint?

Implement robust input validation, rate limiting, and proper authentication to prevent unauthorized access and protect against misuse. Never expose API keys directly in your code; always use environment variables.

What is Express.js used for in Vonage SMS?

Express.js creates the web server and API endpoint for sending SMS messages. It handles routing incoming requests and sending responses to the client. It's a lightweight and flexible framework ideal for this purpose.

How to test Vonage SMS integration locally?

After setting up the server and endpoint, use `curl` or an API client like Postman to send test POST requests to the `/send` endpoint. Include the recipient's phone number and the message in the request body as JSON data.

When should I add authentication to my SMS API?

Authentication is crucial for production environments. You should add authentication (e.g., API keys, JWT) as soon as you're ready to deploy to protect your API endpoint and control access to prevent misuse and unauthorized sending of SMS messages.

Can I send bulk SMS messages with Vonage and Node.js?

The example provided sends single messages. For bulk sending, you would need to modify the code to loop through recipients and manage responses. Be mindful of rate limiting by Vonage and consider implementing queues or delays.

How do I deploy my Node.js Vonage SMS application?

You can deploy using process managers like PM2, platforms like Heroku or AWS Elastic Beanstalk, or via containerization with Docker. Choose the method best suited to your needs and infrastructure.

How to fix 'Non-Whitelisted Destination' error with Vonage?

If using a trial account, make sure you've added and verified the recipient's phone number under 'Test numbers' in the Vonage dashboard. You can only send messages to verified test numbers on trial accounts.