code examples

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

Send SMS with Vonage, Node.js & Fastify: Complete API Tutorial

Learn how to build a high-performance SMS API using Node.js, Fastify framework, and Vonage Messages API. Step-by-step guide with code examples, security best practices, and production-ready patterns.

How to Send SMS Messages with Fastify and Vonage Messages API

Learn how to build a production-ready SMS API endpoint using Node.js, the Fastify web framework, and the Vonage Messages API. This comprehensive tutorial covers project setup, Vonage configuration, API implementation, security features, error handling, and testing—everything you need to send SMS messages programmatically from your Fastify application.

By the end of this guide, you'll have a functional Fastify API endpoint (POST /api/v1/send-sms) that accepts a phone number and message payload, then uses Vonage to deliver the SMS reliably. This solution is ideal for applications requiring transactional SMS, notification systems, or two-factor authentication messaging.

Prerequisites

Before starting this SMS integration tutorial, ensure you have:

  • Node.js (LTS version recommended) and npm installed
  • A Vonage API account with API credentials
  • Basic familiarity with Node.js, asynchronous programming, and REST APIs
  • A tool for making HTTP requests (cURL, Postman, or similar)

Technology Stack Overview

This tutorial uses the following technologies for building a robust SMS API:

  • Node.js: JavaScript runtime environment for server-side development
  • Fastify: A high-performance, low-overhead web framework for Node.js, chosen for its exceptional speed, extensibility, and developer experience
  • Vonage Messages API: Enterprise-grade API for sending messages across multiple channels including SMS, providing reliable message delivery with global reach
  • @vonage/server-sdk: Official Vonage Node.js SDK for seamless API interaction
  • dotenv: Environment variable management module required by fastify-env
  • fastify-env: Fastify plugin for loading and validating environment variables securely
  • fastify-rate-limit: Built-in rate limiting plugin for API protection
  • @sinclair/typebox: TypeScript-based schema validation library for Fastify routes and environment variables

System Architecture

Understanding the SMS API flow helps you implement and troubleshoot effectively:

  1. A client application (Postman, cURL, or your frontend) sends a POST request to the Fastify API endpoint (/api/v1/send-sms)
  2. The Fastify application receives the request and validates input data (phone number format, message content)
  3. The application uses the Vonage Node.js SDK, configured with your Vonage Application ID and Private Key, to call the Vonage Messages API
  4. Vonage's infrastructure handles SMS delivery to the recipient's phone number via carrier networks
  5. The Vonage API returns a response containing the message ID for tracking
  6. The Fastify application sends a success or error response back to the client

Final Outcome: A production-ready Node.js Fastify API server with a single SMS endpoint (POST /api/v1/send-sms) that securely sends text messages using Vonage's reliable messaging infrastructure.


1. Setting up the Node.js Project

Let's initialize the project structure and install all required dependencies for the SMS API.

Create Project Directory

Open your terminal and create a new directory for the Fastify SMS project:

bash
mkdir fastify-vonage-sms
cd fastify-vonage-sms

Initialize Node.js Project

Create a package.json file to manage dependencies:

bash
npm init -y

Install Required Dependencies

Install Fastify, the Vonage SDK, and supporting packages for environment management, rate limiting, and validation:

bash
npm install fastify @vonage/server-sdk dotenv fastify-env fastify-rate-limit @sinclair/typebox

Install pino-pretty as a development dependency for readable log output during development:

bash
npm install --save-dev pino-pretty

Set up Project Structure

Create a well-organized project structure following Node.js best practices:

text
fastify-vonage-sms/
├── node_modules/
├── src/
│   ├── plugins/
│   │   └── env.js         # Environment variable configuration
│   ├── routes/
│   │   └── sms.js         # SMS sending route
│   ├── services/
│   │   └── vonage.js      # Vonage SDK interaction logic
│   └── app.js             # Fastify application setup
├── .env.example           # Example environment variables
├── .env                   # Local environment variables (DO NOT COMMIT)
├── .gitignore             # Git ignore file
├── package.json
└── server.js              # Server entry point

Directory explanation:

  • src/: Contains core application logic
  • plugins/: Fastify plugins for environment variable handling and other cross-cutting concerns
  • routes/: API endpoint definitions
  • services/: External service integrations like Vonage
  • app.js: Configures and exports the Fastify instance
  • server.js: Application entry point that starts the server
  • .env / .env.example: Environment variable management for API credentials

Create .gitignore

Prevent committing sensitive information and build artifacts by creating a .gitignore file:

text
# .gitignore
node_modules
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*

Create .env.example

Create an .env.example file as a template for required environment variables:

dotenv
# .env.example

# Server Configuration
PORT=3000
HOST=0.0.0.0

# Vonage API Credentials & Configuration
# Found in your Vonage Dashboard -> API Settings
VONAGE_API_KEY=YOUR_VONAGE_API_KEY
VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET

# Vonage Application Credentials (for Messages API)
# Create an application in Vonage Dashboard -> Applications
VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID
VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root OR the key content itself

# Vonage Number or Sender ID
# Must be a Vonage virtual number associated with your application
# or an approved Alphanumeric Sender ID
VONAGE_SMS_FROM=YOUR_VONAGE_NUMBER_OR_SENDER_ID

Create Local .env File

Duplicate .env.example to create your local configuration file:

bash
cp .env.example .env

Security Note: Never commit your .env file to version control. Add your actual Vonage credentials in the next section.


2. Integrating with Vonage Messages API

Before implementing the SMS functionality, configure your Vonage account and obtain the necessary API credentials.

Sign Up or Log In to Vonage

Ensure you have a Vonage API account. New accounts typically receive free credit for testing SMS functionality.

Set Messages API as Default

Configure Vonage to use the Messages API for SMS:

  • Navigate to your Vonage API Dashboard
  • Go to Settings
  • Under API KeysSMS Settings, set "Default SMS Setting" to Messages API
  • Click Save changes

This ensures consistency across all Vonage features and enables access to advanced messaging capabilities.

Get API Key and Secret

Retrieve your basic API credentials:

  • Find your API Key and API Secret at the top of the Vonage API Dashboard
  • Copy these credentials
  • Add them to your .env file for VONAGE_API_KEY and VONAGE_API_SECRET

Note: While the Messages API primarily uses Application ID and Private Key for authentication, the SDK may require API Key/Secret for initialization.

Create a Vonage Application

The Messages API requires a Vonage Application to manage authentication and webhook settings:

  • Navigate to Applications in the Vonage Dashboard
  • Click Create a new application
  • Enter a descriptive name (e.g., "Fastify SMS Sender")
  • Under Capabilities, enable Messages
  • For Inbound URL and Status URL, enter placeholder URLs (e.g., https://example.com/inbound, https://example.com/status) since we're only sending SMS in this tutorial
  • Click Generate public and private key – this downloads a private.key file
  • Save this file securely in your project root directory (fastify-vonage-sms/private.key)
  • Important: Do not commit this file to version control
  • Click Generate new application
  • Copy the Application ID from the application page
  • Add the Application ID to your .env file for VONAGE_APPLICATION_ID
  • Update VONAGE_PRIVATE_KEY_PATH in .env to the correct path (e.g., ./private.key)

You need a Vonage virtual number as the sender for outbound SMS:

  • On your application page, scroll to Link virtual numbers
  • Click Link next to an available Vonage number
  • If you don't have a number, purchase one via NumbersBuy numbers
  • Copy the linked number in E.164 format (e.g., 14155550100)
  • Add this to your .env file for VONAGE_SMS_FROM

Alternative: If approved for your account, use an Alphanumeric Sender ID (e.g., "MyApp").

Whitelist Test Numbers (Trial Accounts Only)

Trial Vonage accounts can only send SMS to verified numbers:

  • Navigate to NumbersTest numbers
  • Add destination phone numbers you'll use for testing
  • Verify ownership via the code sent to each number

Your .env file should now contain all required Vonage credentials for SMS integration.


3. Implementing Core SMS Functionality

Now we'll build the Fastify server, configure environment variables, create the Vonage service layer, and define the SMS API route.

Configure Environment Variables Plugin

Create src/plugins/env.js to load and validate environment variables:

javascript
// src/plugins/env.js
import fp from 'fastify-plugin';
import fastifyEnv from 'fastify-env';
import { Type } from '@sinclair/typebox'; // For schema validation

const schema = Type.Object({
    PORT: Type.Number({ default: 3000 }),
    HOST: Type.String({ default: '0.0.0.0' }),
    VONAGE_API_KEY: Type.String(),
    VONAGE_API_SECRET: Type.String(),
    VONAGE_APPLICATION_ID: Type.String(),
    VONAGE_PRIVATE_KEY_PATH: Type.String(), // Can be path or key content
    VONAGE_SMS_FROM: Type.String(),
});

async function configLoader(fastify, options) {
    await fastify.register(fastifyEnv, {
        dotenv: true, // Load .env file using dotenv
        schema: schema,
        confKey: 'config', // Access variables via fastify.config
    });
}

export default fp(configLoader);

Key features:

  • Defines validation schema using @sinclair/typebox to specify expected types
  • dotenv: true enables automatic .env file loading
  • confKey: 'config' makes variables accessible via fastify.config
  • Type validation ensures all required credentials are present before starting

Create Vonage Service Module

Create src/services/vonage.js to encapsulate Vonage SDK initialization and SMS sending logic:

javascript
// src/services/vonage.js
import { Vonage } from '@vonage/server-sdk';
import path from 'path';
import { promises as fs } from 'fs';

let vonage;

// Function to initialize Vonage SDK asynchronously
async function initializeVonage(config, log) {
    if (vonage) {
        return vonage;
    }

    try {
        let privateKeyContent;
        const keyPathOrContent = config.VONAGE_PRIVATE_KEY_PATH;

        // Check if it looks like a file path or the key content itself
        if (keyPathOrContent.startsWith('-----BEGIN PRIVATE KEY-----')) {
             log.info('Using private key content directly from environment variable.');
             privateKeyContent = keyPathOrContent;
        } else {
            log.info(`Attempting to read private key file from path: ${keyPathOrContent}`);
            // Construct the absolute path to the private key file
            const privateKeyPath = path.resolve(process.cwd(), keyPathOrContent);

            // Check if the private key file exists before reading
            try {
                await fs.access(privateKeyPath);
                // Read the private key content
                // The SDK expects the key content as a string or buffer
                privateKeyContent = await fs.readFile(privateKeyPath);
                 log.info(`Successfully read private key file from: ${privateKeyPath}`);
            } catch (err) {
                log.error({ err }, `Vonage Private Key file access error at path: ${privateKeyPath}`);
                throw new Error(`Vonage Private Key file not found or inaccessible at path specified in VONAGE_PRIVATE_KEY_PATH: ${keyPathOrContent}`);
            }
        }

        if (!privateKeyContent) {
             throw new Error('Vonage Private Key content could not be determined.');
        }

        vonage = new Vonage({
            apiKey: config.VONAGE_API_KEY,
            apiSecret: config.VONAGE_API_SECRET,
            applicationId: config.VONAGE_APPLICATION_ID,
            privateKey: privateKeyContent, // Pass the key content directly
        });
        log.info('Vonage SDK initialized successfully.');
        return vonage;
    } catch (error) {
        log.error('Failed to initialize Vonage SDK:', error);
        // Throwing error to prevent app from starting with invalid config
        throw new Error(`Vonage SDK initialization failed: ${error.message}`);
    }
}

// Function to send SMS using the Messages API
async function sendSms(to, text, config, log) {
    const vonageInstance = await initializeVonage(config, log); // Ensure SDK is initialized

    try {
        const resp = await vonageInstance.messages.send({
            message_type: 'text',
            to: to,
            from: config.VONAGE_SMS_FROM,
            channel: 'sms',
            text: text,
        });
        log.info(`SMS sent successfully to ${to}. Message UUID: ${resp.message_uuid}`);
        return resp; // Contains message_uuid
    } catch (err) {
        // Log detailed error information from Vonage if available
        const errorDetails = err?.response?.data || { message: err.message, detail: err.detail };
        log.error({
             message: `Error sending SMS to ${to}`,
             vonageError: errorDetails,
             statusCode: err?.response?.status
        }, `Vonage API Error: ${errorDetails.title || errorDetails.message || 'Unknown error'}`);

        // Rethrow a structured error for the route handler
        throw new Error(`Vonage API Error: ${errorDetails.title || errorDetails.detail || errorDetails.message || 'Unknown error sending SMS'}`);
    }
}

export { initializeVonage, sendSms };

Implementation highlights:

  • initializeVonage: Handles both file-based and inline private key content, with proper error handling
  • sendSms: Calls vonage.messages.send with required parameters for SMS channel
  • Enhanced error logging captures detailed Vonage API error responses
  • Uses path.resolve and process.cwd() for reliable file path handling

Create SMS API Route

Create src/routes/sms.js to define the SMS sending endpoint:

javascript
// src/routes/sms.js
import { Type } from '@sinclair/typebox';
import { sendSms as sendVonageSms } from '../services/vonage.js';

// Schema for validating the request body
const sendSmsBodySchema = Type.Object({
    to: Type.String({
        description: 'Recipient phone number in E.164 format (e.g., +14155550100)',
        // Basic E.164 pattern check (starts with +, followed by 1-15 digits)
        pattern: '^\\+[1-9]\\d{1,14}$'
    }),
    message: Type.String({
         description: 'The text message content',
         minLength: 1,
         maxLength: 1600 // Standard SMS limit consideration
    }),
}, {
    additionalProperties: false // Disallow properties not defined in the schema
});

// Schema for the success response
const sendSmsResponseSchema = Type.Object({
    success: Type.Boolean(),
    message_uuid: Type.String(),
    message: Type.String(),
});

// Optional: Schema for error responses
const errorResponseSchema = Type.Object({
    success: Type.Boolean({ default: false }),
    message: Type.String()
});


async function smsRoutes(fastify, options) {
    // Expose config and logger from fastify instance
    const { config, log } = fastify;

    fastify.post('/send-sms', {
        schema: {
            description: 'Sends an SMS message via Vonage Messages API',
            tags: ['SMS'],
            summary: 'Send an SMS',
            body: sendSmsBodySchema,
            response: {
                200: sendSmsResponseSchema,
                // Define potential error responses for documentation/validation
                400: errorResponseSchema, // Validation errors, potentially some Vonage errors
                500: errorResponseSchema  // Server/Vonage errors
            }
        }
    }, async (request, reply) => {
        const { to, message } = request.body;
        log.info(`Received request to send SMS to: ${to}`);

        try {
            // Call the Vonage service function
            const result = await sendVonageSms(to, message, config, log);

            reply.code(200).send({
                success: true,
                message_uuid: result.message_uuid,
                message: `SMS submitted successfully to ${to}`,
            });

        } catch (error) {
             // Log the error with context
             log.error({ err: error }, `Failed to send SMS to ${to}: ${error.message}`);

             // Determine appropriate status code based on error type if possible
             // Example: Check for specific Vonage error messages
             if (error.message && error.message.includes("Non-Whitelisted")) {
                reply.code(400).send({ success: false, message: 'Destination number not whitelisted for trial account.' });
             } else if (error.message && error.message.includes("Authentication Failed")) {
                 reply.code(401).send({ success: false, message: 'Vonage authentication failed. Check credentials.' });
             } else {
                 // Default to 500 for other errors
                 reply.code(500).send({
                     success: false,
                     message: error.message || 'An internal server error occurred while sending the SMS.',
                 });
             }
        }
    });

     // Basic health check endpoint
     fastify.get('/health', {
          schema: {
              description: 'Checks the health of the service',
              tags: ['Health'],
              summary: 'Health Check',
              response: {
                  200: Type.Object({
                      status: Type.String(),
                      timestamp: Type.String({ format: 'date-time' })
                  })
              }
          }
      }, async (request, reply) => {
         return { status: 'ok', timestamp: new Date().toISOString() };
     });
}

export default smsRoutes;

Route features:

  • Strict E.164 phone number validation using regex pattern
  • Message length validation (1-1600 characters)
  • additionalProperties: false prevents unexpected fields
  • Intelligent error handling with appropriate HTTP status codes
  • Health check endpoint for monitoring

Set up Fastify Application

Create src/app.js to build the Fastify instance and register plugins:

javascript
// src/app.js
import Fastify from 'fastify';
import configLoader from './plugins/env.js';
import smsRoutes from './routes/sms.js';
import { initializeVonage } from './services/vonage.js';
import fastifyRateLimit from 'fastify-rate-limit';

async function buildApp() {
    const fastify = Fastify({
        logger: {
            level: process.env.LOG_LEVEL || 'info', // Use env var or default
            // Use pino-pretty only if not in production for better performance
            transport: process.env.NODE_ENV !== 'production' ? {
                target: 'pino-pretty',
                options: {
                    translateTime: 'HH:MM:ss Z',
                    ignore: 'pid,hostname',
                },
            } : undefined, // Use default JSON output in production
        },
    });

    // Register environment variable plugin FIRST
    await fastify.register(configLoader);

    // Initialize Vonage SDK after config is loaded
    // Ensures config is available and handles potential async init errors on startup
    try {
         await initializeVonage(fastify.config, fastify.log);
    } catch (err) {
         // Use console.error as logger might not be fully ready if init fails early
         console.error("Critical error during Vonage SDK initialization. Exiting.", err);
         process.exit(1); // Exit if critical setup fails
    }

    // Register basic rate limiting
    // Consider making max/timeWindow configurable via env vars
    await fastify.register(fastifyRateLimit, {
        max: 100, // Max requests per window per IP
        timeWindow: '1 minute', // Time window
        // keyGenerator: function (req) { /* more sophisticated key */ return req.ip }
    });

    // Register routes with a prefix
    await fastify.register(smsRoutes, { prefix: '/api/v1' });

    fastify.log.info('Application setup complete. Routes registered under /api/v1');
    return fastify;
}

export default buildApp;

Application configuration:

  • Conditional logging with pino-pretty for development, JSON for production
  • Validates Vonage SDK initialization before starting server
  • Implements rate limiting (100 requests per minute per IP)
  • API versioning with /api/v1 prefix

Create Server Entry Point

Create server.js to start the Fastify application:

javascript
// server.js
import buildApp from './src/app.js';

async function start() {
    let fastify;
    try {
        fastify = await buildApp();

        // Use config loaded by the app
        const port = fastify.config.PORT;
        const host = fastify.config.HOST;

        await fastify.listen({ port, host });
        // Logger is available after listen resolves successfully
        fastify.log.info(`Server listening on http://${host}:${port}`);
        fastify.log.info(`SMS Send Endpoint: POST http://${host}:${port}/api/v1/send-sms`);
        fastify.log.info(`Health Check Endpoint: GET http://${host}:${port}/api/v1/health`);

    } catch (err) {
        // Use console.error for reliable logging during startup failures
        console.error('Error starting server:', err);
        // Attempt to log with Fastify logger if available, otherwise console is fallback
        if (fastify && fastify.log) {
             fastify.log.error(err);
        }
        process.exit(1);
    }
}

start();

Startup features:

  • Robust error handling for server initialization failures
  • Clear logging of available endpoints
  • Graceful failure with proper exit codes

Add NPM Scripts

Update the scripts section in your package.json:

json
{
  "scripts": {
    "start": "node server.js",
    "dev": "NODE_ENV=development node server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
  • npm start: Production mode with JSON logging
  • npm run dev: Development mode with pretty-printed logs

4. Testing Your SMS API

Now let's verify the SMS endpoint works correctly.

Verify Environment Configuration

Double-check that your .env file contains the correct Vonage credentials. If using a trial account, ensure the target phone number is added to your Test Numbers list in Vonage.

Start the Development Server

Launch the server in development mode:

bash
npm run dev

Expected output:

INFO: Server listening on http://0.0.0.0:3000 INFO: SMS Send Endpoint: POST http://0.0.0.0:3000/api/v1/send-sms INFO: Health Check Endpoint: GET http://0.0.0.0:3000/api/v1/health

Send a Test SMS Request

Open a new terminal window and send a test request using cURL. Replace +1YOUR_TEST_PHONE_NUMBER with a verified test number in E.164 format:

bash
curl -X POST http://localhost:3000/api/v1/send-sms \
-H "Content-Type: application/json" \
-d '{
  "to": "+1YOUR_TEST_PHONE_NUMBER",
  "message": "Hello from Fastify and Vonage! Sent at: '"$(date)"'"
}'

Verify the Response

Success response (HTTP 200):

json
{
  "success": true,
  "message_uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "message": "SMS submitted successfully to +1YOUR_TEST_PHONE_NUMBER"
}

The SMS should arrive on the target phone within seconds. Check your server terminal for confirmation logs.

Error response example (HTTP 400):

json
{
  "success": false,
  "message": "Destination number not whitelisted for trial account."
}

Check server logs for detailed error information if issues occur.

Test Input Validation

Verify that validation works correctly by testing invalid requests:

Missing required field:

bash
curl -X POST http://localhost:3000/api/v1/send-sms -H "Content-Type: application/json" -d '{"message": "test"}'

Expected: 400 Bad Request with validation error about missing to property.

Invalid phone format:

bash
curl -X POST http://localhost:3000/api/v1/send-sms -H "Content-Type: application/json" -d '{"to": "12345abc", "message": "test"}'

Expected: 400 Bad Request with pattern validation error.

Extra invalid property:

bash
curl -X POST http://localhost:3000/api/v1/send-sms -H "Content-Type: application/json" -d '{"to": "+14155550100", "message": "test", "extraField": 123}'

Expected: 400 Bad Request with additional properties error.

Test Health Endpoint

Verify the health check endpoint:

bash
curl http://localhost:3000/api/v1/health

Expected response (HTTP 200):

json
{"status":"ok","timestamp":"2025-04-21T10:30:45.123Z"}

5. Error Handling and Logging Best Practices

Proper error handling and logging are essential for maintaining a production SMS API.

Logging Implementation

The application uses Fastify's built-in Pino logger for structured logging:

  • Development mode (npm run dev): Uses pino-pretty for human-readable output
  • Production mode (npm start): Outputs JSON format for log management systems
  • Logs include request information, successful sends, and detailed error context

Monitor logs in the terminal where the server is running to track all SMS operations.

Error Handling Strategy

Validation Errors:

  • Fastify automatically handles request schema validation
  • Returns detailed 400 Bad Request responses with specific validation failures

Vonage API Errors:

  • The sendSms service catches SDK errors and logs detailed information
  • Route handler attempts to return appropriate HTTP status codes:
    • 400 for non-whitelisted numbers or invalid parameters
    • 401 for authentication failures
    • 500 for other server/API errors

Initialization Errors:

  • Errors during Vonage SDK initialization cause the application to exit
  • Prevents running in a broken state with invalid credentials
  • Critical errors logged to console for visibility

Retry Mechanisms

For simple API-triggered SMS sending, retries are typically handled by the calling client. For production systems with asynchronous workflows (job queues, background processing), consider:

  • Implementing retries with exponential backoff for transient errors
  • Handling specific error types (429 Too Many Requests, temporary 5xx errors)
  • Using libraries like async-retry within the sendSms function
  • Integrating with job queue systems for automatic retry logic

6. Security Features and Best Practices

Security is critical when building SMS APIs that handle user data and API credentials.

Input Validation

Implemented via Fastify's schema validation using @sinclair/typebox:

  • Prevents malformed requests from reaching application logic
  • Enforces E.164 phone number format
  • Validates message content length
  • Rejects unexpected fields with additionalProperties: false

Rate Limiting

Basic IP-based rate limiting using fastify-rate-limit:

  • Mitigates DoS attacks and accidental abuse
  • Default: 100 requests per minute per IP address
  • Configure max and timeWindow based on expected usage
  • Consider per-API-key rate limiting for authenticated APIs
  • Integrate with API gateways for advanced protection in production

Secret Management

API credentials are managed securely via environment variables:

  • Development: .env file (excluded from Git via .gitignore)
  • Production: Use platform-specific secret management:
    • AWS Secrets Manager
    • Google Secret Manager
    • HashiCorp Vault
    • Azure Key Vault
    • Doppler or similar services
  • Never hardcode credentials in source code

Private Key Handling

The service supports flexible private key management:

  • File-based (development): Read from path specified in VONAGE_PRIVATE_KEY_PATH
  • Inline content (production): Inject key directly into environment variable
  • Ensure file permissions restrict access if using file-based approach
  • Never commit private keys to version control

Additional Security Recommendations

Security Headers:

Install and configure @fastify/helmet:

bash
npm install @fastify/helmet
javascript
import helmet from '@fastify/helmet';
await fastify.register(helmet);

Authentication:

Protect endpoints using API keys, JWT tokens, or OAuth with @fastify/auth:

bash
npm install @fastify/auth

Dependency Security:

Regularly audit and update dependencies:

bash
npm audit
npm audit fix

Frequently Asked Questions

How do I send SMS with Fastify and Vonage?

Install the @vonage/server-sdk package, configure your Vonage Application ID and private key, then create a Fastify route that calls vonage.messages.send() with the recipient number, message content, and SMS channel configuration.

What phone number format does Vonage require?

Vonage requires E.164 format for all phone numbers: a plus sign (+) followed by the country code and number without spaces or special characters. Examples: +12125551234 (US), +442071838750 (UK).

How much does it cost to send SMS with Vonage?

Vonage SMS pricing varies by destination country and message type. US SMS typically starts around $0.0076 per message segment. Check current pricing at vonage.com/communications-apis/sms/pricing.

Can I use Vonage with a trial account?

Yes, Vonage offers free trial credit for new accounts. Trial accounts can only send SMS to verified numbers added to your Test Numbers list in the Vonage Dashboard under Numbers → Test numbers.

How do I handle SMS delivery failures?

Implement error handling in your route handler to catch Vonage API errors. Log detailed error information for debugging. For production systems, consider implementing retry logic with exponential backoff for transient failures.

What is the SMS character limit for Vonage?

Standard SMS supports up to 160 characters (GSM-7 encoding) or 70 characters (Unicode). Longer messages are automatically segmented, with each segment billed separately. The maximum total length is 1,600 characters.

How do I secure my Vonage API credentials?

Store credentials in environment variables using .env files for development. In production, use secure secret management services like AWS Secrets Manager, Google Secret Manager, or HashiCorp Vault. Never commit credentials to version control.

Can I send MMS with this setup?

Yes, the Vonage Messages API supports MMS. Modify the request to include image, audio, or video URLs in the message object. Check Vonage documentation for MMS-specific parameters and supported countries.

How do I monitor SMS delivery status?

Configure a Status URL webhook in your Vonage Application to receive delivery receipts. Vonage sends HTTP POST requests with delivery status updates (delivered, failed, etc.) to your webhook endpoint.

What Node.js version is required?

Use Node.js LTS version 18 or later for optimal compatibility with Fastify v5 and the latest @vonage/server-sdk features, including modern async/await patterns and security updates.


Learn more about building SMS applications with Node.js and Fastify:


Next Steps: Now that you have a working SMS API, consider exploring advanced features like two-way messaging, delivery status webhooks, or bulk SMS campaigns. Check out our related tutorials for implementing these features with Fastify and Vonage.

Frequently Asked Questions

How to send SMS with Node.js and Fastify?

Use the Vonage Messages API and Fastify framework. Set up a Fastify server, integrate the Vonage Node.js SDK, and create a POST route to handle SMS sending requests. This setup allows your Node.js application to send SMS messages programmatically.

What is the Vonage Messages API used for?

The Vonage Messages API is a service for sending messages through various channels like SMS. It's used to reliably deliver transactional and notification messages within applications. The API handles the complexities of message delivery, letting developers focus on application logic.

Why use Fastify for SMS sending?

Fastify is a high-performance Node.js web framework known for speed and extensibility. Its efficient design minimizes overhead, making it ideal for handling API requests like those for sending SMS messages.

When should I use the Vonage Node.js SDK?

The Vonage Node.js SDK simplifies interactions with Vonage APIs. It handles authentication, request formatting, and response parsing. Use it whenever your Node.js application needs to communicate with Vonage services like the Messages API for SMS sending.

How to set up Vonage Messages API credentials?

Obtain API Key and Secret from your Vonage dashboard, create a Vonage Application, generate and secure a private key, and link a Vonage virtual number or an Alphanumeric Sender ID. Update all these environment variables in your project's .env file.

What is the role of fastify-env?

Fastify-env loads and validates environment variables, ensuring your application has the necessary configuration values. It helps manage sensitive data like API keys and secrets securely. Use it in combination with the dotenv package for loading environment variables from a .env file during development.

How to handle Vonage API errors?

Implement error handling within your Vonage service function to catch and log errors from the Vonage SDK. The route handler should also catch and re-throw errors with appropriate HTTP status codes (e.g., 400, 401, 500).

What security measures are implemented in the Fastify SMS sender?

The application includes schema validation with @sinclair/typebox to sanitize input, basic IP rate limiting using fastify-rate-limit to prevent abuse, and environment variable management for secure credential storage. These mechanisms mitigate common security risks and improve application robustness.

Can I use an Alphanumeric Sender ID with Vonage?

Yes, if it's approved for your account. Instead of a phone number, you can configure an Alphanumeric Sender ID (e.g., 'MyApp') as the 'from' address for your SMS messages in your .env file under VONAGE_SMS_FROM

How to structure a Fastify project for sending SMS?

Organize your code into directories for plugins (env.js), routes (sms.js), and services (vonage.js). This modular structure promotes clean separation of concerns and maintainability. Create app.js for Fastify setup and server.js for launching the application.

What are the prerequisites for this Node.js SMS project?

Ensure you have Node.js (LTS recommended), npm, a Vonage API account, and a basic understanding of Node.js, asynchronous programming, and REST APIs. A tool for making HTTP requests (like cURL or Postman) is essential for testing.

How does the SMS sending process work architecturally?

A client sends a POST request to the Fastify API endpoint. The server validates the request and uses the Vonage Node.js SDK to call the Vonage Messages API. Vonage delivers the SMS, returns a response to the server, which then sends a final response to the client.

How to test the Fastify SMS API endpoint?

Populate the .env file with your credentials and test numbers, then run npm run dev. Send requests using tools like cURL or Postman to the /api/v1/send-sms endpoint with valid parameters. Use the provided cURL examples in the tutorial to test the API.

Where do I find Vonage API credentials?

You can find your API Key, Secret, and Application settings in your Vonage API Dashboard. Navigate to the settings section to manage these settings. Ensure that 'Default SMS Setting' is set to 'Messages API'.