code examples
code examples
Build Two-Way SMS Messaging with Node.js Express & Vonage API (Complete Tutorial)
Learn how to implement inbound and outbound SMS messaging in Node.js using Express and Vonage. Step-by-step guide covers webhooks, auto-replies, and production deployment.
Build Two-Way SMS Messaging with Node.js Express & Vonage API
This comprehensive guide shows you how to build a production-ready Node.js application using the Express framework to handle two-way SMS messaging with the Vonage Messages API. You will learn how to send outbound SMS messages, receive inbound SMS via webhooks, and create automated SMS reply workflows.
Project Overview and Goals
What We're Building:
An Express.js application capable of:
- Sending SMS: Exposing an API endpoint to send SMS messages programmatically via the Vonage Messages API.
- Receiving SMS: Handling incoming SMS messages sent to a Vonage virtual number via webhooks.
- Two-Way Interaction: Replying automatically to inbound messages, demonstrating a basic two-way conversation flow.
Problem Solved:
This application enables businesses and developers to integrate programmatic SMS communication into their workflows, facilitating automated notifications, customer support interactions, alerts, and more, directly from their Node.js backend.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express.js: A minimal and flexible Node.js web application framework used to build the API and handle webhook requests.
- Vonage Messages API: A unified API for sending and receiving messages across various channels, including SMS. We will use it for both outbound and inbound SMS.
- @vonage/server-sdk: The official Vonage Node.js SDK for interacting with Vonage APIs.
- ngrok: A tool to expose local development servers to the internet, essential for testing Vonage webhooks locally.
- dotenv: A module to load environment variables from a
.envfile intoprocess.env.
System Architecture:
+-----------------+ +-----------------------+ +----------------+ +-------------+
| User/Client App | ---> | Node.js/Express App | ---> | Vonage API | ---> | User's Phone|
| (e.g., curl, UI)| | (Sends SMS, | | (Messages API) | | (Receives SMS)|
| | <--- | Handles Webhooks) | <--- | | <--- | (Sends SMS) |
+-----------------+ +-----------------------+ +----------------+ +-------------+
| ^ |
| API Call | | Webhook Notification
v +-----+
+-----------------------+
| Vonage Application |
| (Configured Webhooks) |
+-----------------------+
Prerequisites:
- Vonage API Account: Sign up at Vonage.com. You'll need your API Key and Secret.
- Vonage Virtual Number: Purchase an SMS-capable number from the Vonage Dashboard.
- Node.js and npm: Installed on your development machine (LTS version recommended). Download Node.js
- ngrok: Installed and authenticated. Download ngrok
- Basic JavaScript/Node.js Knowledge: Familiarity with asynchronous programming (
async/awaitor Promises). - Terminal/Command Line Access: For running commands.
Expected Outcome:
By the end of this guide, you will have a running Node.js Express application that can send SMS messages via an API call and automatically reply to any SMS messages received on your configured Vonage number.
1. Setting Up Your Node.js SMS Project
Let's initialize the 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.
bashmkdir vonage-sms-app cd vonage-sms-app -
Initialize npm: Initialize the project with npm to create a
package.jsonfile. The-yflag accepts default settings.bashnpm init -y -
Install Dependencies: Install Express, the Vonage Server SDK, and dotenv.
bashnpm install express @vonage/server-sdk dotenv -
Create
.gitignore: Create a.gitignorefile to prevent sensitive information and unnecessary files from being committed to version control.bashtouch .gitignoreAdd the following lines to your
.gitignorefile:text# .gitignore # Node dependencies node_modules/ # Environment variables .env # Private Key file (if stored in project) private.key # Log files *.log # OS generated files .DS_Store Thumbs.db -
Set Up Environment Variables: Create a
.envfile in the root of your project to store sensitive credentials and configuration.bashtouch .envPopulate
.envwith the following variables. We'll fill in the values in the next section.dotenv# .env # Vonage API Credentials (Found in your Vonage Dashboard) VONAGE_API_KEY=YOUR_VONAGE_API_KEY VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET # Vonage Application Credentials (Generated in the next step) VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Or the full path to your key # Vonage Number (Purchased from Vonage Dashboard) VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Application Port PORT=3000- Why
.env? Storing configuration and secrets in environment variables is a best practice. It keeps sensitive data out of your codebase and makes configuration easier across different environments (development, staging, production).dotenvhelps load these variables during development.
- Why
-
Create Basic Server File: Create an
app.jsfile for your Express application logic.bashtouch app.jsAdd the initial Express setup:
javascript// app.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const { json, urlencoded } = express; const app = express(); app.use(json()); // Middleware to parse JSON request bodies app.use(urlencoded({ extended: true })); // Middleware to parse URL-encoded request bodies const PORT = process.env.PORT || 3000; // Use port from .env or default to 3000 // Basic route for testing app.get('/', (req, res) => { res.send('Vonage SMS App is running!'); }); app.listen(PORT, () => { console.log(`Server listening at http://localhost:${PORT}`); }); -
Run the Basic Server: Start your server to ensure the basic setup is working.
bashnode app.jsYou should see
Server listening at http://localhost:3000in your terminal. You can stop the server withCtrl+C.
2. Vonage Account and Application Setup
Now, let's configure your Vonage account, get the necessary credentials, and set up a Vonage Application to handle messages.
-
Log in to Vonage Dashboard: Access your Vonage API Dashboard.
-
Get API Key and Secret: Your API Key and Secret are displayed at the top of the dashboard home page. Copy these values and paste them into your
.envfile forVONAGE_API_KEYandVONAGE_API_SECRET. -
Purchase a Vonage Number:
- Navigate to Numbers -> Buy numbers.
- Search for a number with SMS capabilities in your desired country.
- Purchase the number.
- Copy this number (including the country code, e.g.,
14155550100) and paste it into your.envfile forVONAGE_NUMBER.
-
Set Default SMS API to Messages API:
- Navigate to Account -> API settings.
- Scroll down to the SMS settings section.
- Under Default SMS Setting, select Messages API.
- Click Save changes.
- Why? Vonage has multiple APIs for SMS. The Messages API is the modern, unified API that supports multiple channels. Setting it as default ensures consistency and that webhooks use the Messages API format.
-
Create a Vonage Application:
- Navigate to Applications -> Create a new application.
- Give your application a descriptive name (e.g.,
Node Two-Way SMS App). - Click Generate public and private key. This will automatically download a
private.keyfile. Save this file securely. Move it to the root of your project directory (or another secure location). UpdateVONAGE_PRIVATE_KEY_PATHin your.envfile to point to its location (e.g.,./private.keyif it's in the project root). - Enable the Messages capability.
- You'll need to enter Inbound URL and Status URL webhooks. For now, you can enter placeholder URLs like
http://example.com/webhooks/inboundandhttp://example.com/webhooks/status. We will update these later with our ngrok URL. - Click Generate new application.
- After creation, you'll see the Application ID. Copy this ID and paste it into your
.envfile forVONAGE_APPLICATION_ID.
-
Link Your Number to the Application:
- Go back to the Applications list and find the application you just created.
- Click Link next to the Vonage number you purchased earlier.
- Why? This tells Vonage that any messages received on this specific number should be handled by this application and its configured webhooks.
3. Implementing Outbound SMS (Sending Messages)
Let's add the functionality to send SMS messages via an API endpoint.
-
Initialize Vonage SDK: In
app.js, initialize the Vonage SDK using the credentials from your environment variables. Place this near the top, afterrequire('dotenv').config();.javascript// app.js // ... (dotenv, express requires) const { Vonage } = require('@vonage/server-sdk'); // Initialize Vonage const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET, // Note: Application ID and Private Key are configured in .env and are crucial // for linking the number and ensuring Vonage directs incoming webhooks correctly, // even though they might not be explicitly required in this SDK client instance // *just* for sending SMS via Key/Secret authentication. // applicationId: process.env.VONAGE_APPLICATION_ID, // privateKey: process.env.VONAGE_PRIVATE_KEY_PATH }, { debug: true }); // Enable debug logging for Vonage SDK // ... (express app setup)- Note on Authentication: We initialize the SDK using the API Key and Secret, which are sufficient for sending SMS via the Messages API as demonstrated in this guide. The Application ID and Private Key (set in
.env) are essential for Vonage to correctly associate your number with this application and route incoming message webhooks.
- Note on Authentication: We initialize the SDK using the API Key and Secret, which are sufficient for sending SMS via the Messages API as demonstrated in this guide. The Application ID and Private Key (set in
-
Create Sending Function: It's good practice to encapsulate the sending logic in a function.
javascript// app.js // ... (Vonage initialization) async function sendSms(to, text) { const from = process.env.VONAGE_NUMBER; // Your Vonage number from .env try { const resp = await vonage.messages.send({ message_type: "text", text: text, to: to, // The recipient's phone number from: from, // Your Vonage virtual number channel: "sms" }); console.log("Message sent successfully:", resp.message_uuid); return { success: true, message_uuid: resp.message_uuid }; } catch (err) { console.error("Error sending SMS:", err.response ? err.response.data : err.message); // Log more detail if available from Vonage response if (err.response && err.response.data) { console.error("Vonage Error Details:", JSON.stringify(err.response.data, null, 2)); } return { success: false, error: err.message }; } } // ... (express app setup)- Why
async/await? Thevonage.messages.sendmethod is asynchronous (it makes a network request).async/awaitprovides a cleaner way to handle promises compared to.then()/.catch()chains. - Parameters Explained:
message_type: "text": Specifies a standard text message.text: The content of the SMS.to: The recipient's phone number in E.164 format (e.g.,14155550101).from: Your Vonage virtual number (must be linked to your app if required by regulations).channel: "sms": Explicitly specifies the SMS channel.
- Why
-
Create API Endpoint for Sending: Add a POST route to your Express app to trigger the
sendSmsfunction.javascript// app.js // ... (sendSms function) // API Endpoint to send SMS app.post('/send-sms', async (req, res) => { const { to, text } = req.body; if (!to || !text) { return res.status(400).json({ error: 'Missing required fields: to, text' }); } // Basic validation (can be more robust) if (!/^\d+$/.test(to.replace(/^\+/, ''))) { return res.status(400).json({ error: 'Invalid "to" phone number format.' }); } const result = await sendSms(to, text); if (result.success) { res.status(200).json({ message: 'SMS sent successfully', message_uuid: result.message_uuid }); } else { res.status(500).json({ error: 'Failed to send SMS', details: result.error }); } }); // ... (app.listen)- Input Validation: Basic checks are included for required fields (
to,text) and a simple format check for thetonumber. Production apps should have more robust validation.
- Input Validation: Basic checks are included for required fields (
-
Test Sending:
-
Restart your Node.js server:
node app.js. -
Open a new terminal window or use a tool like Postman/Insomnia.
-
Send a POST request using
curl:bashcurl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_PERSONAL_PHONE_NUMBER", "text": "Hello from Vonage Node App!" }'- Replace
YOUR_PERSONAL_PHONE_NUMBERwith your actual mobile number in E.164 format (e.g.,14155550101).
- Replace
-
You should receive an SMS on your phone, and see success logs in your Node.js console and a JSON response in your
curlterminal.
-
4. Implementing Inbound SMS Webhooks (Receiving Messages)
To receive messages, Vonage needs a publicly accessible URL (a webhook) to send HTTP POST requests to when your Vonage number gets an SMS. We'll use ngrok for local development.
-
Start ngrok: If your app is running on port 3000 (as configured in
.envor defaulted inapp.js), run ngrok to expose this port. Whilengrokis excellent for local development, for staging or production environments where you have a publicly accessible server, you would use your server's actual public URL instead. Other tunneling services (likelocaltunnelor cloud provider specific tools) also exist.bash# Make sure your Node app is running in another terminal: node app.js ngrok http 3000ngrok will provide a
ForwardingURL (e.g.,https://<random-string>.ngrok.io). Copy thehttpsversion of this URL. -
Update Vonage Application Webhooks:
- Go back to your Vonage Application settings in the dashboard (Applications -> Your App -> Edit).
- Update the Messages webhooks:
- Inbound URL: Paste your ngrok
httpsURL, adding/webhooks/inboundat the end (e.g.,https://<random-string>.ngrok.io/webhooks/inbound). Set the method to POST. - Status URL: Paste your ngrok
httpsURL, adding/webhooks/statusat the end (e.g.,https://<random-string>.ngrok.io/webhooks/status). Set the method to POST.
- Inbound URL: Paste your ngrok
- Click Save changes.
- Why both?
Inbound URL: Receives data about incoming messages (SMS sent to your Vonage number).Status URL: Receives delivery receipts and status updates for outgoing messages (SMS sent from your Vonage number).
-
Create Webhook Handlers in Express: Add POST routes in
app.jsto handle requests from Vonage.javascript// app.js // ... (after /send-sms route) // Webhook endpoint for incoming SMS messages app.post('/webhooks/inbound', (req, res) => { console.log('--- Inbound Message ---'); console.log(JSON.stringify(req.body, null, 2)); // Log the full inbound payload // TODO: Process the inbound message (e.g., reply) // Vonage needs a 200 OK response to know the webhook is working res.status(200).end(); }); // Webhook endpoint for SMS status updates app.post('/webhooks/status', (req, res) => { console.log('--- Message Status ---'); console.log(JSON.stringify(req.body, null, 2)); // Log the status update // TODO: Process the status update (e.g., track delivery) // Respond with 200 OK res.status(200).end(); }); // ... (app.listen)- Crucial: Always respond with a
200 OKstatus to Vonage webhooks promptly. If Vonage doesn't receive a200 OK, it will assume the delivery failed and may retry, leading to duplicate processing. - Logging: Logging the full
req.bodyis essential for understanding the data structure Vonage sends.
- Crucial: Always respond with a
-
Test Receiving:
-
Make sure your Node.js app (
node app.js) and ngrok (ngrok http 3000) are running. -
From your personal mobile phone, send an SMS message to your Vonage virtual number (the one in
VONAGE_NUMBER). -
Check your Node.js console. You should see the "--- Inbound Message ---" log followed by a JSON object containing details about the SMS you sent (sender number, text content, timestamp, etc.).
-
Example Inbound Payload (
req.body):json{ "to": "18335787204", "from": "19999999999", "channel": "sms", "message_uuid": "a580f869-e995-4d76-9b80-a7befe3186a3", "timestamp": "2022-12-07T23:04:32Z", "usage": { "price": "0.0057", "currency": "EUR" }, "message_type": "text", "text": "Hello Vonage!", "sms": { "num_messages": "1" } } -
You can also inspect the request details in the ngrok web interface, usually accessible at
http://127.0.0.1:4040.
-
5. Building Two-Way SMS Communication (Auto-Reply)
Now, let's connect the inbound handler to the outbound function to create an auto-reply bot.
-
Modify Inbound Webhook Handler: Update the
/webhooks/inboundroute to extract the sender's number and message, then callsendSmsto reply.javascript// app.js // Webhook endpoint for incoming SMS messages app.post('/webhooks/inbound', async (req, res) => { // Make the handler async console.log('--- Inbound Message ---'); console.log(JSON.stringify(req.body, null, 2)); const inboundData = req.body; // Ensure it's an inbound SMS text message if (inboundData.channel === 'sms' && inboundData.message_type === 'text') { const sender = inboundData.from; const messageText = inboundData.text; console.log(`Received message "${messageText}" from ${sender}`); // Simple auto-reply logic const replyText = `You said: "${messageText}". Thanks for messaging!`; // Send the reply using the existing sendSms function await sendSms(sender, replyText); } else { console.log('Received non-SMS or non-text message, skipping reply.'); } // Always respond with 200 OK res.status(200).end(); }); // ... (rest of the code)- Key Changes:
- The handler is now
asyncto allowawaiting thesendSmscall. - We extract
inboundData.from(the sender's number) andinboundData.text. - We call
sendSmswith the original sender as the new recipient (to).
- The handler is now
- Key Changes:
-
Test Two-Way Messaging:
- Restart your Node.js app (
node app.js). Ensure ngrok is still running. - Send another SMS from your personal phone to your Vonage number.
- You should see the inbound message logged in your Node console.
- Shortly after, you should receive an SMS reply on your personal phone like:
"You said: "<Your Message>". Thanks for messaging!". - Check the Node console again; you should see logs for both the inbound message and the status of the outbound reply message (coming to the
/webhooks/statusendpoint).
- Restart your Node.js app (
6. Error Handling and Logging
Robust error handling and clear logging are crucial for production applications.
-
Vonage SDK Errors: The
try...catchblock insendSmsalready handles errors from the Vonage API. Loggingerr.response.dataprovides specific Vonage error details when available. -
Webhook Errors:
- Ensure your webhook endpoints (
/webhooks/inbound,/webhooks/status) always return200 OK. Usetry...catchwithin these handlers if performing complex logic that might fail, but ensure theres.status(200).end()is called even if an internal error occurs during processing. - Log any processing errors clearly within the webhook handlers.
- Ensure your webhook endpoints (
-
Enhanced Logging: For production, consider using a structured logging library like
winstonorpinoinstead ofconsole.log. This enables better log parsing, filtering, and integration with log management systems.bashnpm install winstonjavascript// Example using Winston (replace console.log calls) const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ // - Write all logs with importance level of `error` or less to `error.log` // - Write all logs with importance level of `info` or less to `combined.log` new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), ], }); // If we're not in production then log to the `console` if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple(), })); } // Replace console.log('...') with logger.info('...') // Replace console.error('...') with logger.error('...') // --- Example Usage --- // logger.info('Server started'); // logger.error('Failed to send SMS', { error: err.message });- Placement: This logger setup code should typically be placed early in your application startup, for instance, near the top of your
app.jsfile after requiring modules, or encapsulated within a separate logging module (logger.js) that you import where needed.
- Placement: This logger setup code should typically be placed early in your application startup, for instance, near the top of your
-
Retry Mechanisms: Vonage automatically retries sending webhook notifications if it doesn't receive a
200 OK. For outgoing SMS failures (e.g., network issues calling the Vonage API), you might implement your own retry logic within thesendSmsfunction'scatchblock, potentially using libraries likeasync-retrywith exponential backoff.
7. Security Considerations
-
Environment Variables: Never commit your
.envfile or hardcode credentials (API Key,Secret,Private Key) directly in your code. Use environment variables managed securely in your deployment environment. -
Webhook Security (Signature Verification): Vonage can sign its webhook requests, allowing you to verify they genuinely came from Vonage. This prevents attackers from sending fake requests to your endpoints. Implementing signature verification (using JWT or Signed Webhooks based on Vonage settings) is highly recommended for production. Refer to the Vonage Webhook Security Documentation for implementation details using the SDK. The
@vonage/server-sdkoften includes helper functions or utilities to simplify this verification process; consult the SDK's documentation for specific methods related to webhook signature validation. -
Input Validation: Sanitize and validate all input, especially the
textfrom incoming SMS messages, if you plan to store it, display it, or use it in further processing (e.g., database queries) to prevent injection attacks. Libraries likeexpress-validatorcan help. -
Rate Limiting: Protect your
/send-smsendpoint (if publicly exposed) and potentially your webhook endpoints from abuse by implementing rate limiting using middleware likeexpress-rate-limit. -
Private Key Security: Treat your
private.keyfile like any other secret. Ensure its file permissions are restricted and it's stored securely on your server.
8. Testing Your SMS Application
Comprehensive testing ensures your application works reliably.
-
Manual Testing (as performed above):
- Use
curl/Postman to test the/send-smsendpoint. Verify SMS delivery. - Send SMS to your Vonage number. Verify logs and the auto-reply.
- Use
-
Automated Testing (Unit/Integration):
- Unit Tests: Use frameworks like Jest or Mocha/Chai to test individual functions (e.g., the logic inside your webhook handlers, input validation) in isolation. Mock the Vonage SDK to avoid actual API calls during unit tests.
- Integration Tests: Test the interaction between your API endpoints, the Vonage SDK (potentially mocked or hitting a Vonage sandbox if available), and your internal logic. Tools like
supertestare excellent for testing Express routes.
Before writing tests that require the Express app instance, ensure you export it from your main application file (
app.js). Add the following line at the bottom ofapp.js:javascriptmodule.exports = app;bash# Install testing dependencies npm install --save-dev jest supertestExample Jest/Supertest Integration Test (
app.test.js):javascript// app.test.js const request = require('supertest'); // Assuming your app.js exports the express app instance (see instruction above) const app = require('./app'); // Adjust path if needed // Mock the Vonage SDK to avoid real API calls jest.mock('@vonage/server-sdk', () => { const mockMessages = { send: jest.fn().mockResolvedValue({ message_uuid: 'mock-uuid-12345' }) }; return { Vonage: jest.fn().mockImplementation(() => ({ messages: mockMessages })) }; }); describe('SMS Endpoints', () => { it('should return 400 if "to" is missing for /send-sms', async () => { const res = await request(app) .post('/send-sms') .send({ text: 'Test message' }); expect(res.statusCode).toEqual(400); expect(res.body.error).toContain('Missing required fields'); }); it('should return 200 and mock UUID for valid /send-sms', async () => { const res = await request(app) .post('/send-sms') .send({ to: '15551234567', text: 'Test message' }); expect(res.statusCode).toEqual(200); expect(res.body.message_uuid).toEqual('mock-uuid-12345'); }); it('should return 200 for /webhooks/inbound POST', async () => { const res = await request(app) .post('/webhooks/inbound') .send({ // Simulate Vonage inbound payload channel: 'sms', message_type: 'text', from: '15559876543', to: process.env.VONAGE_NUMBER || '18885551212', // Use env var or a default test number text: 'Hello inbound test' }); expect(res.statusCode).toEqual(200); // You could add more assertions here to check if the mocked sendSms was called }); it('should return 200 for /webhooks/status POST', async () => { const res = await request(app) .post('/webhooks/status') .send({ message_uuid: 'some-uuid', status: 'delivered' }); expect(res.statusCode).toEqual(200); }); });Add to
package.jsonscripts:json"scripts": { "start": "node app.js", "test": "jest" }Run tests:
npm test
9. Deployment
Deploying requires moving your app from your local machine to a server accessible on the internet.
-
Choose a Hosting Provider: Options include PaaS (Heroku, Render, Fly.io), IaaS (AWS EC2, Google Compute Engine, DigitalOcean Droplets), or Serverless platforms.
-
Environment Variables: Configure your production environment variables (
VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_ID,VONAGE_PRIVATE_KEY_PATH,VONAGE_NUMBER,PORT,NODE_ENV=production) securely using your hosting provider's interface. Do not commit.envto production. -
Private Key: Securely transfer your
private.keyfile to the production server or use a secrets management service. Ensure theVONAGE_PRIVATE_KEY_PATHenvironment variable points to the correct location on the server. -
Update Vonage Webhooks: Once your application is deployed and has a public URL (e.g.,
https://your-app-name.herokuapp.com), update the Inbound URL and Status URL in your Vonage Application settings to point to your production endpoints (e.g.,https://your-app-name.herokuapp.com/webhooks/inbound). Remove the ngrok URLs. -
Build/Deployment Process: Follow your hosting provider's instructions. This typically involves pushing your code to Git, connecting the repository to the provider, and triggering a build/deploy. Ensure
npm install --omit=dev(or similar command) is run in production to avoid installing development dependencies. -
Process Manager: Use a process manager like
pm2or your platform's built-in mechanism (e.g., Heroku Dynos) to keep your Node.js application running reliably and restart it if it crashes.bash# Install pm2 globally on the server (if needed) npm install pm2 -g # Start your app pm2 start app.js --name vonage-sms-app # Monitor pm2 list pm2 logs vonage-sms-app -
CI/CD (Optional but Recommended): Set up a Continuous Integration/Continuous Deployment pipeline (using GitHub Actions, GitLab CI, Jenkins, etc.) to automatically test and deploy your application whenever you push changes to your repository.
10. Troubleshooting and Caveats
-
SMS Not Sending:
- Check Node console logs for errors from the Vonage SDK.
- Verify API Key/Secret in
.envare correct. - Ensure the
tonumber is in valid E.164 format. - Check your Vonage account balance.
- Confirm the
fromnumber (VONAGE_NUMBER) is correct and SMS-capable. - Look for error details in the
err.response.dataif logged.
-
Inbound Webhooks Not Triggering:
- Verify the Inbound URL in Vonage Application settings is exactly correct (HTTPS, correct path
/webhooks/inbound, no typos). - Ensure your deployed application is running and accessible at the webhook URL. Check server logs.
- If using ngrok locally: Ensure ngrok is running and forwarding to the correct local port (
3000). Ensure the ngrok URL in Vonage is the current active one (it changes each time you restart ngrok unless you have a paid plan with a static domain). Check the ngrok web interface (http://127.0.0.1:4040) for requests. - Verify the Vonage number is correctly linked to the Vonage Application.
- Check the Vonage Dashboard's API Logs (Logs -> API logs) for errors related to webhook delivery attempts.
- Verify the Inbound URL in Vonage Application settings is exactly correct (HTTPS, correct path
-
Receiving Multiple Inbound Messages (Retries):
- This happens if your webhook doesn't respond with
200 OKquickly enough. Ensure your/webhooks/inboundhandler returnsres.status(200).end()immediately, even if background processing continues. Offload long-running tasks to a background job queue if necessary.
- This happens if your webhook doesn't respond with
-
Incorrect Default API: If webhooks have unexpected formats, double-check that the Messages API is set as the default SMS setting in your Vonage account API settings.
Related Resources
Learn more about SMS messaging with Node.js:
Frequently Asked Questions
How to send SMS messages with Node.js and Express
Use the Vonage Messages API and the @vonage/server-sdk. Create an API endpoint in your Express app that takes the recipient's number and message text, then uses the Vonage SDK to send the SMS. Ensure your Vonage number is linked to your Vonage application.
What is the Vonage Messages API?
The Vonage Messages API is a unified API for sending and receiving messages across multiple channels, including SMS. It simplifies communication by providing a single interface for various messaging types.
Why does Vonage need a webhook for inbound SMS?
Vonage uses webhooks to deliver inbound SMS messages to your application in real-time. When someone sends an SMS to your Vonage number, Vonage sends an HTTP POST request to your configured webhook URL with the message details.
When should I use ngrok for Vonage webhooks?
Use ngrok during local development to create a publicly accessible URL that Vonage can use to reach your webhook endpoints. For production, replace the ngrok URL with your deployed application's public URL.
Can I test Vonage SMS sending locally?
Yes, you can test sending SMS locally using the Vonage SDK and your API credentials. Use tools like curl or Postman to make requests to your local Express app's /send-sms endpoint. Replace placeholder numbers with your own for testing.
How to receive SMS messages with Node.js and Express
Set up webhook endpoints in your Express app (e.g., /webhooks/inbound) and configure these URLs in your Vonage Application settings. When an SMS is sent to your Vonage number, Vonage will send an HTTP POST request to your webhook with the message data.
What is a Vonage Application ID?
The Vonage Application ID is a unique identifier for your Vonage application. It's generated when you create an application in the Vonage Dashboard and used to associate your Vonage number and webhook URLs with your application.
Why use dotenv in a Node.js project?
Dotenv is a module that loads environment variables from a .env file into process.env. This is a best practice for managing sensitive credentials (like API keys) and configuration, keeping them out of your codebase.
How to set up two-way SMS messaging with Node.js and Vonage
Combine the send and receive functionalities. In your inbound webhook handler, extract the sender's number and message content. Then, use the Vonage SDK to send an SMS reply back to the sender.
What is the purpose of the Vonage private key?
The Vonage private key, generated along with your Application ID, is used for more secure authentication in certain Vonage API interactions, particularly when using JWT for webhook signatures. This key should be kept confidential and loaded via environment variable from the .env file.
When should I implement webhook signature verification?
Webhook signature verification is highly recommended for production applications. It ensures that webhook requests are genuinely coming from Vonage and prevents malicious actors from spoofing requests. Verify the signatures in your webhook endpoints using JWT (JSON Web Tokens).