Frequently Asked Questions
Use the Vonage Messages API with the @vonage/server-sdk in your Node.js app. After setting up a Vonage application and linking your virtual number, make a POST request to the /send-sms endpoint with recipient number and message text in the request body. The server-side code will use vonage.messages.send() to send the SMS through the Vonage API.
The Vonage Messages API allows sending SMS messages, receiving inbound SMS, and tracking message delivery statuses. It's a unified API supporting several messaging channels, but this tutorial uses it for SMS because of its reliability and broad reach. It handles both outbound messaging and inbound webhooks.
Express.js simplifies setting up the web server and handling API requests and routing for our SMS application. It's lightweight and commonly used with Node.js. We define routes for sending SMS and receiving webhooks from Vonage.
ngrok is essential during development to expose your local server's webhook endpoints to the internet so Vonage can reach them. Vonage requires publicly accessible URLs for webhooks. Once deployed to a live server, ngrok is no longer required.
Yes, by setting up a status webhook URL in your Vonage application. The app will receive real-time delivery receipts (DLRs) at this endpoint, including the message UUID and delivery status (e.g., 'delivered', 'failed'). This is handled by the /webhooks/status route in the Express app.
Configure an inbound webhook URL in your Vonage application settings. Vonage will send incoming messages to this URL, which corresponds to the /webhooks/inbound route in your app. This route will receive the sender's number and message text. You'll need an SMS-enabled Vonage number linked to your application.
The private.key file is used for authentication with the Vonage Messages API, typically through JSON Web Tokens (JWT). The key is generated with your Vonage application and paired with a public key. Store it securely and never commit it to version control.
In the Vonage dashboard, create a new application, enable the Messages capability, and specify your ngrok HTTPS URLs for the inbound and status webhooks. Generate public and private keys, saving the private key securely. Then, link your Vonage virtual number to the application.
You'll need Node.js and npm installed, a Vonage API account with an API key and secret, a rented Vonage virtual number, ngrok for local development, and optionally the Vonage CLI. The tutorial also recommends using dotenv for environment variables.
dotenv loads environment variables from a .env file, keeping sensitive credentials like API keys out of your source code. This enhances security and makes managing configurations easier. It’s best practice for handling API secrets.
After setting up your Vonage application and linking the webhooks to ngrok URLs, start your Node.js server. Send an SMS to your Vonage number to trigger the inbound webhook. Send an SMS from your app to a test number to check the status webhook, observing logs for confirmation.
The @vonage/server-sdk simplifies interaction with Vonage APIs within your Node.js code. It handles authentication and provides methods like vonage.messages.send() to easily send messages and manage other Vonage services.
The Vonage Messages API provides delivery receipts (DLRs) via webhooks. Set up a status URL in your Vonage application. Your app will receive updates at this endpoint with the message UUID and status, enabling real-time delivery tracking within your application.
This guide provides a comprehensive, step-by-step tutorial for building a production-ready Node.js application using the Express framework to send SMS messages, receive inbound SMS, and track message delivery statuses via webhooks using the Vonage Messages API.
We'll cover everything from project setup and core functionality implementation to security, error handling, and deployment considerations. By the end, you'll have a robust system capable of reliably handling SMS communication flows.
Project overview and goals
This project aims to create a Node.js service that can:
Problem Solved: This application addresses the need for real-time, two-way SMS communication with status tracking, crucial for applications like appointment reminders, order confirmations, two-factor authentication (2FA), and customer support notifications where delivery confirmation is essential.
Technologies Used:
.env
file intoprocess.env
, keeping sensitive credentials out of source code.System Architecture:
Prerequisites:
npm install -g @vonage/cli
. Useful for managing applications and numbers.1. Setting up the project
Let's create the project structure, install dependencies, and configure the environment.
1. Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
2. Initialize Node.js Project: Initialize the project using npm, accepting the defaults.
This creates a
package.json
file.3. Install Dependencies: Install Express for the web server, the Vonage SDK for API interaction, and
dotenv
for environment variable management.express
: Web framework for handling HTTP requests and routes.@vonage/server-sdk
: Simplifies calls to the Vonage APIs.dotenv
: Loads environment variables from a.env
file.4. Create Project Structure: Create the necessary files and folders.
server.js
: Will contain our main Express application logic..env
: Stores sensitive configuration like API keys (DO NOT commit this file)..gitignore
: Specifies intentionally untracked files that Git should ignore (like.env
andnode_modules
).private.key
: Will store the private key downloaded from your Vonage Application (DO NOT commit this file).5. Configure
.gitignore
: Add the following lines to your.gitignore
file to prevent committing sensitive information and unnecessary files:6. Configure
.env
: Open the.env
file and add placeholders for your Vonage credentials and application details. We'll fill these in later.Explanation of Configuration Choices:
.env
File: Using a.env
file anddotenv
is a standard practice for managing configuration and secrets in Node.js applications, keeping sensitive data separate from the codebase..gitignore
: Essential for preventing accidental exposure of API keys, private keys, and environment files in version control systems like Git.private.key
: The Vonage Messages API often uses JWTs generated with a public/private key pair for authentication via Applications. Storing the key securely is paramount.2. Vonage account and application setup
Before writing code, we need to configure our Vonage account and create a Vonage Application to handle Messages API interactions and webhooks.
1. Get API Key and Secret: Log in to your Vonage API Dashboard. Your API Key and Secret are displayed at the top. Copy these values into your
.env
file forVONAGE_API_KEY
andVONAGE_API_SECRET
.2. Set Default SMS API: Navigate to your Account Settings in the Vonage dashboard. Scroll down to ""API settings"" > ""SMS settings"". Ensure ""Default SMS Setting"" is set to Messages API. This ensures webhooks use the Messages API format. Click ""Save changes"".
3. Create a Vonage Application: Vonage Applications act as containers for your communication configurations, including webhook URLs and authentication keys.
private.key
file. Save this file in the root directory of your project (the same location as yourserver.js
). Ensure theVONAGE_PRIVATE_KEY_PATH
in your.env
file correctly points to it (./private.key
). The public key is automatically stored by Vonage.ngrok
in the next step. For now, you can enter temporary placeholders likehttp://example.com/webhooks/inbound
andhttp://example.com/webhooks/status
. We will update these shortly. Ensure the method is set to POST..env
file forVONAGE_APPLICATION_ID
.4. Link Your Vonage Number: Scroll down on the Application details page to the ""Link virtual numbers"" section. Find the Vonage virtual number you rented and click the ""Link"" button next to it. This directs incoming messages and status updates for this number to the webhooks defined in this application. Copy this number (in E.164 format, e.g.,
18885551212
) into your.env
file forVONAGE_NUMBER
.5. Start ngrok: Now, let's get the public URL for our webhooks. Open a new terminal window (keep the first one for running the server later). Navigate to your project directory (optional but good practice) and run ngrok, telling it to expose the port your Express server will run on (defined as
PORT
in.env
, default is 3000).ngrok will display output similar to this:
Copy the
https://<random-string>.ngrok.io
URL (use the HTTPS version). This is your public base URL.6. Update Webhook URLs in Vonage Application: Go back to your Vonage Application settings in the dashboard (https://dashboard.nexmo.com/applications, find your app, and click ""Edit"").
YOUR_NGROK_HTTPS_URL/webhooks/inbound
(e.g.,https://<random-string>.ngrok.io/webhooks/inbound
)YOUR_NGROK_HTTPS_URL/webhooks/status
(e.g.,https://<random-string>.ngrok.io/webhooks/status
)Now, Vonage knows where to send incoming messages and status updates.
3. Implementing core functionality (Express server and Vonage SDK)
Let's write the Node.js code in
server.js
to initialize the server, configure Vonage, send SMS, and handle the webhooks.Code Explanation:
dotenv
first to ensure environment variables are available. Importexpress
and necessary components from@vonage/server-sdk
.express.json()
andexpress.urlencoded()
are crucial for parsing incoming webhook request bodies, which Vonage sends as JSON.FileSystemCredentials
, providing the Application ID and the path to theprivate.key
file. This method is required for authenticating Messages API calls associated with an Application./send-sms
Endpoint:POST
route to trigger sending an SMS.to
andtext
from the JSON request body.vonage.messages.send()
within anasync
function.message_type: 'text'
,channel: 'sms'
, along withto
,from
, andtext
.try...catch
for error handling, logging errors and returning appropriate HTTP status codes (400 for bad input, 500 or Vonage's status for API errors).message_uuid
on success, which is useful for tracking./webhooks/inbound
Endpoint:POST
route matching the Inbound URL configured in Vonage.req.body
) received from Vonage. This contains details about the incoming SMS (sender number, message text, timestamp, etc.).200 OK
response. Vonage expects this acknowledgment; otherwise, it will retry sending the webhook, leading to duplicate processing.TODO
) for adding your application-specific logic (database saving, auto-replies)./webhooks/status
Endpoint:POST
route matching the Status URL configured in Vonage.message_uuid
of the original outbound message and its deliverystatus
(e.g.,delivered
,failed
,rejected
,accepted
), along with timestamps and potential error details.200 OK
response to acknowledge receipt.TODO
) for updating your application's message records.GET
route for basic verification that the server is running.4. Running and testing the application
Let's bring it all together and test the flow.
1. Ensure ngrok is Running: Verify that your
ngrok http 3000
command is still running in its terminal window and that theForwarding
URL matches the one configured in your Vonage Application's webhook settings. If it stopped, restart it and update the URLs in Vonage if the random subdomain changed (unless you have a paid ngrok plan with a static domain).2. Fill
.env
File: Make sure you have filled in all the values in your.env
file:VONAGE_API_KEY
,VONAGE_API_SECRET
,VONAGE_APPLICATION_ID
,VONAGE_NUMBER
, and a validTO_NUMBER
for testing. Remember to use the E.164 format for phone numbers (e.g.,14155552671
).3. Start the Node.js Server: In your primary terminal window (the one where you created the files and installed dependencies), run:
You should see output like:
4. Test Sending an SMS: Open a third terminal window or use a tool like Postman or
curl
to send a POST request to your/send-sms
endpoint.Using
curl
:Replace
YOUR_TEST_RECIPIENT_NUMBER
with the number you set inTO_NUMBER
or any other valid number.server.js
, you should see:Attempting to send SMS from <VONAGE_NUMBER> to <TO_NUMBER>: ""Hello from Vonage Node.js Application!""
SMS submitted successfully: { message_uuid: '...' }
curl
Output: You should receive a JSON response like:{""message"":""SMS sent successfully!"",""message_uuid"":""...""}
.5. Test Delivery Status Webhook: Wait a few seconds after the message is delivered to your phone.
--- Delivery Status Update Received ---
Request Body: { ... ""status"": ""delivered"", ""message_uuid"": ""..."", ... }
(or potentiallyaccepted
first, thendelivered
). The exact status flow can vary slightly.UUID: ..., Status: delivered, Timestamp: ...
6. Test Inbound SMS Webhook: Using the phone that received the test message (or any phone), send an SMS message to your Vonage virtual number (the one specified in
VONAGE_NUMBER
).--- Inbound SMS Received ---
Request Body: { ""from"": { ""type"": ""sms"", ""number"": ""<SENDER_NUMBER>"" }, ""text"": ""<YOUR_MESSAGE_TEXT>"", ... }
From: <SENDER_NUMBER>, Text: <YOUR_MESSAGE_TEXT>, UUID: ..., Timestamp: ...
If all these steps work and you see the corresponding logs, your core functionality is correctly implemented!
5. Implementing error handling, logging, and retry mechanisms
While our basic example includes
try...catch
andconsole.log
/console.error
, production applications need more robust strategies. This involves replacing the basic console logging with a structured logger like Winston for better analysis and handling errors gracefully.Error Handling Strategy:
vonage.messages.send()
call. Inspecterr.response.data
orerr.message
for details from the Vonage API. Return appropriate HTTP status codes (e.g., 400 for invalid input to Vonage, 401 for auth issues, 500/503 for Vonage server issues). Log these errors using a structured logger./webhooks/inbound
,/webhooks/status
) intry...catch
blocks. Log any errors encountered during processing (e.g., database write failure) using the logger. Crucially, still return a200 OK
response to Vonage unless the request itself is malformed (which is unlikely for valid Vonage webhooks). Acknowledging the webhook prevents Vonage retries for processing errors that Vonage can't fix. Handle the processing failure asynchronously (e.g., add to a retry queue)./send-sms
) and webhook payloads (check for expected fields) early and return400 Bad Request
if invalid. Log validation failures.Logging:
Using a dedicated logging library like Winston or Pino is highly recommended over
console.log
/console.error
for production applications.console.*
: The primary goal is to replace all instances ofconsole.log
andconsole.error
in yourserver.js
(specifically within the route handlers like/send-sms
,/webhooks/inbound
,/webhooks/status
) with calls to a logger instance (e.g.,logger.info
,logger.error
).debug
,info
,warn
,error
. Log informational messages (like successful sends, received webhooks) atinfo
, potential issues atwarn
, and definite errors aterror
. Usedebug
for verbose tracing during development.message_uuid
,request_id
,endpoint
, etc.Example using Winston (Basic Setup):
npm install winston
server.js
:Integrating the Logger:
Go back through the
server.js
code provided in Section 3 and systematically replace everyconsole.log(...)
withlogger.info(...)
and everyconsole.error(...)
withlogger.error(...)
. Ensure you pass relevant contextual information as the second argument (metadata object) to the logger methods, as shown in the examples above. This provides much richer and more useful logs for debugging and monitoring.Retry Mechanisms (Webhook Processing):
Vonage automatically retries sending webhooks if it doesn't receive a
200 OK
response within a certain timeout (usually a few seconds). However, this only handles network issues or your server being temporarily down. It doesn't handle errors within your webhook processing logic (like a database connection failure).For robust processing:
res.status(200).send('OK')
immediately after receiving and basic validation of the webhook.async-retry
or queue-specific features can help.This ensures your webhook endpoints remain responsive to Vonage while handling transient processing failures gracefully. For simpler applications, logging the error and potentially alerting might suffice, accepting that some webhook events might be lost if processing fails persistently.
6. Creating a database schema and data layer (Conceptual)
Storing message details and status is often necessary. Here’s a conceptual schema and considerations.
Database Choice: PostgreSQL, MySQL, MongoDB, or others depending on needs. Relational databases (Postgres, MySQL) are often suitable for structured message data.
Conceptual Schema (Relational Example):
Data Layer Implementation:
vonage.messages.send()
, create a record insms_messages
withdirection = 'outbound'
,status = 'submitted'
,recipient_number
,message_body
, etc. Store the intendedmessage_uuid
if you generate one client-side, or leave it NULL.vonage.messages.send()
call, update the record with themessage_uuid
returned by Vonage./webhooks/status
):message_uuid
,status
,timestamp
, anderror
details.sms_messages
usingmessage_uuid
.status
,status_updated_at
,vonage_status_code
, andvonage_status_reason
. Handle potential race conditions if multiple status updates arrive for the same UUID (e.g., check timestamps)./webhooks/inbound
):from.number
,text
,message_uuid
,timestamp
.sms_messages
withdirection = 'inbound'
,status = 'received'
,sender_number = from.number
,message_body = text
,received_at = timestamp
, and themessage_uuid
.Migrations: Use database migration tools (like Sequelize CLI, TypeORM CLI, Prisma Migrate) to manage schema changes version control.
Performance: Index columns frequently used in
WHERE
clauses (message_uuid
,status
,direction
, timestamps).