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 project setup to deployment considerations, enabling you to integrate SMS functionality into your applications effectively.
This tutorial focuses on sending SMS messages. While Vonage also enables receiving messages via webhooks, that functionality requires a publicly accessible server (often set up using tools like ngrok during development) and is detailed in separate Vonage documentation.
Project Overview and Goals
Goal: To create a simple, robust Node.js API endpoint that accepts a recipient phone number and a message text, then uses the Vonage Messages API to send an SMS message.
Problem Solved: This provides a foundational service for applications needing to send programmatic SMS notifications, alerts, verification codes, or other messages to users.
Technologies Used:
- Node.js: A JavaScript runtime environment ideal for building scalable network applications.
- Express: A minimal and flexible Node.js web application framework providing features for web and mobile applications, perfect for creating our API endpoint.
- Vonage Messages API: A powerful API from Vonage that enables sending messages across various channels, including SMS, MMS, WhatsApp, and more. We'll use it specifically for SMS.
@vonage/server-sdk
: The official Vonage Server SDK for Node.js, simplifying interactions with the Vonage APIs.dotenv
: A zero-dependency module that loads environment variables from a.env
file intoprocess.env
, crucial for managing sensitive credentials securely.
System Architecture:
graph LR
Client[Client Application / Postman / curl] -->|1. POST /send-sms (to, text)| ExpressAPI[Node.js/Express API Server];
ExpressAPI -->|2. Initialize Vonage SDK| VonageSDK[@vonage/server-sdk];
ExpressAPI -->|3. Call vonage.messages.send()| VonageSDK;
VonageSDK -->|4. Send SMS Request (HTTPS)| VonageAPI[Vonage Messages API];
VonageAPI -->|5. Process & Send SMS| SMSNetwork[Carrier SMS Network];
SMSNetwork -->|6. Deliver SMS| UserPhone[User's Phone];
VonageAPI -->|7. Return message_uuid (async)| VonageSDK;
VonageSDK -->|8. Return Response| ExpressAPI;
ExpressAPI -->|9. Return API Response (e.g., { success: true, messageId: ... })| Client;
subgraph Your Application
ExpressAPI
VonageSDK
end
subgraph Vonage Platform
VonageAPI
end
Prerequisites:
- Node.js and npm: Installed on your system (LTS version recommended). Download from nodejs.org.
- Vonage API Account: Sign up for free at Vonage API Dashboard.
- Vonage Virtual Phone Number: You need a Vonage number capable of sending SMS messages. You can rent one via the Vonage Dashboard. Note that trial accounts may have restrictions (see Troubleshooting section).
- Vonage Application: A Vonage Application configured for the Messages API, with generated public/private keys.
- Basic Command Line/Terminal Knowledge: Familiarity with navigating directories and running commands.
- (Optional) API Testing Tool: Like Postman or
curl
for testing the API endpoint.
Final Outcome: A running Node.js server with a single API endpoint (/send-sms
) that successfully sends an SMS message via Vonage when called with a valid phone number and message.
1. Setting up the Project
Let's initialize our 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: Use
npm init
to create apackage.json
file. The-y
flag accepts default settings.npm init -y
-
Install Dependencies: Install Express (web framework), the Vonage Server SDK, and
dotenv
(for environment variables).npm install express @vonage/server-sdk dotenv --save
express
: Handles HTTP requests and routing.@vonage/server-sdk
: Interacts with the Vonage API.dotenv
: Loads environment variables from a.env
file.--save
: Adds these packages to thedependencies
section in yourpackage.json
.
-
Create Project Structure: Create the main application file and the environment file.
touch index.js .env .gitignore
index.js
: Will contain our Express server and API logic..env
: Will store our sensitive credentials (API keys, secrets, phone numbers). 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 them.# .gitignore node_modules/ .env *.log private.key # If storing the key directly in the project
-
Configure
.env
: Open the.env
file and add the following placeholders. We will fill these in later (Section 4).# .env PORT=3000 # Vonage Credentials (Using Application ID & Private Key - Recommended for Messages API) VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Or the full path to your key file # Vonage Virtual Number VONAGE_VIRTUAL_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER_E164
PORT
: The port number our Express server will listen on.VONAGE_APPLICATION_ID
: Your Vonage Application ID (obtained from the Vonage Dashboard).VONAGE_PRIVATE_KEY_PATH
: The file path to theprivate.key
file downloaded when creating your Vonage Application. We assume it will be placed in the project root for this example.VONAGE_VIRTUAL_NUMBER
: The Vonage phone number you rented, in E.164 format (e.g.,+12015550123
).- Note on Authentication: The Messages API primarily uses Application ID and Private Key for authentication via JWTs generated by the SDK. This method is generally preferred for the Messages API and provides better security partitioning. We will use Application ID/Private Key in this guide.
2. Implementing Core Functionality
Now, let's write the core logic in index.js
to initialize the Vonage SDK and prepare the function to send SMS.
// index.js
'use strict';
// 1. Load Environment Variables
require('dotenv').config(); // Load variables from .env file into process.env
// 2. Import Dependencies
const express = require('express');
const { Vonage } = require('@vonage/server-sdk'); // Import Vonage SDK
// 3. Initialize Vonage SDK
// Ensure required environment variables are present
if (!process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_PRIVATE_KEY_PATH || !process.env.VONAGE_VIRTUAL_NUMBER) {
console.error(""Error: Required Vonage environment variables (VONAGE_APPLICATION_ID, VONAGE_PRIVATE_KEY_PATH, VONAGE_VIRTUAL_NUMBER) are not set."");
console.error(""Please check your .env file."");
process.exit(1); // Exit if essential config is missing
}
const vonage = new Vonage({
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_PRIVATE_KEY_PATH,
});
// 4. Define the SMS Sending Function
async function sendSms(toNumber, messageText) {
// Validate input format (basic)
if (!/^\+[1-9]\d{1,14}$/.test(toNumber)) {
// Use standard quotes within the error string
throw new Error('Invalid ""to"" number format. Must be E.164 format (e.g., +12125551234)');
}
if (!messageText || typeof messageText !== 'string' || messageText.trim().length === 0) {
throw new Error('Invalid ""text"" parameter. Message text cannot be empty.');
}
const fromNumber = process.env.VONAGE_VIRTUAL_NUMBER;
// Use template literal for cleaner logging
console.log(`Attempting to send SMS from ${fromNumber} to ${toNumber}`);
try {
// Use vonage.messages.send() for the Messages API
const resp = await vonage.messages.send({
channel: 'sms',
message_type: 'text',
to: toNumber,
from: fromNumber,
text: messageText,
// Optional: client_ref for your internal tracking (max 40 chars)
// client_ref: `my-internal-ref-${Date.now()}`
});
console.log('SMS submitted successfully:');
console.log(resp); // Log the full response from Vonage
// The Messages API returns a message_uuid for tracking
if (resp && resp.message_uuid) {
return resp.message_uuid;
} else {
// This case might indicate an issue before actual sending attempt
console.error(""Unexpected response structure from Vonage:"", resp);
throw new Error('SMS submission failed or response format unexpected.');
}
} catch (err) {
console.error(""Error sending SMS via Vonage:"");
// Log detailed error if available (e.g., network issue, SDK error)
if (err.response && err.response.data) {
console.error(""Vonage API Error Details:"", JSON.stringify(err.response.data, null, 2));
// Rethrow a more specific error based on Vonage response if needed
throw new Error(`Vonage API Error: ${err.response.data.title || 'Unknown error'} - ${err.response.data.detail || JSON.stringify(err.response.data)}`);
} else {
console.error(err); // Log the raw error object
throw new Error(`Failed to send SMS: ${err.message || 'Unknown error'}`);
}
}
}
// --- Express API Setup will go here in the next section ---
// Placeholder for exports and starting the server (will be added later)
Explanation:
- Load Environment Variables:
require('dotenv').config()
reads the.env
file and makes variables likeprocess.env.VONAGE_APPLICATION_ID
available. This should be done at the very beginning. - Import Dependencies: We import
express
and theVonage
class from the SDK. - Initialize Vonage SDK: We create a
Vonage
instance, passing our Application ID and the path to the private key file retrieved fromprocess.env
. Basic validation ensures these crucial variables are set. - Define
sendSms
Function:- This
async
function takes the recipient number (toNumber
) and the message (messageText
) as arguments. - It performs basic validation on the
toNumber
(checking for E.164 format) andmessageText
. - It retrieves the
fromNumber
from the environment variable. - It calls
vonage.messages.send()
, specifying thechannel
('sms'),message_type
('text'),to
,from
, andtext
. - The
try...catch
block handles potential errors during the API call (network issues, invalid credentials, Vonage API errors). - On success, it logs the response and returns the
message_uuid
, which is Vonage's identifier for the submitted message. - On failure, it logs detailed error information and throws an error to be handled by the calling code (our API endpoint).
- This
3. Building a Complete API Layer
Now, let's use Express to create an API endpoint that utilizes our sendSms
function.
Add the following code to the end of your index.js
file:
// index.js (continued)
// --- Express API Setup ---
const app = express();
// Middleware to parse JSON request bodies
app.use(express.json());
// Middleware to parse URL-encoded request bodies
app.use(express.urlencoded({ extended: true }));
// Define the POST endpoint for sending SMS
app.post('/send-sms', async (req, res) => {
// Extract 'to' and 'text' from the request body
const { to, text } = req.body;
// Basic input validation for presence
if (!to || !text) {
// Corrected console error logging
console.error('Validation Error: Missing ""to"" or ""text"" in request body');
return res.status(400).json({
success: false,
// Corrected JSON string format
message: 'Missing required parameters: ""to"" (E.164 format) and ""text"".'
});
}
try {
// Call our core SMS sending function
const messageId = await sendSms(to, text);
// Send success response
console.log(`API successfully processed SMS request. Vonage Message ID: ${messageId}`);
return res.status(200).json({
success: true,
message: 'SMS submitted successfully.',
messageId: messageId // Include the Vonage message_uuid
});
} catch (error) {
// Handle errors from the sendSms function
console.error(`API Error processing /send-sms: ${error.message}`);
// Determine appropriate status code (e.g., 400 for validation, 500 for server/Vonage errors)
const statusCode = error.message.includes('Invalid') ? 400 : 500;
return res.status(statusCode).json({
success: false,
message: `Failed to send SMS: ${error.message}`
});
}
});
// Health check endpoint (Good practice)
app.get('/health', (req, res) => {
res.status(200).json({ status: 'OK', timestamp: new Date().toISOString() });
});
// Export app and sendSms for testing purposes
// Note: Starting the server should typically be in a separate file (e.g., server.js)
// for better testability, but we'll keep it here for simplicity in this guide.
module.exports = { app, sendSms };
// Start the Express server (only if this file is run directly)
if (require.main === module) {
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server listening on http://localhost:${PORT}`);
console.log(`SMS Send Endpoint: POST http://localhost:${PORT}/send-sms`);
console.log(`Health Check: GET http://localhost:${PORT}/health`);
});
}
Explanation:
- Initialize Express:
const app = express();
creates an Express application. - Middleware:
app.use(express.json());
enables the server to parse incoming request bodies formatted as JSON.app.use(express.urlencoded({ extended: true }));
enables parsing of URL-encoded data (often used by HTML forms).
- POST
/send-sms
Endpoint:- Defines a route that listens for POST requests at the
/send-sms
path. - It extracts the
to
phone number andtext
message from the request body (req.body
). - It performs basic validation to ensure both parameters are present.
- It calls the
await sendSms(to, text)
function within atry...catch
block. - On success, it returns a
200 OK
response withsuccess: true
and themessageId
received from Vonage. - On failure (catching the error thrown by
sendSms
), it logs the error and returns an appropriate error status code (400
for bad input,500
for internal/Vonage errors) and a JSON response withsuccess: false
and the error message.
- Defines a route that listens for POST requests at the
- GET
/health
Endpoint: A simple health check endpoint that returns a200 OK
status, useful for monitoring. - Exports:
module.exports = { app, sendSms };
exports the Expressapp
instance and thesendSms
function, making them accessible for testing (See Section 13 - Note: Section 13 was mentioned but not provided in the original input, this reference is kept for context). - Start Server: The
if (require.main === module)
block ensuresapp.listen()
is only called whenindex.js
is run directly (e.g.,node index.js
), not when it's required by another module (like a test file). This starts the server on the specifiedPORT
.
Testing the Endpoint:
Once your Vonage credentials are set up (Section 4), you can test this endpoint using curl
or Postman.
Using curl
:
curl -X POST http://localhost:3000/send-sms \
-H ""Content-Type: application/json"" \
-d '{
""to"": ""+12015550124"",
""text"": ""Hello from Node.js and Vonage!""
}'
Expected Success Response (JSON):
{
""success"": true,
""message"": ""SMS submitted successfully."",
""messageId"": ""aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee""
}
Expected Error Response (JSON - e.g., missing parameter):
{
""success"": false,
""message"": ""Missing required parameters: \""to\"" (E.164 format) and \""text\"".""
}
Expected Error Response (JSON - e.g., Vonage API failure):
{
""success"": false,
""message"": ""Failed to send SMS: Vonage API Error: Unauthorized - Please check your Application ID/private key""
}
4. Integrating with Necessary Third-Party Services (Vonage)
This section details how to get the required credentials from Vonage and configure them in your .env
file.
-
Sign Up/Log In to Vonage: Go to the Vonage API Dashboard and log in or create a new account.
-
Create a Vonage Application: Applications act as containers for your Vonage settings and credentials.
- Navigate to ""Applications"" in the left-hand menu.
- Click ""Create a new application"".
- Name: Give your application a descriptive name (e.g., ""Node SMS Sender App"").
- Generate Public and Private Key: Click the button to generate key pair. Crucially, save the
private.key
file immediately. You cannot retrieve it later. Place this file in your project directory (or a secure location referenced by the full path in.env
). - Capabilities: Enable the ""Messages"" capability.
- Inbound URL / Status URL: For sending SMS, these are less critical but still required by the dashboard. You can enter placeholders like
https://example.com/webhooks/inbound
andhttps://example.com/webhooks/status
. If you later implement receiving SMS or delivery receipts, you'll need valid, publicly accessible URLs here (often using ngrok during development).
- Inbound URL / Status URL: For sending SMS, these are less critical but still required by the dashboard. You can enter placeholders like
- Click ""Generate new application"".
- Application ID: On the next screen, copy the generated Application ID.
-
Obtain a Vonage Virtual Number: You need a phone number associated with your Vonage account to send SMS from.
- Navigate to ""Numbers"" > ""Buy numbers"" in the left-hand menu.
- Search for numbers by country and features (ensure ""SMS"" is selected).
- Choose a number and click ""Buy"". Confirm the purchase.
- Navigate to ""Numbers"" > ""Your numbers"". Find the number you just purchased. Copy the number in E.164 format (e.g.,
+12015550123
).
-
Link Number to Application (Important): Associate your virtual number with the application you created.
- Go back to ""Applications"" and select the application you created (""Node SMS Sender App"").
- Scroll down to the ""Linked numbers"" section.
- Click ""Link"" next to the virtual number you purchased.
-
Configure Messages API Settings: Ensure your account is set to use the Messages API as the default for SMS.
- Navigate to your main Account Settings.
- Scroll down to ""API settings"".
- Find the ""SMS settings"" section.
- Ensure ""Default SMS Setting"" is set to Messages API. If it's set to ""SMS API"", toggle it and click ""Save changes"".
-
Update
.env
File: Now, open your.env
file and replace the placeholders with the actual values you obtained:# .env PORT=3000 # Vonage Credentials (Using Application ID & Private Key) VONAGE_APPLICATION_ID=YOUR_COPIED_APPLICATION_ID # Paste the Application ID here VONAGE_PRIVATE_KEY_PATH=./private.key # Ensure this path matches where you saved the key file # Vonage Virtual Number VONAGE_VIRTUAL_NUMBER=+12015550123 # Paste your rented Vonage number in E.164 format
Environment Variable Explanation:
VONAGE_APPLICATION_ID
: What: Unique identifier for your Vonage Application. How: Obtained after creating an application in the Vonage Dashboard. Purpose: Used by the SDK (along with the private key) to authenticate requests to the Messages API via JWT.VONAGE_PRIVATE_KEY_PATH
: What: File path to the private key file. How: Downloaded when creating the Vonage Application. Purpose: Used by the SDK to sign JWTs for authenticating requests. Keep this file secure and do not commit it to version control.VONAGE_VIRTUAL_NUMBER
: What: The Vonage phone number used as the sender ID ('from' number). How: Rented via the Vonage Dashboard (""Numbers"" section). Purpose: Specifies the origin number for the outgoing SMS. Must be in E.164 format.
Security: The private.key
file and your .env
file contain sensitive credentials. Ensure they are:
- Never committed to Git (use
.gitignore
). - Stored securely in your development and production environments.
- Have appropriate file permissions set (restrict read access).
5. Implementing Proper Error Handling, Logging, and Retry Mechanisms
Our current error handling is basic. Let's enhance it.
Error Handling Strategy:
- Validation Errors (Client-side): Catch invalid input (missing params, bad number format) early in the API endpoint and return
400 Bad Request
. - SDK/Network Errors (Server-side): Catch errors during the
vonage.messages.send()
call. Log detailed information for debugging. Return500 Internal Server Error
or a more specific 5xx code if appropriate. - Vonage API Errors (Server-side): Inspect the error object returned by the SDK (often in
err.response.data
) for specific Vonage error codes/messages (e.g., authentication failure, insufficient funds, invalid destination). Log these details and return500
(or potentially4xx
if the error clearly indicates a client-side issue like an invalid 'to' number rejected by Vonage).
Logging:
console.log
and console.error
are suitable for development. For production, use a dedicated logging library like Winston or Pino for structured logging, different log levels (debug, info, warn, error), and configurable outputs (file, console, external services).
Example using Winston (Conceptual):
npm install winston
- Configure Winston:
// logger.js const winston = require('winston'); const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', // Control log level via env var format: winston.format.combine( winston.format.timestamp(), winston.format.json() // Log in JSON format ), transports: [ // Log errors to a file new winston.transports.File({ filename: 'error.log', level: 'error' }), // Log everything to another file new winston.transports.File({ filename: 'combined.log' }), // Log to console (especially in development) new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() // Simple format for console ) }) ], }); module.exports = logger;
- Replace
console.log
/console.error
withlogger.info
/logger.error
:// index.js (modified example using logger) const logger = require('./logger'); // Assuming logger.js exists // ... inside /send-sms endpoint error handler } catch (error) { // Log more context like stack trace and request details logger.error(`API Error processing /send-sms: ${error.message}`, { stack: error.stack, requestBody: req.body }); const statusCode = error.message.includes('Invalid') ? 400 : 500; return res.status(statusCode).json({ /* ... response */ }); } // ... inside sendSms function catch block } catch (err) { // Log error object and any Vonage-specific response data logger.error(""Error sending SMS via Vonage"", { error: err, vonageResponse: err.response?.data }); // ... throw error }
Retry Mechanisms:
Network glitches or temporary Vonage issues can cause failures. Implementing retries can improve reliability. Use libraries like async-retry
for straightforward implementation with exponential backoff.
Example using async-retry
(Conceptual):
npm install async-retry
- Wrap the Vonage call in
retry
:// index.js (modified sendSms function with retry logic) const retry = require('async-retry'); const logger = require('./logger'); // Assuming logger is setup async function sendSms(toNumber, messageText) { // ... input validation ... const fromNumber = process.env.VONAGE_VIRTUAL_NUMBER; try { const resp = await retry(async (bail, attempt) => { // bail is a function to stop retrying (e.g., for non-retryable errors) // attempt is the current attempt number logger.info(`Attempt ${attempt} to send SMS to ${toNumber}`); try { const vonageResponse = await vonage.messages.send({ channel: 'sms', message_type: 'text', to: toNumber, from: fromNumber, text: messageText, }); logger.info('SMS submitted successfully:', vonageResponse); return vonageResponse; // Return successful response } catch (sdkError) { // Check for non-retryable errors and call bail() // Examples: Authentication errors, invalid number formats, insufficient funds if (sdkError.response) { const status = sdkError.response.status; const data = sdkError.response.data; // 401 Unauthorized is definitely non-retryable if (status === 401) { logger.error(`Non-retryable Vonage API error (Status: 401 - Unauthorized). Bailing.`); bail(new Error(`Vonage Authentication Error: ${data?.title || 'Unauthorized'}`)); return; // Important } // Some 400 Bad Requests might be non-retryable (e.g., invalid 'to' format) // Check specific error details if possible if (status === 400 && data?.title?.includes('Invalid')) { logger.warn(`Potentially non-retryable Vonage API error (Status: 400 - ${data.title}). Bailing.`); bail(new Error(`Vonage Bad Request Error: ${data?.title} - ${data?.detail || 'Invalid request'}`)); return; // Important } // Add checks for other known non-retryable errors (e.g., specific error codes for insufficient funds) // if (data?.code === 'specific-non-retryable-code') { bail(...); return; } } // If error seems temporary (network issue, 5xx server error), let retry handle it logger.warn(`Attempt ${attempt} failed: ${sdkError.message}. Retrying...`); throw sdkError; // Throw error to trigger retry } }, { retries: 3, // Number of retries factor: 2, // Exponential backoff factor minTimeout: 1000, // Minimum time between retries (ms) maxTimeout: 5000, // Maximum time between retries (ms) onRetry: (error, attempt) => { logger.warn(`Retrying SMS send (Attempt ${attempt}) due to error: ${error.message}`); } }); if (resp && resp.message_uuid) { return resp.message_uuid; } else { logger.error(""Unexpected response structure after retries:"", resp); throw new Error('SMS submission failed after retries or response format unexpected.'); } } catch (err) { // This catch block now catches the final error after all retries failed // or if bail() was called logger.error(""Failed to send SMS after multiple retries:"", { finalError: err }); if (err.response && err.response.data) { // Throw the specific error message from Vonage if available throw new Error(`Vonage API Error: ${err.response.data.title || 'Unknown error'} - ${err.response.data.detail || JSON.stringify(err.response.data)}`); } else { // Throw the error message passed to bail() or the last retry error throw new Error(`Failed to send SMS after retries: ${err.message || 'Unknown error'}`); } } }
Testing Error Scenarios:
- Provide invalid credentials in
.env
. - Provide an incorrectly formatted phone number (e.g.,
12345
,+1 555 123 4567
). - Use a valid number but one not whitelisted on a trial account (see Section 11 - Note: Section 11 was mentioned but not provided in the original input, this reference is kept for context).
- Temporarily disconnect network connectivity.
- Send requests without required parameters (
to
,text
).
Log Analysis: When issues occur, check your configured log output (e.g., error.log
, console, or a log aggregation service) for detailed error messages, stack traces, and potentially the Vonage API response body, which often contains specific error codes and descriptions useful for debugging.
6. Creating a Database Schema and Data Layer
For this specific guide (only sending SMS), a database is not strictly required. However, in a real-world application, you would likely want to track sent messages, their status, cost, and potentially link them to users or events in your system.
If Tracking Were Needed:
-
Schema Design (Example using SQL):
CREATE TABLE sent_sms_log ( id SERIAL PRIMARY KEY, -- Or UUID vonage_message_uuid VARCHAR(36) UNIQUE, -- Store the ID from Vonage client_ref VARCHAR(40), -- Optional client reference sent to Vonage to_number VARCHAR(20) NOT NULL, from_number VARCHAR(20) NOT NULL, message_body TEXT NOT NULL, status VARCHAR(20) DEFAULT 'submitted', -- e.g., submitted, delivered, failed, rejected status_timestamp TIMESTAMPTZ, -- Timestamp of the last status update (requires webhook) submission_timestamp TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, error_code VARCHAR(50), -- Store error code if failed error_message TEXT, -- Store error description if failed price DECIMAL(10, 5), -- Store cost (requires webhook) currency VARCHAR(3), -- Store currency (requires webhook) -- Optional: Foreign key to your users table -- user_id INT REFERENCES users(id) ); -- Index for common lookups CREATE INDEX idx_sent_sms_log_uuid ON sent_sms_log(vonage_message_uuid); CREATE INDEX idx_sent_sms_log_to_number ON sent_sms_log(to_number); CREATE INDEX idx_sent_sms_log_submission_timestamp ON sent_sms_log(submission_timestamp);
-
Entity Relationship Diagram (Conceptual):
erDiagram SENT_SMS_LOG { INT id PK VARCHAR vonage_message_uuid UK VARCHAR client_ref VARCHAR to_number VARCHAR from_number TEXT message_body VARCHAR status TIMESTAMPTZ status_timestamp TIMESTAMPTZ submission_timestamp VARCHAR error_code TEXT error_message DECIMAL price VARCHAR currency }
-
Data Access Layer: You would use an ORM (Object-Relational Mapper) like Prisma or Sequelize in Node.js to interact with the database.
- Prisma Example (Conceptual
schema.prisma
):model SentSmsLog { id Int @id @default(autoincrement()) vonageMessageUuid String? @unique @map(""vonage_message_uuid"") clientRef String? @map(""client_ref"") @db.VarChar(40) toNumber String @map(""to_number"") @db.VarChar(20) fromNumber String @map(""from_number"") @db.VarChar(20) messageBody String @map(""message_body"") @db.Text status String @default(""submitted"") @db.VarChar(20) statusTimestamp DateTime? @map(""status_timestamp"") @db.Timestamptz submissionTimestamp DateTime @default(now()) @map(""submission_timestamp"") @db.Timestamptz errorCode String? @map(""error_code"") @db.VarChar(50) errorMessage String? @map(""error_message"") @db.Text price Decimal? @db.Decimal(10, 5) currency String? @db.VarChar(3) // userId Int? @map(""user_id"") // user User? @relation(fields: [userId], references: [id]) @@map(""sent_sms_log"") @@index([vonageMessageUuid]) @@index([toNumber]) @@index([submissionTimestamp]) } // Example User model if linking // model User { // id Int @id @default(autoincrement()) // email String @unique // smsLogs SentSmsLog[] // }
- Prisma Example (Conceptual