Send SMS with Node.js, Express, and Vonage
This guide provides a complete walkthrough for building a Node.js application using the Express framework to send SMS messages via the Vonage Messages API. We will cover everything from initial project setup and Vonage configuration to implementing the core sending functionality, handling errors, adding security measures, and preparing for deployment.
By the end of this tutorial, you will have a functional Express API endpoint capable of accepting a phone number and message text, and using Vonage to deliver the SMS. This serves as a foundational building block for applications requiring SMS notifications, alerts, or user communication features.
Project Overview and Goals
Goal: To create a simple, robust Node.js Express API that sends outbound SMS messages using the Vonage Messages API.
Problem Solved: Provides a programmatic way to send SMS messages, enabling applications to communicate directly with users via text without manual intervention.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its asynchronous nature, large ecosystem (npm), and suitability for I/O-bound tasks like API interactions.
- Express: A minimal and flexible Node.js web application framework. Chosen for its simplicity in setting up API endpoints and handling HTTP requests.
- Vonage Messages API: A unified API from Vonage for sending messages across various channels (SMS, MMS, WhatsApp, etc.). Chosen for its reliability, global reach, and the specific requirement of sending SMS. We will use the
`@vonage/server-sdk`
Node.js library. dotenv
: A zero-dependency module that loads environment variables from a.env
file intoprocess.env
. Chosen for securely managing API credentials and configuration outside the codebase.ngrok
(for setup step only): A tool to expose local servers to the internet. While primarily used for receiving webhooks, it's required only during the one-time Vonage Application setup step in this guide, which mandates publicly accessible webhook URLs even though we don't implement webhook receiving logic here.
System Architecture:
+-------------+ +-----------------+ +-------------+ +--------------+
| User / App | ----> | Node.js/Express | ----> | Vonage API | ----> | Mobile Phone |
| (Client) | | (Your Server) | | (Messages) | | (Recipient) |
+-------------+ +-----------------+ +-------------+ +--------------+
| ^ | |
| POST /send-sms | | API Call | SMS Delivery
| {to, text} | | (App ID, Priv Key) |
| | V |
+---------------------+ +----------------------+
Loads Credentials
from .env
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. You can download it from nodejs.org.
- Vonage API Account: Sign up for free at Vonage API Dashboard. New accounts receive free credit for testing.
- Vonage Phone Number: Rent a virtual phone number from your Vonage dashboard capable of sending SMS.
ngrok
: Install ngrok from ngrok.com and authenticate your client (a free account is sufficient).- Basic understanding of Node.js, Express, APIs, and terminal commands.
1. Setting up the project
Let's initialize the Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal or command prompt and create a new directory for your project, then navigate into it.
mkdir vonage-sms-sender cd vonage-sms-sender
-
Initialize Node.js Project: Create a
package.json
file to manage project dependencies and scripts. The-y
flag accepts default settings.npm init -y
-
Install Dependencies: Install Express for the web server, the Vonage Server SDK for interacting with the API, and
dotenv
for managing environment variables.npm install express @vonage/server-sdk dotenv
-
Create
.gitignore
: Create a file named.gitignore
in the root of your project. This prevents sensitive files and unnecessary directories from being committed to version control (like Git). Add the following lines:# .gitignore # Node dependencies node_modules/ # Environment variables .env # Private keys *.key private.key # Log files *.log # Operating system files .DS_Store Thumbs.db
Why
.gitignore
? It's crucial for security (keeping.env
and private keys out of repositories) and maintaining a clean project structure. -
Create
.env
File: Create a file named.env
in the root of your project. This file will store your Vonage credentials and other configuration details. Leave it empty for now; we will populate it in the next section. -
Project Structure: Your basic project structure should now look like this:
vonage-sms-sender/ ├── .env ├── .gitignore ├── node_modules/ ├── package-lock.json └── package.json
We will add our application code file (
index.js
) later.
2. Integrating with Vonage
Before writing code, we need to configure our Vonage account and application to enable SMS sending via the Messages API.
-
Log in to Vonage: Access your Vonage API Dashboard.
-
Locate API Key and Secret: On the main dashboard page, you'll find your API Key and API Secret. Note these down securely, although for the Messages API sending method we'll primarily use the Application ID and Private Key generated next.
-
Set Default SMS API (Crucial):
- Navigate to your account Settings.
- Scroll down to the API keys section, then find SMS settings.
- Ensure that the default API for sending SMS is set to Messages API. If it's set to ""SMS API"", toggle it to ""Messages API"".
- Click Save changes.
- Why? Vonage has two SMS APIs (SMS and Messages). The SDK and webhook formats differ. This guide uses the Messages API.
-
Run
ngrok
: We need a temporary public URL for the Vonage Application setup. Open a new terminal window, navigate to where you installedngrok
, and run:ngrok http 3000
(Assuming our Express server will run on port 3000).
ngrok
will display a forwarding URL (e.g.,https://<unique-id>.ngrok.io
). Copy thehttps
version of this URL. Keep this terminal window running. -
Create a Vonage Application:
- In the Vonage Dashboard, navigate to Applications > Create a new application.
- Give your application a name (e.g., ""Node SMS Sender"").
- Click Generate public and private key. This will automatically download a file named
private.key
. Save this file securely inside your project directory (e.g., in the rootvonage-sms-sender/
folder). Do not commit this file to Git. The public key is stored by Vonage. - Enable the Messages capability.
- Enter the following URLs, replacing
<your-ngrok-url>
with thehttps
URL you copied fromngrok
:- Inbound URL:
<your-ngrok-url>/webhooks/inbound
(Method:POST
) - Status URL:
<your-ngrok-url>/webhooks/status
(Method:POST
) - Why Status URL? This URL receives delivery receipts and status updates about messages you send. Even if you don't actively process them in this basic guide, configuring it is good practice and required by the platform.
- Inbound URL:
- Click Generate new application.
-
Get Application ID: After creating the application, you'll be taken to its configuration page. Copy the Application ID. It's a UUID string.
-
Link Your Vonage Number:
- Scroll down to the Link virtual numbers section on the application's page.
- Find the Vonage virtual number you rented earlier and click the Link button next to it.
- Why link? The Messages API requires sending SMS from a number associated with the Application ID being used for authentication.
-
Populate
.env
File: Now, open the.env
file you created earlier and add your credentials and configuration:# .env # Vonage Credentials VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID_HERE VONAGE_PRIVATE_KEY_PATH=./private.key VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER_HERE # Server Configuration PORT=3000
- Replace
YOUR_APPLICATION_ID_HERE
with the Application ID you copied. - Ensure
VONAGE_PRIVATE_KEY_PATH
points to the correct path where you saved theprivate.key
file relative to your project root. Using a relative path like./private.key
works but can be fragile depending on where the application is run from. - Replace
YOUR_VONAGE_VIRTUAL_NUMBER_HERE
with the Vonage virtual number you linked to the application (use E.164 format, e.g.,`14155550100`
). PORT=3000
matches the port we exposed withngrok
and will use for our Express server.
Explanation of Variables:
VONAGE_APPLICATION_ID
: Identifies your specific Vonage application.VONAGE_PRIVATE_KEY_PATH
: Path to the private key file used for authenticating API requests with the Application ID. (See Section 12 for a more robust alternative using environment variables for the key content, especially recommended for deployment).VONAGE_NUMBER
: The sender ID (your virtual number) for outgoing SMS messages.PORT
: The port your Express application will listen on.
- Replace
3. Implementing Core Functionality (Sending SMS)
Now let's write the Node.js code to create the Express server and the SMS sending logic.
-
Create
index.js
: Create a file namedindex.js
in the root of your project directory. -
Add Boilerplate and Initialization: Open
index.js
and add the following code to import modules, load environment variables, initialize Express, and set up the Vonage client:// index.js 'use strict'; require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const { Vonage } = require('@vonage/server-sdk'); // --- Basic Input Validation --- // Simple check for required environment variables if (!process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_PRIVATE_KEY_PATH || !process.env.VONAGE_NUMBER) { console.error('__ Error: Missing required Vonage environment variables (VONAGE_APPLICATION_ID, VONAGE_PRIVATE_KEY_PATH, VONAGE_NUMBER).'); console.error('Please check your .env file.'); process.exit(1); // Exit if essential config is missing } // --- Initialize Vonage Client --- // Note: For production/deployment, consider loading the private key content // from an environment variable instead of a file path for better security and flexibility. // See Section 12 for details. let vonage; try { vonage = new Vonage({ applicationId: process.env.VONAGE_APPLICATION_ID, privateKey: process.env.VONAGE_PRIVATE_KEY_PATH // Use the path directly }); } catch (err) { console.error('__ Error: Failed to initialize Vonage SDK. Check VONAGE_PRIVATE_KEY_PATH and file permissions.', err); process.exit(1); } // --- Initialize Express App --- const app = express(); app.use(express.json()); // Middleware to parse JSON request bodies app.use(express.urlencoded({ extended: true })); // Middleware to parse URL-encoded request bodies const port = process.env.PORT || 3000; // Use port from .env or default to 3000 // --- Placeholder for API Endpoint (added in next step) --- // --- Start Server --- const server = app.listen(port, () => { console.log(`__ Server listening at http://localhost:${port}`); }); // Export app and server for testing purposes (see Section 13) module.exports = { app, server };
Why
'use strict';
? Enforces stricter parsing and error handling in JavaScript. Whyrequire('dotenv').config();
first? Ensures environment variables are loaded before any other code tries to access them. Why initialize Vonage SDK outside the endpoint? Avoids re-initializing the SDK on every request, which is inefficient. Added a try-catch around initialization for robustness.
4. Building the API Layer
Let's create the /send-sms
endpoint that will receive requests and trigger the SMS sending process.
-
Add POST Endpoint: In
index.js
, below the// --- Placeholder for API Endpoint ---
comment, add the following code for the/send-sms
route handler:// index.js (continued) // --- API Endpoint to Send SMS --- app.post('/send-sms', async (req, res) => { console.log(`Received POST request to /send-sms`); console.log('Request Body:', req.body); // --- Request Validation --- const { to, text } = req.body; // Destructure recipient number and message text if (!to || !text) { console.error(`__ Validation Error: Missing 'to' or 'text' in request body.`); return res.status(400).json({ success: false, error: 'Missing required fields: `to` (recipient phone number) and `text` (message content).' }); } // Basic check for E.164 format (starts with +, followed by digits) // More robust validation might be needed in production. if (!/^\+[1-9]\d{1,14}$/.test(to)) { console.error(`__ Validation Error: Invalid phone number format for 'to': ${to}`); return res.status(400).json({ success: false, error: 'Invalid phone number format. Please use E.164 format (e.g., +14155550100).' }); } const fromNumber = process.env.VONAGE_NUMBER; // Get sender number from .env console.log(`Attempting to send SMS from ${fromNumber} to ${to}`); // --- Call Vonage API --- try { const resp = await vonage.messages.send({ message_type: 'text', text: text, to: to, from: fromNumber, channel: 'sms' }); console.log('_ Vonage API Success:', resp); // Log the success response from Vonage // --- Send Success Response --- res.status(200).json({ success: true, message: `SMS sent successfully to ${to}.`, message_uuid: resp.message_uuid }); } catch (err) { // --- Handle Vonage API Errors --- console.error('__ Vonage API Error:', err); // Log the detailed error from Vonage SDK // Provide more context if available let errorMessage = 'Failed to send SMS due to an internal error.'; let statusCode = 500; if (err.response && err.response.data) { console.error(' Error Details:', err.response.data); // Use specific details from Vonage response if possible errorMessage = `Vonage API Error: ${err.response.data.title || 'Unknown error'} (${err.response.data.type || 'N/A'}). ${err.response.data.detail || ''}`; // Use status code from Vonage if available and relevant (e.g., 4xx for client errors) if (err.response.status >= 400 && err.response.status < 500) { statusCode = err.response.status; } } else if (err.message) { // Fallback to the general error message from the SDK/Error object errorMessage = `Error: ${err.message}`; } res.status(statusCode).json({ success: false_ error: errorMessage }); } }); // --- Basic Health Check Endpoint --- app.get('/health'_ (req_ res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // --- (Keep the app.listen and module.exports part from the previous step) ---
Why
async/await
? Thevonage.messages.send()
method returns a Promise, makingasync/await
a clean way to handle the asynchronous API call. Whytry...catch
? Essential for handling potential errors during the API call (network issues, invalid credentials, Vonage service errors, etc.). Whyexpress.json()
middleware? Allows Express to automatically parse the incoming JSON request body (req.body
). Whyvonage.messages.send()
? This is the method from the Vonage Node.js SDK specifically for using the Messages API. We specify thechannel
assms
, themessage_type
astext
, and provideto
,from
, andtext
. Why E.164 validation? Vonage expects phone numbers in E.164 format (e.g.,`+14155550100`
). Basic validation helps catch errors early. Why backticks forto
andtext
in error messages? Improves clarity by indicating these are field names. -
Test the Endpoint:
-
Ensure your
ngrok
tunnel (from Step 2.4) is still running. -
Start your Node.js server in the first terminal window:
node index.js
You should see
__ Server listening at http://localhost:3000
. -
Open a new terminal window (or use a tool like Postman) and send a POST request using
curl
:curl -X POST http://localhost:3000/send-sms \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""+1xxxxxxxxxx"", ""text"": ""Hello from Node.js and Vonage!"" }'
- Replace
`+1xxxxxxxxxx`
with a valid test phone number (see Troubleshooting section about whitelisting for trial accounts). - You can also use your actual mobile number if it's whitelisted or if you have upgraded your Vonage account.
- Replace
-
Expected
curl
Response (Success):{ ""success"": true, ""message"": ""SMS sent successfully to +1xxxxxxxxxx."", ""message_uuid"": ""aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"" }
(The
message_uuid
will be unique) -
Expected
curl
Response (Validation Error):{ ""success"": false, ""error"": ""Missing required fields: `to` (recipient phone number) and `text` (message content)."" }
-
Check Terminal: Observe the logs in the terminal where
node index.js
is running. You should see the request body and the success or error response from the Vonage API. -
Check Phone: You should receive the SMS on the destination phone number shortly after a successful API call.
-
5. Implementing Proper Error Handling and Logging
We've already added basic try...catch
blocks and console.log
/console.error
. Let's refine this.
-
Consistent Strategy: Our current strategy is:
- Validate inputs early and return
400 Bad Request
. - Use
try...catch
around the Vonage API call. - Log detailed errors server-side using
console.error
. - Return a structured JSON error response to the client (
`{ success: false, error: '...' }`
) with an appropriate HTTP status code (e.g.,400
for validation,500
for server/Vonage errors, or specific Vonage4xx
codes if applicable).
- Validate inputs early and return
-
Logging Levels:
console.log
is used for informational messages (request received, attempt starting), andconsole.error
is used for actual errors (validation failures, API exceptions). For production, consider using a dedicated logging library like Winston or Pino for features like:- Different log levels (debug, info, warn, error).
- Structured logging (JSON format).
- Writing logs to files or external services.
- Example (Conceptual with Winston):
// // Example setup (replace console.log/error) // const winston = require('winston'); // const logger = winston.createLogger({ // level: 'info', // format: winston.format.json(), // transports: [ // new winston.transports.Console(), // // new winston.transports.File({ filename: 'error.log', level: 'error' }), // // new winston.transports.File({ filename: 'combined.log' }), // ], // }); // // Usage: logger.info('Message sent'); logger.error('API failed', err);
-
Retry Mechanisms: For transient network errors or temporary Vonage issues, a retry strategy can improve reliability. Libraries like
async-retry
can simplify this.- Example (Conceptual):
// const retry = require('async-retry'); // // Inside the endpoint, wrap the vonage call: // try { // const resp = await retry(async bail => { // // If vonage.messages.send throws an error that shouldn't be retried (e.g., 4xx), call bail(err) // // Otherwise, just let it throw, and async-retry will retry // const vonageResp = await vonage.messages.send({...}); // // Check vonageResp for specific non-retryable conditions if needed // return vonageResp; // }, { // retries: 3, // Number of retries // factor: 2, // Exponential backoff factor // minTimeout: 1000, // Minimum delay ms // onRetry: (error, attempt) => { // console.warn(`Retrying Vonage API call (attempt ${attempt}) due to error: ${error.message}`); // } // }); // // ... rest of success handling ... // } catch (err) { // // ... error handling ... // }
- Be cautious: Don't retry on errors caused by bad input (
4xx
errors) or non-recoverable issues. Focus retries on potential network flakes or temporary server errors (often5xx
).
- Example (Conceptual):
-
Testing Error Scenarios:
- Send requests missing
to
ortext
to test validation. - Temporarily modify
.env
with incorrectVONAGE_APPLICATION_ID
orVONAGE_PRIVATE_KEY_PATH
to test authentication errors. - Send to a non-whitelisted number (if using a trial account) to test that specific error.
- Simulate network issues (e.g., disconnect Wi-Fi briefly) if testing retry logic.
- Send requests missing
6. Creating a Database Schema and Data Layer
Not Applicable: This guide focuses solely on the stateless act of sending an outbound SMS via an API call. There is no data persistence required for this core functionality.
If you were building a more complex application (e.g., tracking message history, managing contacts, scheduling messages), you would introduce a database (like PostgreSQL, MySQL, MongoDB) and a data layer (using an ORM like Prisma or Sequelize, or native drivers) here. This would involve:
- Defining database schemas (e.g., a
messages
table with columns likemessage_uuid
,to_number
,from_number
,text
,status
,submitted_at
,delivered_at
). - Writing data access functions (e.g.,
saveMessageRecord
,updateMessageStatus
). - Setting up database migrations.
7. Adding Security Features
Security is paramount, especially when dealing with APIs and credentials.
- Environment Variables (Revisited): Never hardcode API keys, secrets, application IDs, or private key paths directly in your code. Use environment variables loaded via
dotenv
and ensure.env
andprivate.key
are in your.gitignore
. - Input Validation (Revisited): We added basic validation for
to
andtext
. For production, consider more robust validation libraries likejoi
orexpress-validator
to enforce types, formats (E.164), length limits, and potentially sanitize inputs. Note: Sanitizing SMS text (e.g., escaping HTML) is generally not needed and can corrupt the message content; focus on validation.- Example (Conceptual with
express-validator
):// const { body, validationResult } = require('express-validator'); // // Add middleware to the route: // app.post('/send-sms', // // Validates E.164 format (e.g., +14155550100) when strictMode is true. // // 'any' allows any locale, adjust if needed. // body('to').isMobilePhone('any', { strictMode: true }).withMessage('Invalid E.164 phone number format required.'), // // Ensure text is a non-empty string and trim whitespace. Avoid escape() for SMS. // body('text').notEmpty().isString().trim(), // async (req, res) => { // const errors = validationResult(req); // if (!errors.isEmpty()) { // return res.status(400).json({ success: false, errors: errors.array() }); // } // // ... rest of the handler ... // } // );
- Example (Conceptual with
- Rate Limiting: Protect your API endpoint and Vonage account from abuse (accidental or malicious) by limiting the number of requests a client can make in a given time window. Use middleware like
express-rate-limit
.- Add to
index.js
:// index.js (near the top, after require statements) const rateLimit = require('express-rate-limit'); // --- Rate Limiting --- 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, error: 'Too many requests, please try again after 15 minutes.' } }); // --- Initialize Express App --- (before routes) const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Apply the rate limiting middleware to the SMS sending endpoint app.use('/send-sms', limiter); // --- (rest of app setup and routes)
- Add to
- HTTPS: Always use HTTPS in production to encrypt data in transit. This is typically handled by your deployment platform (e.g., Heroku, Vercel) or a reverse proxy (like Nginx) sitting in front of your Node.js application.
- Private Key Security: The
private.key
file is highly sensitive. Ensure its file permissions restrict access only to the user running the Node.js application (e.g.,`chmod 400 private.key`
). Do not expose it publicly.
8. Handling Special Cases
- International Numbers: The E.164 format (
+
followed by country code and number) is designed for global use. Ensure users provide numbers in this format. - Character Limits & Encoding: Standard SMS messages have character limits (160 for GSM-7 encoding, fewer for Unicode). Longer messages might be split into multiple segments (concatenated SMS), potentially incurring higher costs. Vonage generally handles concatenation, but be mindful of message length. Ensure your text uses appropriate character sets; Vonage handles most common encodings.
- Sender ID: In some countries, the
from
number might be replaced by a generic ID or an alphanumeric sender ID (if pre-registered and supported). This behavior varies by destination country and carrier regulations. Check Vonage's country-specific guidelines if this is critical. - Invalid
to
Numbers: The API call might succeed even if theto
number is technically valid (E.164 format) but doesn't actually exist or cannot receive SMS. You might only find out through the Status URL webhook delivering a""failed""
or""rejected""
status later (if you implement webhook handling).
9. Implementing Performance Optimizations
For this simple application, major optimizations aren't usually necessary, but good practices include:
- SDK Initialization: As done, initialize the Vonage SDK once outside the request handler.
- Asynchronous Operations: Node.js and the Vonage SDK are inherently asynchronous. Using
async/await
correctly prevents blocking the event loop. - Resource Usage: Keep request handlers lightweight. Offload any heavy processing (if added later) to background jobs if possible.
- Load Testing: For high-throughput scenarios, use tools like
k6
orautocannon
to simulate traffic and identify bottlenecks.# Example using autocannon (install: npm install -g autocannon) # Replace +1xxxxxxxxxx with a valid (potentially test/whitelisted) number autocannon -m POST -H ""Content-Type=application/json"" -b '{""to"":""+1xxxxxxxxxx"", ""text"":""Load test""}' http://localhost:3000/send-sms
- Caching: Not applicable here, but in other scenarios, caching frequently accessed data (that doesn't change often) can reduce load.
10. Adding Monitoring, Observability, and Analytics
For production readiness, monitoring is crucial.
- Health Checks: We added a basic
/health
endpoint. Monitoring services can ping this endpoint to ensure the application is running. - Logging (Revisited): Implement structured logging (e.g., JSON) and forward logs to a centralized logging platform (e.g., Datadog, Logz.io, ELK stack). This enables searching, analysis, and alerting based on log patterns.
- Performance Metrics: Monitor key Node.js metrics (event loop lag, CPU usage, memory usage) and application-specific metrics (request latency, error rates for the
/send-sms
endpoint). Tools like PM2 provide basic monitoring, while APM (Application Performance Monitoring) solutions (Datadog APM, New Relic, Dynatrace) offer deeper insights. - Error Tracking: Integrate an error tracking service (e.g., Sentry, Bugsnag) to automatically capture, aggregate, and alert on unhandled exceptions and significant errors. These often provide more context than plain logs.
// // Example Sentry Setup (Conceptual - requires npm install @sentry/node @sentry/tracing) // const Sentry = require('@sentry/node'); // const Tracing = require('@sentry/tracing'); // If using tracing // Sentry.init({ // dsn: 'YOUR_SENTRY_DSN', // integrations: [ // // enable HTTP calls tracing // new Sentry.Integrations.Http({ tracing: true }), // // enable Express.js middleware tracing // new Tracing.Integrations.Express({ app }), // ], // tracesSampleRate: 1.0, // Adjust in production // }); // // Sentry Request Handler - Must be the first middleware // app.use(Sentry.Handlers.requestHandler()); // // TracingHandler creates a trace for every incoming request // app.use(Sentry.Handlers.tracingHandler()); // // ... your routes (/send-sms, /health) ... // // Sentry Error Handler - Must be before any other error middleware and after all controllers // app.use(Sentry.Handlers.errorHandler()); // // Optional fallthrough error handler // app.use(function onError(err, req, res, next) { // // The error id is attached to `res.sentry` to be returned // // and optionally displayed to the user for support. // res.statusCode = 500; // res.end(res.sentry + '\n'); // });
- Vonage Dashboard: Utilize the Vonage dashboard to monitor message delivery rates, costs, and logs specific to Vonage interactions. Configure alerts within Vonage if needed.
11. Troubleshooting and Caveats
- Error: Non-Whitelisted Destination:
- Meaning: Your Vonage account is likely in trial/demo mode, and you tried sending an SMS to a phone number not registered and verified in your Vonage dashboard's
""Test Numbers""
section. - Solution: Go to your Vonage Dashboard -> Numbers -> Test Numbers. Add the recipient's phone number and verify it using the process provided.
- Meaning: Your Vonage account is likely in trial/demo mode, and you tried sending an SMS to a phone number not registered and verified in your Vonage dashboard's