Send SMS with Node.js, Express, and Vonage
This guide provides a complete walkthrough for building a simple Node.js application using the Express framework to send SMS messages via the Vonage API. We will cover project setup, Vonage integration, building an API endpoint, basic security, and testing.
By the end of this tutorial, you will have a functional Express server with a single API endpoint that accepts a destination phone number and a message, then uses the Vonage Node.js SDK to send an SMS. This serves as a foundational building block for applications requiring SMS notification or communication features.
Project Overview and Goals
- Goal: Create a simple web service that can send SMS messages on demand via an HTTP request.
- Problem Solved: Provides a programmatic way to send SMS notifications or messages without manual intervention, suitable for alerts, verification codes, or basic communication.
- Technologies:
- Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its asynchronous nature and large package ecosystem (npm).
- Express: A minimal and flexible Node.js web application framework. Chosen for its simplicity in setting up API endpoints.
- Vonage API: A communications platform-as-a-service (CPaaS) providing APIs for SMS, voice, video, and more. Chosen for its robust SMS capabilities and developer-friendly SDK.
- Vonage Node.js SDK (
@vonage/server-sdk
): Simplifies interaction with the Vonage APIs from a Node.js application.
- Architecture: A client (like
curl
or a frontend application) sends a POST request to our Express server. The Express server validates the request, uses the Vonage SDK (authenticated with API credentials) to interact with the Vonage API, which then delivers the SMS to the recipient's phone. - Prerequisites:
- Node.js and npm: Installed on your system. You can download them from nodejs.org.
- Vonage API Account: Sign up for free at Vonage API Dashboard. New accounts receive free credits for testing.
- Vonage API Key and Secret: Found on the homepage of your Vonage API Dashboard after signing up.
- Vonage Virtual Number: You need a Vonage phone number to send SMS messages from. You can purchase one in the Vonage Dashboard (
Numbers
>Buy numbers
). Ensure the number is SMS-capable in your target country. - Test Phone Number(s): A mobile phone number capable of receiving SMS messages for testing purposes. Note Vonage trial account limitations below.
1. Setting up the project
Let's create the project directory, initialize Node.js, and install the necessary dependencies.
-
Create Project Directory: Open your terminal or command prompt and run:
mkdir vonage-sms-sender cd vonage-sms-sender
-
Initialize Node.js Project: This creates a
package.json
file to manage dependencies and project metadata.npm init -y
-
Install Dependencies: We need Express for the web server, the Vonage SDK to interact with the API, and
dotenv
to manage environment variables securely.npm install express @vonage/server-sdk dotenv
express
: The web framework.@vonage/server-sdk
: The official Vonage library for Node.js.dotenv
: Loads environment variables from a.env
file intoprocess.env
.
-
Configure
package.json
for ES Modules: We'll use modern JavaScriptimport
/export
syntax. Open yourpackage.json
file and add the""type"": ""module""
line:{ ""name"": ""vonage-sms-sender"", ""version"": ""1.0.0"", ""description"": """", ""main"": ""index.js"", ""type"": ""module"", ""scripts"": { ""start"": ""node index.js"", ""test"": ""echo \""Error: no test specified\"" && exit 1"" }, ""keywords"": [], ""author"": """", ""license"": ""ISC"", ""dependencies"": { ""@vonage/server-sdk"": ""^3.x.x"", ""dotenv"": ""^16.x.x"", ""express"": ""^4.x.x"" } }
-
Create Environment File (
.env
): This file will store your sensitive API credentials and configuration. Never commit this file to version control. Create a file named.env
in the root of your project directory:# .env VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET VONAGE_FROM_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Server configuration (optional) PORT=3000
- Important: Replace
YOUR_API_KEY
andYOUR_API_SECRET
with the actual credentials from your Vonage dashboard. - Important: Replace
YOUR_VONAGE_VIRTUAL_NUMBER
with the SMS-capable Vonage number you acquired (use E.164 format, typically without the leading '+', e.g.,15551234567
- check SDK requirements if unsure). PORT
: The port your Express server will listen on (defaulting to 3000).
- Important: Replace
-
Create
.gitignore
File: To prevent accidentally committing sensitive information and unnecessary files, create a.gitignore
file in the root of your project:# .gitignore # Dependencies node_modules # Environment variables .env # Logs *.log # Optional OS generated files .DS_Store Thumbs.db
-
Create Server File (
index.js
): Create the main file for your application,index.js
, in the project root. We will populate this in the next steps.
2. Integrating with Vonage
Now, let's configure the Vonage SDK within our application.
-
Vonage Account Setup:
- API Credentials: If you haven't already, log in to the Vonage API Dashboard. Your
API key
andAPI secret
are displayed prominently on the main page. Copy these into your.env
file. - Virtual Number: Navigate to
Numbers
>Buy numbers
in the dashboard. Search for and purchase an SMS-capable number in your desired country. Copy this number (following the format specified in step 1.5, e.g.,15551234567
) into theVONAGE_FROM_NUMBER
field in your.env
file. - Trial Account Limitation (Crucial): By default, new Vonage accounts are in a trial/demo mode. In this mode, you can only send SMS messages to phone numbers that you have verified and added to your Test Numbers list. Go to your dashboard
Account
>Settings
>Test Numbers
to add and verify the phone number(s) you will use for testing. You must upgrade your account (by adding billing information) to send messages to any number.
- API Credentials: If you haven't already, log in to the Vonage API Dashboard. Your
-
Initialize Vonage SDK in
index.js
: Openindex.js
and add the following code to import dependencies, load environment variables, and initialize the Vonage client:// index.js import express from 'express'; import { Vonage } from '@vonage/server-sdk'; import 'dotenv/config'; // Loads .env variables into process.env // --- Vonage Client Initialization --- // Ensure API Key and Secret are loaded from environment variables const vonageApiKey = process.env.VONAGE_API_KEY; const vonageApiSecret = process.env.VONAGE_API_SECRET; const vonageFromNumber = process.env.VONAGE_FROM_NUMBER; if (!vonageApiKey || !vonageApiSecret) { console.error('Error: Vonage API Key or Secret not found in environment variables.'); console.error('Make sure you have a .env file with VONAGE_API_KEY and VONAGE_API_SECRET'); process.exit(1); // Exit if credentials are missing } if (!vonageFromNumber) { console.warn('Warning: VONAGE_FROM_NUMBER not found in environment variables. Sending SMS will fail.'); // Consider exiting if 'from' number is absolutely required: process.exit(1); } const vonage = new Vonage({ apiKey: vonageApiKey, apiSecret: vonageApiSecret, }); // --- Express App Setup --- 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 (optional but common) app.use(express.urlencoded({ extended: true })); // --- API Endpoint (to be added next) --- // --- Start Server --- app.listen(port, () => { console.log(`Server listening at http://localhost:${port}`); console.log(`Vonage 'From' number: ${vonageFromNumber || 'Not Set in .env'}`); });
- We import
express
,Vonage
from the SDK, and rundotenv/config
immediately to load the.env
file. - We retrieve the API key, secret, and 'from' number from
process.env
. Crucially, we add checks to ensure these variables exist, preventing the application from starting without credentials or warning if the 'from' number is missing. - We instantiate the
Vonage
client using the API key and secret. For simple SMS sending, this authentication method is sufficient. (The Messages API often uses Application ID and Private Key, which is a different setup). - Basic Express setup includes initializing the app, setting the port, and adding middleware to parse incoming JSON requests.
- The
app.listen
block starts the server.
- We import
3. Building the SMS Sending Endpoint
Let's create the API endpoint that will trigger the SMS sending.
-
Create POST Route: Add the following route handler inside
index.js
, before theapp.listen
call:// index.js // ... (previous code: imports, vonage init, app setup) ... // --- API Endpoint --- app.post('/send-sms', async (req, res) => { console.log('Received /send-sms request:', req.body); // 1. Extract data from request body const { to, message } = req.body; const from = process.env.VONAGE_FROM_NUMBER; // Get 'from' number from env // 2. Basic Input Validation if (!to || !message) { console.error('Validation Error: Missing `to` or `message` in request body.'); return res.status(400).json({ success: false, message: 'Missing required fields in request body: `to`, `message`.', }); } if (!from) { console.error('Configuration Error: Missing VONAGE_FROM_NUMBER in .env file.'); // Return 500 as this is a server config issue return res.status(500).json({ success: false, message: 'Server configuration error: Vonage \'from\' number is not set.', }); } // Basic format check (simple example, not full E.164 validation) // This checks for digits only, optionally after removing a leading '+' if (!/^\+?\d+$/.test(to) || !/^\+?\d+$/.test(from)) { console.error('Validation Error: Invalid phone number format detected.'); return res.status(400).json({ success: false, message: 'Invalid phone number format. Use E.164 format (e.g., +15551234567 or 15551234567).', }); } // 3. Send SMS using Vonage SDK try { console.log(`Attempting to send SMS from ${from} to ${to}`); const resp = await vonage.sms.send({ to, from, text: message }); // 4. Process Vonage Response // Vonage SMS API returns status '0' for success. if (resp.messages[0].status === '0') { console.log('Message sent successfully:', resp.messages[0]); res.status(200).json({ success: true, message: 'SMS sent successfully!', messageId: resp.messages[0]['message-id'], }); } else { // Vonage reported an error for this message const errorCode = resp.messages[0].status; const errorText = resp.messages[0]['error-text']; console.error(`Message failed with Vonage error code ${errorCode}: ${errorText}`); // Attempt to map Vonage error codes to appropriate HTTP status codes let statusCode = 500; // Default to Internal Server Error if (['1', '2', '3', '4', '5', '6', '8', '9', '15', '29'].includes(errorCode)) { // Codes related to bad input ('to' number issues, content, invalid sender) // or configuration (non-whitelisted) suggest a client-side issue (4xx) statusCode = 400; } // Other errors might be temporary server issues (5xx) res.status(statusCode).json({ success: false, message: `Failed to send SMS: ${errorText}`, errorCode: errorCode, }); } } catch (error) { // 5. Handle SDK/Network Errors (errors during the API call itself) console.error('Error sending SMS via Vonage SDK:', error); res.status(500).json({ success: false, message: 'An unexpected error occurred while contacting the Vonage API.', error: error.message, // Include specific error message for debugging }); } }); // ... (rest of the code: app.listen) ...
- Route: We define a POST endpoint at
/send-sms
. POST is appropriate as this action creates a resource (an SMS message). - Data Extraction: We get the recipient number (
to
) and the message content (message
) from the JSON request body (req.body
). The sender number (from
) is retrieved from our environment variables. - Validation: We perform basic checks:
- Ensure
to
andmessage
are present in the request body (returns400 Bad Request
). - Ensure
from
(our Vonage number) is configured in the environment (returns500 Internal Server Error
as it's a server config issue). - A very simple check for numeric characters (allowing an optional leading '+'). This is not full E.164 validation. Real-world applications need more robust validation (see Security section). Returns
400 Bad Request
.
- Ensure
- Vonage Call: We use
await vonage.sms.send(...)
.- The
vonage.sms.send
method is part of the SDK specifically for sending SMS messages using the simpler SMS API authentication (API Key/Secret). - We pass
to
,from
, andtext
(which corresponds to themessage
from the request body).
- The
- Response Handling:
- The Vonage API returns an array of message statuses. We check
resp.messages[0].status
. A status of'0'
indicates success. - If successful, we log the details and return a
200 OK
response with the Vonagemessage-id
. - If Vonage reports an error (status is not '0'), we log the specific
error-text
andstatus
code from Vonage. We attempt to map common Vonage error codes related to bad input (like invalid number format, non-whitelisted number) to an HTTP400 Bad Request
, otherwise defaulting to500 Internal Server Error
. We return the Vonage error details in the JSON response.
- The Vonage API returns an array of message statuses. We check
- Error Handling (
try...catch
): Thetry...catch
block handles potential errors during the SDK call itself (e.g., network issues, invalid credentials caught later). We log the error and return a generic500 Internal Server Error
.
- Route: We define a POST endpoint at
4. Running and Testing
Let's start the server and send a test SMS.
-
Start the Server: In your terminal, in the project directory, run:
npm start
You should see output like:
Server listening at http://localhost:3000 Vonage 'From' number: 15551234567
(Your 'From' number will vary).
-
Test with
curl
: Open another terminal window. You must replaceYOUR_TEST_PHONE_NUMBER
with a phone number you have whitelisted in your Vonage account settings (if using a trial account). Use the E.164 format (e.g.,15559876543
or+15559876543
).curl -X POST http://localhost:3000/send-sms \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""YOUR_TEST_PHONE_NUMBER"", ""message"": ""Hello from Node.js and Vonage!"" }'
-
Check Results:
- Terminal (curl):
- Success: You should receive a JSON response like:
{ ""success"": true, ""message"": ""SMS sent successfully!"", ""messageId"": ""some-vonage-message-id-12345"" }
- Failure (e.g., Validation):
{ ""success"": false, ""message"": ""Missing required fields in request body: `to`, `message`."" }
- Failure (e.g., Vonage Error - Non-whitelisted):
(HTTP status code might be 400){ ""success"": false, ""message"": ""Failed to send SMS: Non Whitelisted Destination"", ""errorCode"": ""15"" }
- Failure (e.g., Vonage Error - Server Issue):
(HTTP status code might be 500){ ""success"": false, ""message"": ""Failed to send SMS: Internal Error"", ""errorCode"": ""5"" }
- Terminal (Server Logs): Check the output of your running
npm start
command for detailed logs about the request processing and any errors encountered (both validation and Vonage API errors). - Your Phone: You should receive the SMS message on the test phone number shortly after a successful API response. Delays can occur depending on carriers.
- Vonage Dashboard: You can check the
Logs
section (often underMessages
orSMS
) in your Vonage dashboard to see the status of the sent message, associated costs, and any errors reported by Vonage.
- Terminal (curl):
5. Security Considerations
While this is a basic example, keep these security points in mind for production:
- API Key Security: Your
.env
file is critical. Ensure it's listed in.gitignore
and never committed to source control. Use secure environment variable management in your deployment environment (e.g., platform secrets, vault). - Input Validation: The current phone number validation is very basic. Implement robust validation, especially for the
to
number. Strongly consider using a dedicated library likegoogle-libphonenumber
(via its Node.js portlibphonenumber-js
) to parse and validate phone numbers against the E.164 standard accurately. Sanitize message content if it might originate from user input elsewhere to prevent injection attacks. - Rate Limiting: Protect your endpoint and Vonage account from abuse (accidental or malicious). Implement rate limiting on the
/send-sms
endpoint (e.g., usingexpress-rate-limit
) to restrict the number of requests a single client (IP address or authenticated user) can make in a given time period. - Authentication/Authorization: If this API is exposed publicly or even internally within a larger system, ensure only authorized users or services can trigger SMS sending. Implement proper authentication mechanisms (e.g., API keys for backend services, JWT tokens for user sessions). Do not expose an unprotected SMS sending endpoint to the internet.
6. Troubleshooting and Caveats
- Error:
Vonage API Key or Secret not found
: Ensure your.env
file exists in the project root, is correctly named (.env
), and contains theVONAGE_API_KEY
andVONAGE_API_SECRET
variables with valid values. Make suredotenv/config
is imported early inindex.js
(import 'dotenv/config';
). Restart the server after creating/modifying.env
. - Error:
Vonage 'from' number is not set
(500 error from API): EnsureVONAGE_FROM_NUMBER
is present and correctly spelled in your.env
file and has a valid Vonage number assigned. Restart the server. - Error:
401 Unauthorized
(in Vonage logs or response error text): Double-check that your API Key and Secret in the.env
file exactly match those in your Vonage dashboard. Regenerate keys if necessary. - Error:
Non Whitelisted Destination
(Vonage error code 15, likely HTTP 400): This is the most common issue with trial accounts. Ensure theto
phone number you are sending to has been added and verified in your Vonage dashboard underAccount
>Settings
>Test Numbers
. Upgrade your account (add payment details) to send to any number. - Error:
Invalid Sender Address (from)
(Vonage error, likely HTTP 400): Ensure theVONAGE_FROM_NUMBER
in your.env
file is a valid, SMS-capable number associated with your Vonage account and is in the correct format (usually E.164 without leading+
for the SDK, e.g.,15551234567
). Check the specific requirements for your country/number type. - Error:
Throughput capacity exceeded
(Vonage error): You might be sending messages too quickly for your account's allowed rate (often 1 SMS/second by default). Implement delays between sends if sending in bulk, use asynchronous processing queues, or contact Vonage support about increasing limits. - No SMS Received:
- Check the API response and server logs for errors first. Did the API call succeed?
- Verify the
to
number is correct and whitelisted (if using a trial account). - Check the message status in the Vonage dashboard logs. It might show failed delivery attempts.
- Ensure the receiving device has network signal and is not blocking messages from unknown numbers.
- Consider carrier filtering or temporary network issues.
- SMS API vs. Messages API: This guide uses the standard
vonage.sms.send
method, authenticating via API Key/Secret. Vonage also has a more versatile Messages API (vonage.messages.send
) which supports multiple channels (SMS, MMS, WhatsApp, etc.) and often provides more detailed responses and features like delivery receipts via webhooks. The Messages API typically requires setup with a Vonage Application (generating an Application ID and Private Key file) for authentication. Choose the API that best fits your needs; for simple SMS,vonage.sms.send
is often easier to start with.
7. Next Steps
This basic service can be extended in many ways:
- Receive SMS: Implement a webhook endpoint using Express to receive incoming SMS messages sent to your Vonage number. This requires setting up webhook URLs in the Vonage dashboard for your number and potentially using tools like
ngrok
for local development testing. - Delivery Receipts (DLRs): Configure a status webhook URL in Vonage (either globally or per-message) to receive delivery receipts, confirming if a message was successfully delivered to the handset (or if it failed). Process these receipts in another Express endpoint.
- Robust Error Handling: Implement more granular error handling based on specific Vonage error codes. Add retry logic with exponential backoff for transient network errors or specific Vonage errors (like throughput limits).
- Asynchronous Sending: For higher volume sending, use a message queue (like RabbitMQ or Redis) to decouple the API request from the actual Vonage API call. The API endpoint adds a job to the queue, and a separate worker process handles sending the SMS.
- Logging: Integrate a proper logging library (like
winston
orpino
) for structured, leveled logging instead ofconsole.log
, making it easier to manage logs in production. - Database Integration: Store message logs (including status,
message-id
, recipient, timestamp), recipient information, or message templates in a database (e.g., PostgreSQL, MongoDB). - Frontend Integration: Build a simple web interface (using HTML, CSS, JavaScript, and potentially a framework like React or Vue) that allows users to enter a number and message and submit it to your
/send-sms
API endpoint. - Deployment: Deploy the application to a cloud platform like Heroku, AWS (EC2, Lambda, Fargate), Google Cloud (Cloud Run, App Engine), or Azure (App Service). Ensure environment variables (
.env
contents) are configured securely in the deployment environment, not by committing the.env
file. - Testing: Add unit tests (e.g., using Jest or Mocha/Chai) to test validation logic and helper functions. Add integration tests that mock the Vonage SDK (
@vonage/server-sdk
) to verify the/send-sms
endpoint behavior without making actual API calls.
8. Complete Code Repository
You can find the complete working code for this tutorial in a GitHub repository. (Note: A link to the actual repository would be placed here in a final version.)
This guide provides a solid foundation for sending SMS messages using Node.js, Express, and Vonage. Remember to handle credentials securely, implement robust validation and error handling, and consult the official Vonage Node.js SDK documentation and Vonage SMS API documentation for more advanced features and details. Happy coding!