code examples

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

Developer Guide: Node.js & Express Two-Way SMS with Vonage Messages API

A comprehensive guide to building a Node.js/Express application for sending and receiving SMS messages using the Vonage Messages API, covering setup, webhooks, security, and deployment.

This guide provides a comprehensive walkthrough for building a Node.js application using the Express framework to both send outbound SMS messages and receive inbound SMS messages via webhooks, leveraging the Vonage Messages API.

We will cover everything from initial project setup and Vonage configuration to implementing core messaging logic, handling webhooks, ensuring security, and preparing for deployment. By the end, you'll have a functional foundation for two-way SMS communication.

Project Goal: To create a simple Node.js server that can:

  1. Send an SMS message to a specified phone number via the Vonage Messages API.
  2. Receive incoming SMS messages sent to a Vonage virtual number via a webhook endpoint.

Technology Stack:

  • Node.js: JavaScript runtime environment.
  • Express: Minimalist web framework for Node.js, used to create the webhook endpoint.
  • Vonage Messages API: Vonage's unified API for sending and receiving messages across various channels (we'll focus on SMS).
  • @vonage/server-sdk: The official Vonage Node.js SDK for interacting with the API.
  • ngrok: A tool to expose local servers to the internet for webhook testing during development.
  • dotenv: Module to load environment variables from a .env file.

System Architecture:

+-------------+ +-----------------+ +-----------------+ +-------------+ | Your Phone |<---->| Vonage Platform |<---->| Your Node.js App| | Developer | | (End User) | | (Messages API) | | (Express Server)|<---->| | +-------------+ +-----------------+ +-----------------+ +-------------+ ^ | ^ | SMS | Webhook Call | API Call | v | +---------------------+------------------------+ (Send/Receive Flow)

Prerequisites:

  • Node.js and npm (or yarn): Installed on your development machine. (Download Node.js)
  • Vonage API Account: Sign up for free at Vonage. Note your API Key and Secret from the dashboard.
  • Vonage Virtual Number: Purchase an SMS-capable number through the Vonage dashboard (Numbers > Buy numbers).
  • ngrok: Installed and authenticated. (Download ngrok). A free account is sufficient.
  • Basic understanding of Node.js, Express, and REST APIs.

1. Project Setup

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-app
    cd vonage-sms-app
  2. Initialize Node.js Project: This creates a package.json file.

    bash
    npm init -y
  3. Install Dependencies: We need the Vonage SDK, the Express framework, and dotenv for managing environment variables.

    bash
    npm install @vonage/server-sdk express dotenv
  4. Create Project Structure: Create the necessary files and directories.

    bash
    touch server.js send-sms.js .env .gitignore
    • server.js: Will contain our Express server code for handling inbound webhooks.
    • send-sms.js: A simple script to demonstrate sending an outbound SMS.
    • .env: Stores sensitive credentials like API keys (will be ignored by Git).
    • .gitignore: Specifies files/directories Git should ignore (like .env and node_modules).
  5. Configure .gitignore: Add the following lines to your .gitignore file to prevent committing sensitive information and dependencies:

    text
    # Dependencies
    node_modules
    
    # Environment variables
    .env
    
    # Vonage Private Key
    private.key
    
    # OS generated files
    .DS_Store
    Thumbs.db

2. Vonage Account and Application Setup

Before writing code, we need to configure Vonage correctly.

  1. API Credentials: Locate your API Key and API Secret on the main page of your Vonage API Dashboard. While the Messages API primarily uses the Application ID and Private Key for authentication in this guide, having the Key/Secret handy is useful.

  2. Choose API for SMS: Vonage offers two APIs for SMS (SMS API and Messages API). The Messages API is more modern and versatile. Ensure it's set as the default for your account:

    • Go to your Vonage API Dashboard.
    • Navigate to API Settings in the left-hand menu.
    • Scroll down to SMS Settings.
    • Under ""Default SMS Setting"", select Messages API.
    • Click Save changes.
  3. Create a Vonage Application: Applications act as containers for your communication configurations, including webhook URLs and authentication methods.

    • Navigate to Applications in the dashboard (Applications > Create a new application).
    • Enter an Application name (e.g., ""Node Express SMS App"").
    • Click Generate public and private key. Crucially, save the private.key file that downloads. We recommend initially saving it in your project's root directory (vonage-sms-app/private.key). Remember, this file is sensitive and should not be committed to Git (it's already in .gitignore).
    • Security Note: For production environments, avoid storing the key file directly within the project. Best practices include storing it securely outside the project directory and referencing its path via an environment variable, or even better, loading the content of the key directly from an environment variable (see Deployment section).
    • Enable the Messages capability.
    • You'll see fields for Inbound URL and Status URL. We'll fill these in later once we have our ngrok tunnel running. For now, you can enter temporary placeholders like https://example.com/webhooks/inbound and https://example.com/webhooks/status.
    • Click Generate new application.
    • Note the Application ID that is generated – you will need this.
  4. Link Your Vonage Number: Associate your purchased Vonage virtual number with the application you just created.

    • Navigate to Numbers > Your numbers.
    • Find the virtual number you want to use for receiving SMS.
    • Click the Manage button (or the pencil icon) next to the number.
    • In the Applications dropdown under Forward to, select the application you created (""Node Express SMS App"").
    • Click Save. Now, any SMS sent to this number will trigger events directed to the webhook URLs defined in your application.

3. Environment Variables

Store your sensitive credentials and configuration in the .env file. Never hardcode credentials directly in your source code.

Populate your .env file with the following, replacing the placeholder values:

dotenv
# .env

# Vonage Application Credentials (from Application setup)
VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID
VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root

# Vonage Virtual Number (The one linked to your application, E.164 format recommended)
VONAGE_NUMBER=+14155550100 # Example: Use your actual number with country code

# Test Recipient Number (Your mobile number, for sending tests, E.164 format recommended)
YOUR_PHONE_NUMBER=+14155550199 # Example: Use your actual number with country code

# Optional: Vonage API Key/Secret (Needed if using other APIs or different auth)
# VONAGE_API_KEY=YOUR_API_KEY
# VONAGE_API_SECRET=YOUR_API_SECRET
  • Replace YOUR_APPLICATION_ID with the actual value from your Vonage application settings.
  • Ensure VONAGE_PRIVATE_KEY_PATH points to the correct location of your downloaded private.key file (relative to where you run the node scripts).
  • Replace +14155550100 with your Vonage number in E.164 format (including the + and country code).
  • Replace +14155550199 with your personal mobile number, also in E.164 format, for testing.
  • The VONAGE_API_KEY and VONAGE_API_SECRET are commented out as they are not strictly required for the Messages API when using Application ID/Private Key authentication as shown in this guide, but you might uncomment and fill them if needed for other purposes.

4. Implementing SMS Sending (send-sms.js)

This script demonstrates how to send an outbound SMS using the Vonage SDK with Application ID and Private Key authentication.

javascript
// send-sms.js

require('dotenv').config(); // Load environment variables from .env file
const { Vonage } = require('@vonage/server-sdk');
const { SMS } = require('@vonage/messages');

// --- Configuration ---
const vonageAppId = process.env.VONAGE_APPLICATION_ID;
const vonagePrivateKeyPath = process.env.VONAGE_PRIVATE_KEY_PATH;
const vonageNumber = process.env.VONAGE_NUMBER;
const recipientNumber = process.env.YOUR_PHONE_NUMBER; // Your mobile number
const messageText = `Hello from Vonage and Node.js! (Sent: ${new Date().toLocaleTimeString()})`;

// Basic validation
if (!vonageAppId || !vonagePrivateKeyPath || !vonageNumber || !recipientNumber) {
  console.error(""Error: Missing required environment variables (VONAGE_APPLICATION_ID, VONAGE_PRIVATE_KEY_PATH, VONAGE_NUMBER, YOUR_PHONE_NUMBER). Check your .env file."");
  process.exit(1); // Exit if configuration is missing
}

// --- Initialize Vonage Client ---
// Using Application ID and Private Key for authentication with Messages API
const vonage = new Vonage({
    applicationId: vonageAppId,
    privateKey: vonagePrivateKeyPath,
});


// --- Send SMS Function ---
async function sendSms() {
    console.log(`Attempting to send SMS from ${vonageNumber} to ${recipientNumber}`);
    try {
        const resp = await vonage.messages.send(
            new SMS( // Using the specific SMS class for clarity
                {
                    to: recipientNumber,
                    from: vonageNumber,
                    text: messageText,
                }
            )
        );

        console.log('Message sent successfully!');
        console.log('Message UUID:', resp.messageUuid); // Unique identifier for the message

    } catch (err) {
        console.error('Error sending SMS:');
        if (err.response) {
            // API error response
            console.error('Status:', err.response.status);
            console.error('Data:', err.response.data);
        } else {
            // Network or other errors
            console.error(err);
        }
        process.exitCode = 1; // Indicate failure
    }
}

// --- Execute Sending ---
sendSms();

Explanation:

  1. require('dotenv').config(): Loads the variables from your .env file into process.env.
  2. Import Vonage and SMS: We import the main SDK class and the specific SMS class from the @vonage/messages sub-module.
  3. Configuration: Reads the necessary Application ID, private key path, and phone numbers from process.env. Includes basic validation.
  4. Initialize Vonage: Creates an instance of the Vonage client using only the applicationId and privateKey for authentication, which is standard for the Messages API.
  5. sendSms Function:
    • Uses an async function to work with the promise-based SDK methods.
    • Calls vonage.messages.send(), passing an instance of the SMS class.
    • The SMS constructor takes an object with to, from, and text properties.
    • Uses a try...catch block for robust error handling.
    • Logs the messageUuid for tracking.
  6. Execute: Calls the sendSms() function.

To Test Sending: Run the script from your terminal:

bash
node send-sms.js

You should see output indicating success or failure, and shortly after, receive the SMS on the phone number specified in YOUR_PHONE_NUMBER.


5. Implementing SMS Receiving (Webhook Server - server.js)

This Express server listens for incoming POST requests from Vonage when an SMS is sent to your virtual number.

javascript
// server.js

require('dotenv').config();
const express = require('express');
const { json, urlencoded } = express; // Import body parsing middleware

const app = express();
const port = process.env.PORT || 3000; // Use environment variable for port or default to 3000

// --- Middleware ---
// IMPORTANT: Vonage signature verification middleware should be added here for production
// See Section 8: Security Considerations
app.use(json()); // Parse incoming JSON requests (Vonage webhooks use JSON)
app.use(urlencoded({ extended: true })); // Parse URL-encoded requests

// --- Webhook Endpoint for Inbound SMS ---
// Vonage sends POST requests to this URL when an SMS is received
app.post('/webhooks/inbound', (req, res) => {
    console.log('--- Inbound SMS Received ---');
    console.log('Timestamp:', new Date().toISOString());
    console.log('Request Body:', JSON.stringify(req.body, null, 2)); // Pretty print the JSON body

    // Extract key information (Verify these fields against current Vonage Messages API docs for inbound SMS)
    const { msisdn, to, text, messageId, 'message-timestamp': messageTimestamp } = req.body;

    if (msisdn && to && text) {
        console.log(`From: ${msisdn}`); // The sender's phone number
        console.log(`To: ${to}`);       // Your Vonage virtual number
        console.log(`Text: ${text}`);   // The message content
        console.log(`Message ID: ${messageId}`);
        console.log(`Message Timestamp: ${messageTimestamp}`);

        // --- Add your application logic here ---
        // Example: Log to database, trigger another action, send an auto-reply, etc.
        // Be mindful of potential infinite loops if auto-replying!
        // ------------------------------------------

    } else {
        console.warn('Received incomplete or unexpected webhook payload.');
    }

    // --- IMPORTANT: Respond to Vonage ---
    // Vonage expects a 200 OK response to acknowledge receipt of the webhook.
    res.status(200).send('OK');
});

// --- Webhook Endpoint for Delivery Receipts / Status Updates ---
// Vonage sends POST requests here regarding the status of outbound messages
app.post('/webhooks/status', (req, res) => {
    console.log('--- Message Status Update ---');
    console.log('Timestamp:', new Date().toISOString());
    console.log('Request Body:', JSON.stringify(req.body, null, 2));

    // Process status information (Verify fields against current Vonage Messages API docs for status webhooks)
    // Example fields might include:
    // const { message_uuid, status, timestamp, to, from, error } = req.body;
    // console.log(`Message UUID: ${message_uuid}, Status: ${status}, Timestamp: ${timestamp}`);
    // Add logic here to update message status in your database, etc.

    res.status(200).send('OK'); // Acknowledge receipt
});

// --- Basic Health Check Endpoint ---
app.get('/health', (req, res) => {
    res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});


// --- Start Server ---
app.listen(port, () => {
    console.log(`Server listening for webhooks at http://localhost:${port}`);
    console.log(`Inbound SMS webhook expected at POST /webhooks/inbound`);
    console.log(`Status webhook expected at POST /webhooks/status`);
});

Explanation:

  1. Imports & Middleware: Sets up Express and body parsing. A comment highlights where security middleware should go.
  2. /webhooks/inbound Endpoint (POST):
    • Logs the received req.body. Note: The specific fields (msisdn, to, text, etc.) should be verified against the current Vonage Messages API documentation, as they can occasionally change. The code includes common fields as an example.
    • Sends back 200 OK to acknowledge receipt.
    • Includes a placeholder for custom application logic.
  3. /webhooks/status Endpoint (POST):
    • Logs status updates for outbound messages. Note: The fields in the commented-out example (message_uuid, status, timestamp) should also be verified against current Vonage documentation for status webhooks.
    • Sends back 200 OK.
  4. /health Endpoint (GET): Basic health check.
  5. app.listen: Starts the server.

6. Local Development with ngrok

Use ngrok to expose your local server to the internet for Vonage webhooks.

  1. Start Your Server:

    bash
    node server.js
  2. Start ngrok: In a separate terminal:

    bash
    ngrok http 3000
  3. Get ngrok URL: Copy the HTTPS Forwarding URL provided by ngrok (e.g., https://xxxxxxxx.ngrok.io).

  4. Update Vonage Application Webhooks:

    • Go to your Vonage Application settings (Applications).
    • Edit your application.
    • Update the Messages capability URLs:
      • Inbound URL: YOUR_NGROK_HTTPS_URL/webhooks/inbound
      • Status URL: YOUR_NGROK_HTTPS_URL/webhooks/status
    • Save changes.

7. Verification and Testing

  1. Ensure server.js and ngrok are running.
  2. Test Inbound: Send an SMS from your mobile to your Vonage number. Check the server.js console for logs.
  3. Test Outbound & Status: Run node send-sms.js. Receive the SMS. Check the server.js console for both the initial send attempt logs (from send-sms.js) and the subsequent status update logs (from the webhook hitting server.js).

8. Security Considerations

  • Webhook Signature Verification (Highly Recommended for Production): Vonage signs webhook requests using JWT (JSON Web Tokens) based on your application's private key or a shared secret. Verifying this signature ensures authenticity and integrity.
    • The @vonage/server-sdk can help. You typically use middleware to check the Authorization header (for JWT/private key method) or a custom header (for shared secret method).
    • Conceptual Example (JWT/Private Key Method Middleware):
      javascript
      const { Vonage } = require('@vonage/server-sdk');
      // const { Auth } = require('@vonage/auth'); // Assuming Auth class handles verification
      
      // In your server setup, before webhook routes:
      app.use('/webhooks', (req, res, next) => {
        try {
          // Pseudo-code: Actual implementation depends on SDK version/helpers
          // You might need to initialize Vonage with credentials here or have it accessible
          // const vonage = new Vonage({ /* ... credentials ... */ });
          // const valid = vonage.auth.verifyWebhookSignature(req.headers, req.body); // Or similar SDK function
      
          // Placeholder for actual verification logic based on SDK docs
          const signatureHeader = req.headers['authorization']; // Example: Check Bearer token
          const isSignatureValid = (signatureHeader /* && verifyLogic(signatureHeader, req.body) */) ? true : false; // Replace with real verification
      
          if (isSignatureValid) {
            console.log('Webhook signature verified successfully (Conceptual).');
            next(); // Signature is valid, proceed to the route handler
          } else {
            console.warn('Invalid webhook signature (Conceptual).');
            res.status(401).send('Invalid signature'); // Reject unauthorized request
          }
        } catch (error) {
          console.error('Error verifying webhook signature:', error);
          res.status(500).send('Signature verification error');
        }
      });
      
      // Your app.post('/webhooks/inbound', ...) and app.post('/webhooks/status', ...) routes follow
    • Consult the official Vonage documentation on securing webhooks and the @vonage/server-sdk documentation for the exact implementation details and required setup (like providing the public key or secret).
  • Environment Variables: Never commit .env files or private keys. Use .gitignore. Use secure environment variable injection in production.
  • Rate Limiting: Use middleware like express-rate-limit to protect webhook endpoints.
  • Input Validation: Sanitize and validate message content (req.body.text) if used elsewhere.
  • HTTPS: Always use HTTPS for production webhook URLs.

9. Error Handling and Logging

  • Webhook Handler Errors: Wrap internal processing logic in try...catch. Log errors but always send 200 OK to Vonage to prevent retries. Handle errors asynchronously if needed.
  • Robust Logging: Use libraries like winston or pino for structured, leveled logging in production.
  • Outbound Send Errors: Enhance error handling in send-sms.js (e.g., retries).
  • Monitoring: Use error tracking services (Sentry, Bugsnag).

10. Database Integration (Optional Next Step)

  1. Choose Database & ORM/Driver: (e.g., PostgreSQL + Prisma, MongoDB + Mongoose).
  2. Install Dependencies: npm install prisma @prisma/client pg (example).
  3. Define Schema: Model SmsMessage (direction, IDs, numbers, body, status, timestamps).
  4. Run Migrations: Apply schema (npx prisma migrate dev).
  5. Integrate: Use ORM client in webhook handlers to save/update messages and in send-sms.js to log outbound messages.
javascript
// Example snippet for server.js using Prisma
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

// Inside app.post('/webhooks/inbound', ...) after logging:
try {
    await prisma.smsMessage.create({
        data: {
            direction: 'inbound',
            vonageMsgId: messageId,
            fromNumber: msisdn,
            toNumber: to,
            body: text,
            status: 'received',
            // receivedAt is set by default in Prisma schema usually
        }
    });
    console.log('Inbound message saved to database.');
} catch (dbError) {
    console.error('Database error saving inbound message:', dbError);
    // Still send 200 OK to Vonage, but handle the DB error (e.g., log, alert)
}
// Similar logic in /webhooks/status to update message status based on message_uuid

11. Troubleshooting and Caveats

  • ngrok Issues: Check firewalls, ngrok status/interface, ensure server.js runs on the correct port.
  • Webhooks Not Received: Verify Vonage App URLs match current ngrok HTTPS URL exactly. Confirm number linkage. Check Vonage Dashboard API Logs.
  • Authentication Errors (Sending): Double-check VONAGE_APPLICATION_ID and VONAGE_PRIVATE_KEY_PATH in .env. Ensure the key file exists and is readable. Check API error response data.
  • Incorrect API Selected: Ensure consistency between Vonage account settings (Messages API default) and code usage.
  • Missing 200 OK Response: Ensure webhook handlers always return 200 OK promptly, even if internal processing fails (log the error).
  • Number Formatting: Always use the full E.164 format for phone numbers, including the leading + and country code (e.g., +14155552671), both in your .env file and when sending messages. While the SDK might sometimes correctly interpret numbers without the +, relying on this can lead to errors. Using the full E.164 format consistently is the recommended best practice.
  • Carrier Filtering/Blocking: Delivery issues despite "submitted" status might be carrier-related. Contact Vonage support. Ensure compliance (e.g., 10DLC in US).

12. Deployment and CI/CD

  1. Choose Platform: Heroku, Vercel, AWS, Google Cloud, etc.
  2. Environment Variables: Configure VONAGE_APPLICATION_ID, VONAGE_PRIVATE_KEY_PATH (or key content), VONAGE_NUMBER, etc., securely in the platform's settings. Do not deploy .env.
  3. Private Key Handling in Env Vars: Since private keys are multi-line, directly pasting them into standard environment variables can be problematic. Common solutions:
    • Base64 Encode: Encode the key file content into a single Base64 string. Store this string in an environment variable (e.g., VONAGE_PRIVATE_KEY_BASE64). In your code, decode it before passing it to the Vonage SDK.
      bash
      # Example encoding (Linux/macOS)
      cat private.key | base64 > private.key.b64
      # Copy content of private.key.b64 into your env var
      javascript
      // In your code
      const privateKeyContent = Buffer.from(process.env.VONAGE_PRIVATE_KEY_BASE64, 'base64').toString('utf-8');
      const vonage = new Vonage({
        applicationId: vonageAppId,
        privateKey: privateKeyContent, // Pass the decoded content
      });
    • Platform Secrets Management: Use built-in secrets management tools (e.g., AWS Secrets Manager, Google Secret Manager, Heroku Config Vars, Vercel Environment Variables supporting multi-line).
    • File Path (Less Ideal for Serverless): If deploying to a VM/container, you might securely place the key file on the filesystem and use VONAGE_PRIVATE_KEY_PATH pointing to its location.
  4. Update Webhook URLs: Change Vonage Application URLs to your permanent production HTTPS URL (e.g., https://your-app.your-domain.com/webhooks/inbound).
  5. PORT Variable: Ensure server.js uses process.env.PORT.
  6. package.json Scripts: Add a start script:
    json
    {
      ""scripts"": {
        ""start"": ""node server.js"",
        ""send"": ""node send-sms.js""
      }
    }
  7. Deployment Commands: Use platform CLI/UI (e.g., git push heroku main, Vercel deploy hooks). Set environment variables securely.
  8. CI/CD: Implement automated testing and deployment pipelines (GitHub Actions, GitLab CI, etc.).

13. Conclusion

You have built a Node.js application for two-way SMS using Express and the Vonage Messages API. You've covered setup, sending, receiving via webhooks, local testing, and key considerations for security, error handling, and deployment.

Next Steps:

  • Implement robust webhook signature verification.
  • Add database persistence.
  • Build specific application logic.
  • Set up proper logging, monitoring, and error tracking.
  • Deploy to production.

This guide provides a solid foundation for integrating SMS communication into your Node.js projects.

Frequently Asked Questions

How to send SMS messages with Node.js and Vonage?

Use the Vonage Messages API with the Node.js SDK. Initialize the Vonage client with your API credentials, then use `vonage.messages.send()` with the recipient's number, your Vonage virtual number, and the message text. Ensure your Vonage number is linked to your application in the dashboard.

What is the Vonage Messages API?

Vonage's unified API for sending and receiving messages across SMS and other channels. It allows developers to easily integrate messaging functionality into their applications. This API uses Application ID and Private Key for authentication with the Node.js SDK as demonstrated in the article.

Why does Vonage use webhooks for inbound SMS?

Webhooks provide a real-time mechanism for Vonage to push incoming SMS messages to your application. Your server exposes an endpoint, and Vonage sends a POST request to this URL whenever a message arrives at your Vonage virtual number.

When should I verify webhook signatures?

Webhook signature verification is crucial for production environments. It prevents unauthorized requests. Use the `@vonage/server-sdk` middleware for this. During development with `ngrok`, it can often be omitted for initial testing.

Can I use a different port for my local server?

Yes, you can change the port. Modify the `port` variable in `server.js`. When using `ngrok`, ensure the port number matches what `ngrok` is forwarding.

How to receive inbound SMS with Node.js and Express?

Create a webhook endpoint (e.g., `/webhooks/inbound`) in your Express app. Vonage will send POST requests to this endpoint containing the SMS data. Always respond with a 200 OK status, even if errors occur during processing.

What is ngrok used for in this tutorial?

ngrok creates a public, secure tunnel to your locally running Express server, making it accessible from the internet. This is essential for receiving webhooks from Vonage during development since Vonage needs to reach your server.

How to set up Vonage application for two-way SMS?

Create a new Vonage application in your dashboard. Generate and save your private key, then enable the Messages capability. Link your Vonage virtual number to this application and set up webhook URLs. You'll need the application ID for authentication.

What is the purpose of the .env file?

The `.env` file stores sensitive information such as API keys, secrets, and phone numbers. It should be excluded from version control using `.gitignore` for security.

How to handle Vonage webhook errors in Node.js?

Wrap your webhook handling logic in `try...catch` blocks. Log any errors, but always respond to Vonage with `res.status(200).send('OK')` to prevent retries. Implement error handling asynchronously.

What are best practices for storing Vonage private keys in deployment?

Never commit the key directly. Options include base64 encoding the key and storing it in an environment variable, using platform-specific secrets management, or (less ideal) storing the key file securely outside the project directory.

Where can I find my Vonage API Key and Secret?

These credentials are available on the main page of your Vonage API Dashboard. While this guide uses Application ID and Private Key authentication with the Messages API, it's helpful to know where your Key/Secret are located.

Why should I use E.164 formatting for phone numbers?

E.164 is the international standard and recommended best practice, ensuring reliable delivery. It includes the `+` and country code (e.g., +1 for USA). Always use E.164 consistently in `.env` and when sending messages.

How to integrate a database with the SMS application?

Choose a database (e.g., PostgreSQL, MongoDB) and appropriate driver/ORM. Define a schema to model SMS messages, then use the ORM in webhook handlers to save/update messages and in send-sms.js to log outbound messages.