Send SMS with Node.js, Express, and Vonage
This guide provides a complete walkthrough for building a simple Node.js and Express application to send SMS messages using the Vonage Messages API. We'll cover everything from project setup to basic error handling and testing, enabling you to integrate SMS functionality into your applications.
By the end of this tutorial, you will have a functional Express API endpoint that accepts a destination phone number and a message, then uses the Vonage API to send an SMS. This solves the common need to programmatically send notifications, alerts, or messages to users via SMS.
Key Technologies:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express: A minimal and flexible Node.js web application framework used to create our API endpoint.
- Vonage Messages API: A powerful API for sending and receiving messages across various channels, including SMS. We'll use the official Vonage Node.js SDK.
- dotenv: A module to load environment variables from a
.env
file, keeping sensitive credentials secure.
System Architecture:
[Client (e.g., curl, Postman)]
|
| HTTP POST Request (/send-sms)
v
[Node.js/Express App] ----> [dotenv loads credentials]
|
| Uses Vonage Node.js SDK
v
[Vonage Messages API]
|
| Sends SMS
v
[Recipient's Phone]
Prerequisites:
- Node.js and npm (or yarn): Installed on your system. You can download them from nodejs.org.
- Vonage API Account: Sign up for free at Vonage API Dashboard.
- Vonage API Key and Secret: Found on the homepage of your Vonage API Dashboard after signing up.
- Vonage Virtual Phone Number: You need to rent a Vonage number capable of sending SMS. You can do this through the Vonage Dashboard (
Numbers
>Buy numbers
). Note: Trial accounts may have restrictions on destination numbers. - Basic Command Line/Terminal Knowledge: Familiarity with navigating directories and running commands.
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 your project, then navigate into it.
mkdir vonage-sms-sender cd vonage-sms-sender
-
Initialize npm Project: This creates a
package.json
file to manage dependencies and project metadata. The-y
flag accepts default settings.npm init -y
-
Install Dependencies: We need Express for the server, the Vonage Server SDK to interact with the API, and dotenv to handle environment variables.
npm install express @vonage/server-sdk dotenv
express
: The web framework.@vonage/server-sdk
: The official Vonage library for Node.js. It simplifies interactions with Vonage APIs.dotenv
: Loads environment variables from a.env
file intoprocess.env
. This is crucial for keeping API keys and other sensitive information out of your source code.
-
Create Project Files: Create the main application file and the environment file.
touch index.js .env .gitignore
index.js
: This will contain our Express server and API logic..env
: This file will store our sensitive Vonage credentials. Never commit this file to version control..gitignore
: Specifies intentionally untracked files that Git should ignore.
-
Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing dependencies and sensitive credentials.# .gitignore node_modules/ .env
-
Configure
.env
: Open the.env
file and add your Vonage API credentials and virtual number. Replace the placeholder values with your actual credentials found in the Vonage Dashboard.# .env # Found on your Vonage API Dashboard homepage VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Your Vonage virtual number capable of sending SMS VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Port for the Express server PORT=3000
VONAGE_API_KEY
: Your public API key.VONAGE_API_SECRET
: Your private API secret. Treat this like a password.VONAGE_NUMBER
: The E.164 formatted Vonage number you rented (e.g.,14155550100
).PORT
: The port number our Express application will listen on.
2. Integrating with Vonage (Credentials and Configuration)
Before writing code, ensure your Vonage account is set up correctly.
- Locate Credentials: Log in to the Vonage API Dashboard. Your API key and API secret are displayed prominently on the main page. Copy these into your
.env
file. - Get a Virtual Number:
- Navigate to
Numbers
>Buy numbers
in the dashboard. - Search for numbers with SMS capabilities in your desired country.
- Purchase a number.
- Copy this number (in E.164 format, e.g.,
14155550100
) into theVONAGE_NUMBER
field in your.env
file.
- Navigate to
- Configure Default SMS API: Vonage has two primary APIs for SMS: the legacy SMS API and the newer Messages API. While the code in this guide explicitly uses the Messages API SDK (
vonage.messages.send
), setting the account default can be beneficial for consistency, especially if you plan to implement features like receiving messages via webhooks, as webhook formats differ between the APIs. To set the default:- Go to your Account Settings in the Vonage Dashboard.
- Scroll down to
API settings
. - Under
Default SMS Setting
, select Messages API. - Click
Save changes
.
3. Implementing Core Functionality (Sending SMS)
Let's write the Node.js code to initialize the Vonage SDK and send an SMS.
-
Initialize Dependencies in
index.js
: Openindex.js
and require the necessary modules. Load environment variables first usingdotenv
.// index.js 'use strict'; // Enforce stricter parsing and error handling // Load environment variables from .env file require('dotenv').config(); const express = require('express'); const { Vonage } = require('@vonage/server-sdk'); // Validate essential environment variables const missingVars = []; if (!process.env.VONAGE_API_KEY) missingVars.push('VONAGE_API_KEY'); if (!process.env.VONAGE_API_SECRET) missingVars.push('VONAGE_API_SECRET'); if (!process.env.VONAGE_NUMBER) missingVars.push('VONAGE_NUMBER'); if (missingVars.length > 0) { console.error(`Error: Missing required environment variables: ${missingVars.join(', ')} in .env file.`); process.exit(1); // Exit if configuration is missing } // Initialize Vonage SDK const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET // Note: Application ID and Private Key are used for other APIs (like Voice) or JWT auth, // but typically not needed for basic SMS sending with API Key/Secret via Messages API. }); const app = express(); const port = process.env.PORT || 3000; // Use port from .env or default to 3000 // Middleware to parse JSON request bodies app.use(express.json()); // Middleware to parse URL-encoded request bodies app.use(express.urlencoded({ extended: true })); console.log('Vonage SDK Initialized.');
require('dotenv').config();
: Loads the variables from.env
intoprocess.env
. It's critical to call this early.express()
: Creates an Express application instance.new Vonage(...)
: Initializes the Vonage SDK with your API key and secret.app.use(express.json())
: Adds middleware to automatically parse incoming JSON request bodies, making them available asreq.body
.app.use(express.urlencoded(...))
: Adds middleware to parse incoming form data.
-
Create the SMS Sending Function: It's good practice to encapsulate the sending logic in a reusable function.
// index.js (continued) /** * Sends an SMS message using the Vonage Messages API. * @param {string} recipient - The phone number to send the SMS to (E.164 format). * @param {string} messageText - The text content of the SMS. * @returns {Promise<object>} - A promise that resolves with the Vonage API response on success. * @throws {Error} - Throws an error if sending fails. */ async function sendSms(recipient, messageText) { const fromNumber = process.env.VONAGE_NUMBER; console.log(`Attempting to send SMS from ${fromNumber} to ${recipient}`); try { const response = await vonage.messages.send({ message_type: "text", to: recipient, // E.164 format (e.g., 14155550100) from: fromNumber, // Your Vonage number channel: "sms", text: messageText }); console.log(`SMS submitted successfully: Message UUID: ${response.messageUuid}`); return response; // Contains message_uuid on success } catch (error) { console.error("Error sending SMS via Vonage:", error?.response?.data || error.message || error); // Rethrow the error to be handled by the API route throw new Error(`Failed to send SMS: ${error?.response?.data?.title || error.message}`); } }
- We use an
async function
to work with the promise returned by the SDK. vonage.messages.send()
: This is the core method from the SDK for the Messages API.- Parameters:
message_type: "text"
: Specifies a standard text message.to
: The recipient's phone number (must be in E.164 format).from
: Your Vonage virtual number (also E.164 format).channel: "sms"
: Explicitly tells the Messages API to use SMS.text
: The content of your message.
- The
try...catch
block handles potential errors during the API call. We log detailed errors and rethrow a more user-friendly error message.
- We use an
4. Building the API Endpoint
Now, let's create an Express route that uses our sendSms
function.
// index.js (continued)
// API Endpoint: POST /send-sms
app.post('/send-sms', async (req, res) => {
console.log(""Received request on /send-sms:"", req.body);
const { to, text } = req.body; // Extract recipient number and message text from request body
// Basic Input Validation
if (!to || !text) {
console.error(""Validation Error: Missing 'to' or 'text' in request body."");
return res.status(400).json({ success: false, message: ""Missing 'to' (recipient phone number) or 'text' (message content) in request body."" });
}
// Basic E.164 format check (simple check)
// This regex checks for a '+' followed by digits.
if (!/^\+[1-9]\d{1,14}$/.test(to)) {
console.error(`Validation Error: Invalid 'to' format: ${to}. Must be E.164 format (e.g., +14155550100).`);
return res.status(400).json({ success: false, message: ""Invalid 'to' phone number format. Use E.164 format (e.g., +14155550100)."" });
}
// Note: While this regex covers many common cases, E.164 formatting can be complex.
// For production use, consider a dedicated phone number validation library for more robust checking.
try {
const result = await sendSms(to, text);
console.log(""API call successful:"", result);
res.status(200).json({ success: true, message: ""SMS submitted successfully."", messageId: result.messageUuid });
} catch (error) {
console.error(""API Error sending SMS:"", error.message);
// Send a generic server error status. The specific error is logged internally.
res.status(500).json({ success: false, message: error.message || ""An internal server error occurred while sending the SMS."" });
}
});
// Start the Express server
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
console.log(`API endpoint available at POST http://localhost:${port}/send-sms`);
});
app.post('/send-sms', ...)
: Defines a route that listens for HTTP POST requests on the/send-sms
path.async (req, res)
: The route handler is asynchronous because it calls ourasync sendSms
function.- Input Validation: We check if
to
andtext
are present in the JSON request body (req.body
). We also add a simple regex check for the E.164 format. If validation fails, we send a400 Bad Request
response. - Calling
sendSms
: If validation passes, we callsendSms
with the providedto
andtext
. - Success Response: On success, we send a
200 OK
response with the Vonage message UUID. - Error Response: If
sendSms
throws an error, thecatch
block sends a500 Internal Server Error
response. app.listen()
: Starts the Express server, making it listen for incoming connections on the specified port.
5. Running and Testing the Application
-
Start the Server: Open your terminal in the project directory (
vonage-sms-sender
) and run:node index.js
You should see output indicating the server is listening:
Vonage SDK Initialized. Server listening at http://localhost:3000 API endpoint available at POST http://localhost:3000/send-sms
-
Test with
curl
: Open another terminal window and usecurl
(or a tool like Postman) to send a POST request to your endpoint.- Replace
+1XXXXXXXXXX
with a valid phone number (in E.164 format). Important: If you are using a Vonage trial account, this number must be registered and verified in your Vonage Dashboard underGetting Started
>Add test numbers
. - Modify the
text
field as desired.
curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "+1XXXXXXXXXX", "text": "Hello from my Node.js Vonage App!" }'
- Replace
-
Check Responses:
- Success: You should receive a JSON response in your
curl
terminal like this:You should also see logs in the{ "success": true, "message": "SMS submitted successfully.", "messageId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }
node index.js
terminal confirming the attempt and success. The recipient phone should receive the SMS shortly. - Validation Error (e.g., missing field):
Status code:
{ "success": false, "message": "Missing 'to' (recipient phone number) or 'text' (message content) in request body." }
400 Bad Request
. - Vonage API Error (e.g., invalid credentials, non-whitelisted number):
Status code:
{ "success": false, "message": "Failed to send SMS: Non-Whitelisted Destination" // Example error message }
500 Internal Server Error
. Check thenode index.js
terminal logs for more specific details from Vonage.
- Success: You should receive a JSON response in your
-
Verify in Vonage Dashboard: You can also check the status of your sent messages in the Vonage Dashboard under
Logs
>Messages API logs
.
6. Error Handling and Logging
Our current implementation includes basic error handling:
- Configuration Errors: The application checks for essential
.env
variables on startup and exits if they are missing, listing the specific missing variables. - Input Validation: The
/send-sms
endpoint validates the presence and basic format ofto
andtext
, returning a400 Bad Request
if invalid. - API Call Errors: The
sendSms
function uses atry...catch
block to capture errors during the Vonage API call (e.g., network issues, authentication failures, invalid parameters). These errors are logged to the console and result in a500 Internal Server Error
response from the API endpoint.
Production Considerations:
- Robust Logging: For production, replace
console.log
/console.error
with a dedicated logging library like Winston or Pino. This enables structured logging, different log levels (debug, info, warn, error), and routing logs to files or external services. - Error Tracking: Integrate an error tracking service (e.g., Sentry, Bugsnag) to capture and aggregate errors automatically.
- Specific Error Handling: You might want to parse specific Vonage error codes (available in the
error.response.data
object from the SDK) to provide more context or implement specific retry logic (though Vonage handles some retries internally).
7. Security Considerations
- Environment Variables: Storing API keys, secrets, and phone numbers in
.env
and adding.env
to.gitignore
is the most critical security step. Never hardcode credentials in your source code. - Input Validation: Always validate and sanitize input from users/clients. Our current validation is basic; production apps might need more sophisticated checks (e.g., stricter phone number validation libraries).
- Rate Limiting: To prevent abuse, implement rate limiting on your API endpoint using middleware like express-rate-limit. This restricts how many requests a single IP address can make in a given time window.
npm install express-rate-limit
// index.js (add near other app.use calls) const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers message: { success: false, message: 'Too many requests, please try again later.' } }); // Apply the rate limiting middleware to API calls app.use('/send-sms', limiter); // Apply specifically to the send-sms route // Or app.use(limiter); // To apply to all routes
- HTTPS: Ensure your application is served over HTTPS in production to encrypt communication between the client and your server. Deployment platforms often handle this automatically.
8. Troubleshooting and Caveats
Non-Whitelisted Destination
Error: This is the most common error for trial accounts. Vonage trial accounts can only send SMS to numbers you have explicitly added and verified in your dashboard. Go toGetting Started
>Add test numbers
to manage your allowed destination numbers. You need to upgrade your account (add payment details) to send to any number.401 Unauthorized
Error: Double-check that yourVONAGE_API_KEY
andVONAGE_API_SECRET
in.env
are correct and that the file is being loaded correctly (ensurerequire('dotenv').config();
is called early). Ensure you haven't accidentally committed your.env
file.- Invalid
from
Number: Ensure theVONAGE_NUMBER
in your.env
file is a valid Vonage number you have rented, is SMS-capable, and is in E.164 format. - Invalid
to
Number Format: Ensure theto
number provided in the request body is in E.164 format (e.g.,+14155550100
). - Missing Dependencies: If you get errors like
Cannot find module 'express'
, ensure you rannpm install
. .env
Not Loading: Make surerequire('dotenv').config();
is at the very top of yourindex.js
before you access anyprocess.env
variables. Check that the.env
file is in the same directory where you runnode index.js
. Check the console for specific error messages about missing variables.- SDK Issues: Occasionally, SDKs can have bugs or conflicts. Ensure you're using a reasonably recent version (
npm outdated
can help check). Check the Vonage Node SDK GitHub Issues if you suspect an SDK problem.
9. Deployment Considerations
- Environment Variables: When deploying to platforms like Heroku, Vercel, AWS Elastic Beanstalk, Google Cloud Run, etc., you do not upload your
.env
file. Instead, configure the environment variables (VONAGE_API_KEY
,VONAGE_API_SECRET
,VONAGE_NUMBER
,PORT
) directly through the platform's dashboard or configuration files (e.g., Heroku Config Vars, Vercel Environment Variables). PORT
: Most platforms assign a port dynamically. Your code (const port = process.env.PORT || 3000;
) already handles this by preferring the environment variablePORT
if it's set by the platform.package.json
start
script: Add a start script to yourpackage.json
so deployment platforms know how to run your app:// package.json { // ... other fields "scripts": { "start": "node index.js", // Command to start the server "test": "echo \"Error: no test specified\" && exit 1" }, // ... other fields }
- HTTPS: Ensure your deployment platform provides HTTPS termination.
10. Verification and Next Steps
Verification Checklist:
- Project directory created (
vonage-sms-sender
). npm init -y
executed.- Dependencies installed (
express
,@vonage/server-sdk
,dotenv
). .env
file created with correctVONAGE_API_KEY
,VONAGE_API_SECRET
,VONAGE_NUMBER
..gitignore
created and includesnode_modules/
and.env
.index.js
created with required modules, Vonage SDK initialization, Express setup.sendSms
function implemented usingvonage.messages.send
.POST /send-sms
endpoint created with input validation and callssendSms
.- Server started successfully (
node index.js
). - Test
curl
command sent tohttp://localhost:3000/send-sms
. - Successful
200 OK
response received from API. - SMS message received on the destination test phone.
- (Trial Account) Destination number verified in Vonage dashboard.
Next Steps:
- Receive SMS: Implement webhooks using ngrok (for local development) and another Express route (e.g.,
POST /webhooks/inbound
) to handle incoming SMS messages sent to your Vonage number. See the Vonage documentation or blog posts on receiving messages. - Add Database: Store message history, user data, or application state in a database (e.g., PostgreSQL, MongoDB).
- Implement Authentication: Secure your API endpoint if it's not meant for public use.
- Unit/Integration Tests: Write tests using frameworks like Jest or Mocha to verify the functionality of your
sendSms
function and API endpoint. - Refine Error Handling: Implement more granular error handling and reporting.
You now have a solid foundation for sending SMS messages using Node.js, Express, and the Vonage Messages API. Remember to handle credentials securely and consult the Vonage Messages API documentation for more advanced features and options. Happy coding!