code examples
code examples
Build Two-Way SMS: Node.js, Express & Vonage Messages API Guide
A step-by-step guide to building a Node.js/Express application for sending and receiving SMS messages using the Vonage Messages API, covering setup, implementation, webhooks, and best practices.
Build Two-Way SMS: Node.js, Express & Vonage Messages API Guide
This guide provides a step-by-step walkthrough for building a Node.js application using the Express framework to handle both sending and receiving SMS messages via the Vonage Messages API. We'll cover everything from project setup and core implementation to security considerations and deployment.
By the end of this tutorial, you will have a functional application capable of:
- Sending SMS messages programmatically using the Vonage Node.js SDK.
- Receiving inbound SMS messages sent to your Vonage virtual number via webhooks.
This enables use cases like notifications, alerts, basic customer interactions, or integrating SMS into existing workflows.
Project Overview and Goals
What We're Building: A simple Node.js server using Express that exposes a webhook to receive incoming SMS messages from Vonage and includes a script to send outgoing SMS messages.
Problem Solved: This provides a foundational structure for integrating two-way SMS communication into applications without needing complex infrastructure. It demonstrates the core mechanics of interacting with a powerful communications API.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express.js: A minimal and flexible Node.js web application framework for handling HTTP requests and routing.
- Vonage Messages API: A unified API for sending and receiving messages across various channels, including SMS. We'll use the
@vonage/server-sdkNode.js library. ngrok: A tool to expose local development servers to the internet, essential for testing webhooks.dotenv: A module to load environment variables from a.envfile intoprocess.env.
System Architecture:
graph LR
A[Your Application (Node.js/Express)] <--> B(Vonage Messages API);
B <--> C(SMS Network);
C <--> D[End User's Phone];
subgraph Send SMS
A -- Send Request --> B;
end
subgraph Receive SMS
D -- Sends SMS --> C;
C -- Delivers to Vonage Number --> B;
B -- POST Request (Webhook) --> A;
end
subgraph Local Development
E[ngrok Tunnel] <--> A;
B -- Webhook via ngrok --> E;
endPrerequisites:
- A Vonage API account (Sign up here).
- Node.js and npm (or yarn) installed locally. Check with
node -vandnpm -v. ngrokinstalled globally or available in your PATH (Download here).- A text editor (like VS Code).
- Basic understanding of JavaScript and Node.js concepts.
- A Vonage virtual phone number capable of sending/receiving SMS.
Final Outcome: A local Node.js application that can successfully send an SMS message when a script is run and log incoming SMS messages received on your Vonage number to the console. We will also discuss production considerations like security and deployment.
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
bashmkdir vonage-sms-app cd vonage-sms-app -
Initialize npm: Create a
package.jsonfile to manage project dependencies and scripts.bashnpm init -yThe
-yflag accepts the default settings. -
Install Dependencies: We need
expressfor the web server,@vonage/server-sdkto interact with the Vonage API, anddotenvto manage our credentials securely.bashnpm install express @vonage/server-sdk dotenv -
Create Core Files: Create the main files for our application logic and environment variables.
bashtouch server.js send-sms.js .env .gitignoreserver.js: Will contain the Express server code for receiving messages.send-sms.js: Will contain the script for sending messages..env: Will store our secret API keys and configuration (DO NOT commit this file)..gitignore: Specifies files and directories that Git should ignore.
-
Configure
.gitignore: Add essential patterns to your.gitignorefile to prevent committing sensitive information and unnecessary files.text# Dependencies node_modules/ # Environment variables .env # Vonage Private Key (if stored directly) private.key # Logs npm-debug.log* yarn-debug.log* yarn-error.log* # OS generated files .DS_Store Thumbs.db -
Set up Environment Variables (
.env): Open the.envfile and add the following placeholders. We will populate these values in the ""Integrating with Vonage"" section.dotenv# Vonage API Credentials (Found in your Vonage Dashboard) VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Details (Generated when creating a Vonage Application) VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key # Path to your downloaded private key file # Phone Numbers VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Your purchased Vonage number (E.164 format, e.g., 12015550123) MY_TEST_NUMBER=YOUR_PERSONAL_PHONE_NUMBER # Your mobile number for testing (E.164 format) # Server Configuration PORT=3000 # Optional: Vonage Webhook Signature Secret (See Section 7) # VONAGE_SIGNATURE_SECRET=YOUR_SIGNATURE_SECRETWhy
.env? Using environment variables keeps sensitive credentials out of your codebase, making it more secure and easier to manage different configurations for development, staging, and production. Thedotenvlibrary loads these intoprocess.envwhen the application starts.
Our basic project structure is now ready.
2. Implementing Core Functionality
We'll split the core logic into two parts: sending SMS and receiving SMS.
Sending SMS (send-sms.js)
This script will initialize the Vonage client using Application ID and Private Key authentication (recommended for server-side applications) and send a single SMS message.
-
Edit
send-sms.js: Add the following code:javascript// send-sms.js require('dotenv').config(); // Load environment variables from .env file const { Vonage } = require('@vonage/server-sdk'); // --- Configuration --- const vonageNumber = process.env.VONAGE_NUMBER; const recipientNumber = process.env.MY_TEST_NUMBER; // Your personal number for testing const messageText = ""Hello from Vonage and Node.js!""; // --- Input Validation --- if (!process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH) { console.error(""Error: Vonage Application ID or Private Key Path not set in .env file.""); process.exit(1); } if (!vonageNumber || !recipientNumber) { console.error(""Error: VONAGE_NUMBER or MY_TEST_NUMBER not set in .env file.""); process.exit(1); } // --- Initialize Vonage Client --- // Authentication uses Application ID and Private Key const vonage = new Vonage({ applicationId: process.env.VONAGE_APPLICATION_ID, privateKey: process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH, }, { debug: false }); // Set debug: true for verbose SDK logging // --- Send SMS Function --- async function sendSms() { console.log(`Attempting to send SMS from ${vonageNumber} to ${recipientNumber}...`); try { const resp = await vonage.messages.send({ message_type: ""text"", text: messageText, to: recipientNumber, from: vonageNumber, // Must be your Vonage virtual number channel: ""sms"" }); console.log(""SMS Sent Successfully!""); console.log(""Message UUID:"", resp.message_uuid); // Unique ID for the message } catch (err) { console.error(""Error sending SMS:""); // Log the detailed error response if available if (err.response && err.response.data) { console.error(JSON.stringify(err.response.data, null, 2)); } else { console.error(err); } process.exitCode = 1; // Indicate failure } } // --- Execute --- sendSms();Explanation:
require('dotenv').config(): Loads variables from.env.new Vonage({...}): Initializes the SDK client. We use Application ID and Private Key for authentication, which is more secure for backend services than API Key/Secret.vonage.messages.send({...}): The core function to send a message. We specify:message_type: ""text"": Plain text SMS.text: The content of the message.to: The recipient's phone number (from.env).from: The sender's phone number (must be your Vonage number, from.env).channel: ""sms"": Specifies the channel.
async/awaitwithtry...catch: Handles the asynchronous nature of the API call and catches potential errors. Detailed error logging helps debugging.
Receiving SMS (server.js)
This script sets up an Express server to listen for incoming POST requests from Vonage on a specific webhook URL.
-
Edit
server.js: Add the following code:javascript// server.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); // --- Configuration --- const PORT = process.env.PORT || 3000; const inboundWebhookPath = '/webhooks/inbound'; const statusWebhookPath = '/webhooks/status'; // --- Create Express App --- const app = express(); // --- Middleware --- // Vonage sends webhook data as JSON or URL-encoded form data depending on API/settings. // Use both parsers to be safe. app.use(express.json()); app.use(express.urlencoded({ extended: true })); // --- Routes --- // Health check endpoint app.get('/health', (req, res) => { res.status(200).send('OK'); }); // Vonage Inbound Message Webhook Handler app.post(inboundWebhookPath, (req, res) => { console.log(""--- Inbound SMS Received ---""); console.log(""Timestamp:"", new Date().toISOString()); console.log(""Request Body:"", JSON.stringify(req.body, null, 2)); // Log the entire payload // Extract key information (adjust based on actual payload structure from Vonage docs) const from = req.body.from; const to = req.body.to; const text = req.body.text; const messageId = req.body.message_uuid; console.log(`From: ${from}`); console.log(`To: ${to}`); console.log(`Message Text: ${text}`); console.log(`Message UUID: ${messageId}`); // **IMPORTANT:** Respond with 200 OK quickly! // Vonage expects a timely 200 response to acknowledge receipt. // Failure to respond promptly may result in retries from Vonage. // Offload any heavy processing (DB writes, external API calls) // to a background job/queue if necessary (See Section 9). res.status(200).end(); }); // Vonage Status Webhook Handler (Delivery Receipts - DLRs) app.post(statusWebhookPath, (req, res) => { console.log(""--- Message Status Update Received ---""); console.log(""Timestamp:"", new Date().toISOString()); console.log(""Request Body:"", JSON.stringify(req.body, null, 2)); // **IMPORTANT FOR PRODUCTION:** Parse the request body here! // You need to extract the message UUID, status (e.g., 'delivered', 'failed'), // timestamp, error codes, etc., to track the status of messages you SENT. // This is crucial for reliability and understanding message delivery success. // Example fields to look for (check Vonage docs for exact structure): // const messageUuid = req.body.message_uuid; // const status = req.body.status; // const timestamp = req.body.timestamp; // console.log(`Status for ${messageUuid}: ${status} at ${timestamp}`); // Update your database or application state based on the status. res.status(200).end(); // Acknowledge receipt }); // --- Error Handling Middleware (Basic) --- app.use((err, req, res, next) => { console.error(""--- Unhandled Error ---""); console.error(err.stack); res.status(500).send('Something broke!'); }); // --- Start Server --- app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); console.log(`Inbound SMS Webhook available at http://localhost:${PORT}${inboundWebhookPath}`); console.log(`Status Webhook available at http://localhost:${PORT}${statusWebhookPath}`); console.log('Run ngrok to expose this server: ngrok http', PORT); });Explanation:
express(): Creates an Express application instance.app.use(express.json())&app.use(express.urlencoded(...)): Middleware to parse incoming request bodies. Vonage might send JSON or form data, so using both covers common cases.app.post('/webhooks/inbound', ...): Defines the route handler for POST requests made by Vonage to/webhooks/inboundwhen an SMS is received.console.log(req.body): Logs the data sent by Vonage (sender number, recipient number, message text, message ID, timestamp, etc.).res.status(200).end(): Crucial step. Sends an HTTP 200 OK status back to Vonage immediately to acknowledge receipt. If Vonage doesn't receive a 200 OK quickly, it will assume the delivery failed and may retry sending the webhook, leading to duplicate processing./webhooks/status: Handler for Delivery Receipts (DLRs), which inform you about the status (e.g.,delivered,failed) of messages you sent. Parsing the body is essential in production./health: A simple endpoint for health checks.app.listen(PORT, ...): Starts the server on the specified port (from.envor default 3000).
3. Building a Complete API Layer (Optional Expansion)
The current setup uses a script (send-sms.js) for sending and a webhook handler (server.js) for receiving. For more complex applications, you might expose an API endpoint for sending SMS instead of using a standalone script.
Note: The following example demonstrates adding an API endpoint to server.js. Remember that the // TODO: comments highlight essential steps for production readiness, not optional improvements. Also, if you integrate this into server.js, ensure the Vonage client is initialized only once for the entire application.
Example API Endpoint for Sending:
You could add this to server.js:
// --- Add Vonage Client initialization near the top of server.js ---
// Ensure this is done only ONCE if merging with the server.
const { Vonage } = require('@vonage/server-sdk');
const vonage = new Vonage({
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH,
}, { debug: false });
// --- API Endpoint for Sending SMS ---
app.post('/api/send-sms', async (req, res) => {
// --- Authentication/Authorization (ESSENTIAL FOR PRODUCTION) ---
// TODO: Implement proper API key check, JWT validation, session check,
// or other auth mechanism suitable for your application.
// Example placeholder: Check for a specific header
// const apiKey = req.headers['x-api-key'];
// if (!isValidApiKey(apiKey)) { // Replace isValidApiKey with your actual validation logic
// return res.status(401).json({ error: 'Unauthorized' });
// }
// --- Request Validation (ESSENTIAL FOR PRODUCTION) ---
const { to, text } = req.body;
if (!to || !text || typeof to !== 'string' || typeof text !== 'string') {
return res.status(400).json({ error: 'Invalid request body. Required: ""to"" (string), ""text"" (string)' });
}
// TODO: Add more robust validation:
// - Check 'to' number format (E.164 recommended, use libphonenumber-js or similar)
// - Check 'text' length limits (consider SMS segment limits)
// - Sanitize input if used elsewhere (e.g., logging, database)
const vonageNumber = process.env.VONAGE_NUMBER;
if (!vonageNumber) {
console.error(""VONAGE_NUMBER not configured."");
return res.status(500).json({ error: 'Server configuration error.' });
}
try {
console.log(`API sending SMS from ${vonageNumber} to ${to}`);
const resp = await vonage.messages.send({
message_type: ""text"", text, to, from: vonageNumber, channel: ""sms""
});
console.log(""API SMS Sent Successfully. UUID:"", resp.message_uuid);
res.status(200).json({ success: true, message_uuid: resp.message_uuid });
} catch (err) {
console.error(""API Error sending SMS:"");
if (err.response && err.response.data) {
console.error(JSON.stringify(err.response.data, null, 2));
} else {
console.error(err);
}
// Provide a generic error message to the client for security
res.status(500).json({ success: false, error: 'Failed to send SMS.' });
}
});
// --- Add error handling and server start from original server.js ---
// ... (rest of server.js code: error handler, app.listen) ...Testing with cURL:
curl -X POST http://localhost:3000/api/send-sms \
-H ""Content-Type: application/json"" \
# -H ""X-API-Key: YOUR_SECRET_API_KEY"" # If you implemented auth
-d '{
""to"": ""RECIPIENT_E164_NUMBER"",
""text"": ""API Test Message!""
}'Response Example (Success):
{
""success"": true,
""message_uuid"": ""a1b2c3d4-e5f6-7890-abcd-ef1234567890""
}Response Example (Error):
{
""success"": false,
""error"": ""Failed to send SMS.""
}This demonstrates structuring the sending logic as a proper API endpoint. Remember that the placeholder authentication and validation comments (// TODO:) represent critical steps needed for any real-world application.
4. Integrating with Vonage
Now, let's configure our Vonage account and populate the .env file with the necessary credentials.
-
Log in to Vonage Dashboard: Go to https://dashboard.vonage.com/ and log in.
-
Get API Key and Secret: On the main dashboard page, you'll find your API Key and API Secret (you might need to reveal it) usually under ""API settings"" or your account profile.
- Copy the
API keyand paste it as the value forVONAGE_API_KEYin your.envfile. - Copy the
API secretand paste it as the value forVONAGE_API_SECRETin your.envfile.
(Note: While we use Application ID/Private Key for sending via Messages API, the Key/Secret might be needed for other SDK functions or account management).
- Copy the
-
Purchase a Vonage Number:
- Navigate to Numbers -> Buy numbers.
- Search for a number with SMS capability in your desired country.
- Purchase the number.
- Copy the purchased number (in E.164 format, e.g.,
12015550123for a US number) and paste it as the value forVONAGE_NUMBERin your.envfile. - Also, add your personal mobile number (for testing) in E.164 format as the value for
MY_TEST_NUMBER.
-
Create a Vonage Application: This application acts as a container for your configuration and security credentials (specifically the private key) needed for the Messages API.
- Navigate to Applications -> Create a new application.
- Give your application a name (e.g.,
Node Express SMS App). - Click Generate public and private key. This will automatically download a
private.keyfile. Save this file securely in your project's root directory (the same place as your.envfile). Make sure the path in your.envfile (VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key) matches the location and name of this file. Ensureprivate.keyis listed in your.gitignorefile. - Under Capabilities, find Messages and toggle it ON.
- Two URL fields will appear: Inbound URL and Status URL. We need
ngrokrunning first to get the public URLs for these.
-
Run
ngrok: Open a new terminal window in your project directory. Startngrokto expose your local server (running on port 3000, as defined in.envor the default inserver.js) to the internet.bashngrok http 3000ngrokwill display output similar to this:Session Status online Account Your Name (Plan: Free) Version 3.x.x Region United States (us-cal-1) Web Interface http://127.0.0.1:4040 Forwarding http://xxxxxxxx.ngrok.io -> http://localhost:3000 Forwarding https://xxxxxxxx.ngrok.io -> http://localhost:3000 # <-- COPY THIS HTTPS URL Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00Copy the
https://Forwarding URL. Keep this terminal window open while testing. -
Configure Application Webhooks:
- Go back to your Vonage Application configuration page (Step 4).
- In the Inbound URL field, paste your
ngrokHTTPS URL and append the path/webhooks/inbound. Example:https://xxxxxxxx.ngrok.io/webhooks/inbound - In the Status URL field, paste your
ngrokHTTPS URL and append the path/webhooks/status. Example:https://xxxxxxxx.ngrok.io/webhooks/status - Click Generate application or Save changes.
- You will be taken to the application's details page. Copy the Application ID displayed here.
- Paste the Application ID as the value for
VONAGE_APPLICATION_IDin your.envfile.
-
Link Your Number to the Application:
- Navigate to Numbers -> Your numbers.
- Find the Vonage number you purchased earlier.
- Click the Manage button (or the gear icon/three dots) next to the number.
- In the Forwarding or Application Assignment section (UI might vary), select Forward to Application or Assign Application.
- Choose the Application you just created (e.g.,
Node Express SMS App) from the dropdown menu. - Click Save.
-
Set Default SMS API (CRITICAL): The Vonage Node.js SDK's
vonage.messages.sendmethod relies on the account's default API setting being Messages API.- In the Vonage Dashboard, navigate to Account -> Settings.
- Scroll down to the API settings section.
- Find Default SMS Setting.
- Ensure Messages API is selected. If not, select it and click Save changes. This ensures the webhooks and API calls use the expected format and capabilities for the Messages API.
Your .env file should now be fully populated with your credentials and configuration, and your Vonage account should be correctly set up to route incoming messages to your local application via ngrok.
5. Implementing Error Handling, Logging, and Retry Mechanisms
Our basic scripts include some error handling, but let's refine it.
-
Sending Errors (
send-sms.js):- The
try...catchblock already handles API errors. - Logging
err.response.dataprovides detailed error information from Vonage (e.g., insufficient funds, invalid number format, throttling). - Retries: For transient network errors or temporary Vonage issues (like
429 Too Many Requestsor5xxserver errors), you could implement a retry mechanism using libraries likeasync-retryaround thevonage.messages.sendcall.
javascript// Example using async-retry (install with: npm install async-retry) const retry = require('async-retry'); async function sendSmsWithRetry() { // Define message details here or pass them as arguments const messageDetails = { message_type: ""text"", text: ""Your message content"", // Replace with actual text to: process.env.MY_TEST_NUMBER, from: process.env.VONAGE_NUMBER, channel: ""sms"" }; await retry(async (bail, attemptNumber) => { // If Vonage returns specific error codes indicating a permanent failure, // use bail() to stop retrying. Example: Invalid credentials (401), Invalid number (400). // Check err.response.status or err.response.data.type for specific conditions. try { console.log(`Attempting to send SMS (attempt ${attemptNumber})...`); const resp = await vonage.messages.send(messageDetails); console.log(""SMS Sent Successfully!""); console.log(""Message UUID:"", resp.message_uuid); return resp; // Return result on success } catch (err) { console.warn(`Attempt ${attemptNumber} failed: ${err.message}`); if (err.response && (err.response.status === 401 || err.response.status === 400)) { console.error(""Permanent error detected, not retrying.""); bail(err); // Stop retrying for permanent errors return; // Important to exit after bail } // For other errors (like 5xx, 429, network issues), throw to trigger retry throw err; } }, { retries: 3, // Number of retries factor: 2, // Exponential backoff factor minTimeout: 1000, // Initial timeout (ms) onRetry: (error, attempt) => { console.warn(`Retrying SMS send (attempt ${attempt}) due to error: ${error.message}`); } }); } // In send-sms.js, replace the call to sendSms() with: // sendSmsWithRetry().catch(err => { // console.error(""Failed to send SMS after multiple retries:"", err); // process.exitCode = 1; // }); - The
-
Receiving Errors (
server.js):- The main priority is sending
res.status(200).end()quickly. - Wrap the core logic within the webhook handler in a
try...catchblock to handle unexpected errors during processing (e.g., issues parsingreq.body, database errors if added). - Log errors thoroughly within the
catchblock. - Decide whether to still send
200 OKeven if processing fails (to prevent Vonage retries) or send a500 Internal Server Errorif the failure is critical and retrying might help (though Vonage might retry anyway). Sending200 OKis generally preferred unless you have specific retry logic tied to5xxerrors.
javascript// Inside app.post('/webhooks/inbound', ...) app.post(inboundWebhookPath, async (req, res) => { // Make handler async if using await inside try { console.log(""--- Inbound SMS Received ---""); console.log(""Timestamp:"", new Date().toISOString()); console.log(""Request Body:"", JSON.stringify(req.body, null, 2)); const { from, to, text, message_uuid } = req.body; console.log(`From: ${from}`); console.log(`To: ${to}`); console.log(`Message Text: ${text}`); console.log(`Message UUID: ${message_uuid}`); // --- Add your business logic here --- // Example: await saveMessageToDatabase(from, to, text, message_uuid); // Example: await triggerAnotherApiCall(text); // --- End business logic --- // If business logic succeeds, acknowledge receipt res.status(200).end(); } catch (error) { console.error(""--- Error processing inbound SMS ---""); console.error(""Timestamp:"", new Date().toISOString()); console.error(""Request Body:"", JSON.stringify(req.body, null, 2)); console.error(""Error Stack:"", error.stack); // Decide on response: // Option 1 (Recommended): Acknowledge receipt even on processing error // to prevent Vonage retries for potentially malformed but acknowledged data. // Log the error thoroughly for investigation. if (!res.headersSent) { // Check if response already sent res.status(200).end(); } // Option 2: Signal server error (Vonage might retry, potentially causing duplicates if the error is intermittent) // Use this cautiously, only if a retry is genuinely desired for this specific error. // if (!res.headersSent) { // Ensure headers haven't already been sent // res.status(500).send('Internal Server Error'); // } } }); // Note: res.status(200).end() is now inside the try/catch blocks. - The main priority is sending
-
Logging:
console.logis suitable for development.- For production, use a structured logging library like Pino or Winston. These enable:
- Log Levels:
info,warn,error,debug. - Structured Formats: JSON output for easier parsing by log analysis tools (Datadog, Splunk, ELK stack).
- Transports: Sending logs to files, databases, or external services.
- Log Levels:
- Log key events: Application start, server listening, API calls (request/response summaries), webhook received (key data), errors (with stack traces).
6. Creating a Database Schema and Data Layer (Optional Expansion)
If you need to store message history or related data, you'll need a database.
-
Choose a Database: PostgreSQL, MongoDB, MySQL, etc.
-
Choose an ORM/Driver: Prisma, Sequelize, Mongoose,
pg,mysql2. -
Define Schema:
- Example (using Prisma schema syntax):
prisma// schema.prisma datasource db { provider = ""postgresql"" // or ""mysql"", ""mongodb"", etc. url = env(""DATABASE_URL"") } generator client { provider = ""prisma-client-js"" } model SmsMessage { id String @id @default(cuid()) vonageMsgId String @unique // The message_uuid from Vonage direction String // ""inbound"" or ""outbound"" fromNumber String toNumber String body String? status String? // e.g., ""submitted"", ""delivered"", ""failed"", ""received"" (for inbound) vonageStatusPayload Json? // Store the full status webhook payload for reference createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([createdAt]) @@index([fromNumber]) @@index([toNumber]) } -
Implement Data Access:
- Install Prisma (
npm install prisma --save-dev,npm install @prisma/client). - Initialize Prisma:
npx prisma init. - Set
DATABASE_URLin.env. - Apply schema:
npx prisma db push(for development) ornpx prisma migrate dev(for migrations). - Use the Prisma client in your
server.js(or a dedicated data service layer):
javascript// server.js (add near top) const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); // Inside app.post('/webhooks/inbound', ...) try block: // Make the handler async: app.post(inboundWebhookPath, async (req, res) => { ... }); try { // ... previous logging ... const { from, to, text, message_uuid } = req.body; // NOTE: For production/high-volume, move this DB operation // to a background queue (See Section 9) to ensure the webhook // responds quickly. await prisma.smsMessage.create({ data: { vonageMsgId: message_uuid, direction: 'inbound', fromNumber: from, toNumber: to, body: text, status: 'received', // Initial status for inbound // Use Vonage timestamp if available and valid, otherwise fallback createdAt: req.body.timestamp && !isNaN(Date.parse(req.body.timestamp)) ? new Date(req.body.timestamp) : new Date(), }, }); console.log(`Inbound message ${message_uuid} saved to database.`); // Acknowledge receipt AFTER successful processing (if not queueing) if (!res.headersSent) { res.status(200).end(); } } catch (error) { console.error(""--- Error processing inbound SMS ---""); console.error(""Timestamp:"", new Date().toISOString()); console.error(""Request Body:"", JSON.stringify(req.body, null, 2)); console.error(""Error Stack:"", error.stack); console.error(""Database error during inbound processing:"", error); // Still acknowledge receipt to prevent Vonage retries for DB issues if (!res.headersSent) { res.status(200).end(); } } finally { // Optional: Add graceful shutdown logic for Prisma connection // process.on('SIGINT', async () => { await prisma.$disconnect(); process.exit(); }); } // res.status(200).end() moved inside try/catch/finally logic- Similarly, update
send-sms.jsor the/api/send-smsendpoint to record outbound messages and update their status based on the Status Webhook (/webhooks/status). Remember to handle potential database errors gracefully in all data access points.
- Install Prisma (
Frequently Asked Questions
how to send sms with node.js and vonage
Use the Vonage Messages API with the Node.js SDK. After setting up a Vonage application and number, initialize the Vonage client with your API credentials. Then, use `vonage.messages.send()` with the recipient's number, your Vonage number, and the message text. This function handles sending the SMS through the Vonage platform.
what is vonage messages api used for
The Vonage Messages API is a unified platform for sending and receiving messages across various channels like SMS, WhatsApp, Viber, and Facebook Messenger. This guide specifically focuses on SMS, enabling you to build applications that can send notifications, alerts, conduct basic customer interactions, or integrate SMS into existing workflows.
why use ngrok for vonage webhooks
Ngrok creates a secure tunnel from your local development server to the public internet, allowing Vonage to reach your webhook endpoints during development. Since Vonage delivers webhook events via HTTP requests to public URLs, ngrok provides a way to make your local server temporarily accessible externally.
how to receive sms messages in node.js
Create an Express.js route that handles POST requests to a specific webhook URL (e.g., `/webhooks/inbound`). Configure your Vonage application to send incoming SMS messages to this URL. The request body will contain message details like sender, recipient, and text. Respond to Vonage with a 200 OK status code to acknowledge receipt.
what is a vonage application id
A Vonage Application ID is a unique identifier for your application within the Vonage platform. It's essential for authenticating and authorizing access to Vonage APIs, including the Messages API used for sending SMS. The guide demonstrates how to create an application and obtain its ID in the Vonage Dashboard.
when should I use application id/private key authentication
Application ID/Private Key authentication is recommended for server-side applications interacting with Vonage APIs, especially those sending messages like SMS. This approach, as demonstrated in the provided code examples, enhances security over using API Key/Secret directly in your codebase.
how to set up vonage sms webhooks with express
Define POST routes in your Express application for inbound messages (`/webhooks/inbound`) and delivery receipts (`/webhooks/status`). Provide the ngrok HTTPS URL with these paths as webhooks in your Vonage Application settings. Ensure your Vonage number is linked to this application to receive incoming SMS and delivery status updates.
can I use a different port for my express server
Yes, you can change the port your Express server runs on. Modify the `PORT` environment variable in your `.env` file or the default port value in `server.js`. Remember to update the ngrok command and Vonage webhook URLs accordingly if you change the port.
what is the purpose of dotenv library
The `dotenv` library securely loads environment variables from a `.env` file into `process.env`. This allows you to store sensitive information like API keys and secrets outside your codebase, improving security and configuration management across different environments (development, staging, production).
how to handle vonage webhook delivery receipts
Set up a POST route (e.g., `/webhooks/status`) to receive DLRs. Vonage sends these to this URL when the message status changes (e.g., 'delivered', 'failed'). Parse `req.body` in this route to extract the message UUID, status, and timestamp. This information is crucial for tracking message delivery success and handling failures in production.
why respond with 200 ok to vonage webhooks
Responding with a 200 OK status code to Vonage webhooks is crucial to acknowledge message receipt and prevent retries. Vonage expects a prompt 200 OK, and if it doesn't receive this, it might assume failure and resend the webhook, potentially leading to duplicate message processing and incorrect behavior.
how to store received sms in a database
Define a database schema (e.g., using Prisma) with relevant fields like sender, recipient, message text, timestamps, and message status. Implement data access logic in your webhook handler (`/webhooks/inbound`) to save received SMS details to your database. For production applications, consider using a message queue for database operations to avoid blocking the webhook response.
what is the structure of the inbound sms webhook payload
The inbound SMS webhook payload is a JSON object containing message details. Refer to the Vonage API documentation for the exact structure, but it typically includes sender (`from`), recipient (`to`), message content (`text`), a unique message identifier (`message_uuid`), and a timestamp. The article provides examples of how to extract these fields from `req.body`.