Frequently Asked Questions
Use the `/schedule` endpoint of the Node.js Express app, sending a POST request with recipient, message, and sendAt (ISO 8601 format) in the body. The app uses `node-cron` to schedule and the Vonage Messages API to send the SMS at the specified time. A unique job ID is returned for tracking.
The Vonage Messages API is a versatile communication API that allows sending messages across various channels, including SMS. This Node.js app utilizes it to deliver the scheduled SMS messages reliably and efficiently.
Vonage offers reliable, scalable communication APIs with global reach and various features. The Messages API provides a unified approach for different communication channels, though this guide focuses on SMS capabilities.
A persistent store like Redis or a database is essential in production. The in-memory storage used in this guide is only for development; server restarts will clear scheduled jobs. Production apps need persistence and ideally a queue manager (BullMQ, Agenda).
Yes, send a DELETE request to `/api/schedule/{jobId}`, providing the job ID you received when scheduling. This will stop the scheduled task if it hasn't executed yet and remove it from the system.
Create a Vonage Application, get a virtual number, and link them. Put API keys, Application ID, private key path, and Vonage number in a `.env` file (never commit to Git). The Node.js app loads these for Vonage API interaction.
Node-cron is a task scheduler for Node.js. It allows you to trigger functions at specific intervals or times. The provided example uses it to execute the SMS sending function at the user-defined 'sendAt' time.
The guide includes basic error handling and logging. For production, enhance this with a structured logger, retry logic within the scheduler, and custom error classes. Exponential backoff is a good practice for retries.
The `/schedule` endpoint is a POST endpoint that accepts the SMS scheduling requests. It takes the recipient number, the message content, and the desired sending time as input and schedules the SMS message accordingly.
Use tools like `curl` or Postman to send requests to the local server after starting it with `npm start`. Verify the responses, check your phone for the SMS, and monitor server logs. Automated unit and integration tests are recommended.
Implement input validation, rate limiting, proper authentication (API keys, JWTs), and secure headers. The provided code includes examples of rate limiting and helmet.js for header security.
The project uses Node.js, Express, the Vonage Messages API and Server SDK, `node-cron` for scheduling, `dotenv` for environment variables, and `uuid` for unique identifiers.
The client interacts with the Node.js/Express API server, which uses scheduling logic (`node-cron`) and interacts with the Vonage API via the SDK. Messages are then sent from the Vonage cloud to the recipient.
The guide recommends creating directories for `src`, `src/routes`, `src/services`, and `src/config` for organizing application code, route definitions, service logic, and configuration respectively.
This guide provides a step-by-step walkthrough for creating a Node.js application using the Express framework to schedule SMS messages for future delivery via the Vonage Messages API. We'll build an API endpoint that accepts scheduling requests, manages pending messages, and uses Vonage to send them at the specified time.
Project Overview and Goals
What We're Building:
We are building a backend API service that enables users to schedule SMS messages. The core functionality includes:
POST /schedule
) to accept SMS scheduling requests (recipient number, message content, desired send time).node-cron
) to trigger SMS sending at the correct time.Problem Solved:
This application addresses the need to send automated SMS communications at specific future dates and times, such as appointment reminders, follow-up messages, or time-sensitive alerts, without requiring manual intervention at the moment of sending.
Technologies Used:
@vonage/server-sdk
.node-cron
: A simple cron-like job scheduler for Node.js to trigger tasks at specific times.dotenv
: To manage environment variables securely.uuid
: To generate unique identifiers for scheduled jobs.Why Vonage?
Vonage provides reliable and scalable communication APIs with extensive features and global reach, making it a solid choice for integrating SMS capabilities into applications. The Messages API offers a unified way to handle multiple channels, although we focus on SMS here.
System Architecture:
Prerequisites:
npm install -g @vonage/cli
Final Outcome:
By the end of this guide_ you will have a running Node.js Express application with a
/schedule
endpoint capable of accepting SMS scheduling requests and sending the messages at the specified future time using Vonage.1. Setting Up the Project
Let's initialize our Node.js project and install the necessary dependencies.
Step 1: Create Project Directory
Open your terminal and create a new directory for your project_ then navigate into it.
Step 2: Initialize Node.js Project
Initialize the project using npm (or yarn). The
-y
flag accepts default settings.This creates a
package.json
file.Step 3: Install Dependencies
Install Express_ the Vonage Server SDK_
node-cron
_dotenv
_ anduuid
.express
: Web framework.@vonage/server-sdk
: Official Vonage SDK for Node.js.node-cron
: Task scheduler.dotenv
: Loads environment variables from a.env
file.uuid
: Generates unique IDs for tracking jobs.Step 4: Project Structure (Recommended)
Create a basic structure for better organization:
src/
: Contains your main application code.src/routes/
: Holds Express route definitions.src/services/
: Contains business logic_ like interacting with Vonage or the scheduler.src/config/
: For configuration files (like Vonage SDK initialization).Step 5: Create Basic Express Server (
src/app.js
)Create a file named
app.js
inside thesrc
directory with the following basic Express setup:Step 6: Create Start Script
Modify the
scripts
section in yourpackage.json
to easily start the server:You can now test the basic server by running
npm start
in your terminal and navigating tohttp://localhost:3000
in your browser or usingcurl http://localhost:3000
. You should see the JSON message. Stop the server withCtrl+C
.2. Setting Up Vonage Credentials and SDK
To interact with the Vonage API, you need credentials and a virtual phone number. We'll use the Application ID and Private Key method, which is recommended for the Messages API.
Step 1: Create a Vonage Application
An Application acts as a container for your communication settings and credentials.
Messages
capability) is useful if you plan to add status updates later. If prompted for webhook URLs, you can provide placeholders for now (e.g.,https://example.com/webhooks/inbound
,https://example.com/webhooks/status
).private.key
in your current directory and outputs an Application ID. Save this ID.private.key
file securely (e.g., in your project root, but ensure it's gitignored). The public key remains with Vonage.Step 2: Obtain a Vonage Virtual Number
You need a Vonage phone number to send SMS messages from.
US
with your desired country code):NUMBER
andCOUNTRY_CODE
):Step 3: Link the Number to Your Application
Associate the purchased number with the application you created. This tells Vonage which application's settings (and potentially webhooks) to use for messages involving this number.
Step 4: Configure Environment Variables
Create a file named
.env
in the root of your project (the same level aspackage.json
). Never commit this file to Git.YOUR_API_KEY
,YOUR_API_SECRET
,YOUR_APPLICATION_ID
, andYOUR_VONAGE_NUMBER
with your actual values.VONAGE_PRIVATE_KEY_PATH
points correctly to where you savedprivate.key
..env
andprivate.key
to your.gitignore
file:Step 5: Initialize Vonage SDK (
src/config/vonageClient.js
)Create a configuration file to initialize the SDK instance using your Application ID and Private Key.
This setup reads the private key file and uses the Application ID for authentication, which is the standard way for the Messages API. We also added basic checks to ensure critical environment variables are present.
3. Implementing the Scheduling Logic
We'll use
node-cron
to run tasks at specific times and a simple in-memory object to keep track of scheduled jobs.Important Caveat: This in-memory storage is not suitable for production. If the server restarts, all scheduled jobs will be lost. For production, use a persistent store like Redis or a database, potentially with a dedicated job queue library (e.g., BullMQ, Agenda).
Step 1: Create Scheduler Service (
src/services/schedulerService.js
)This service will manage the scheduling and storage of jobs.
Explanation:
scheduledJobs
: An object acting as our simple in-memory database. Keys arejobId
, values contain thenode-cron
task object and job details.scheduleSms
:Date
object (sendAt
).sendAt
is a valid future date.jobId
usinguuid
.cron.schedule(sendAt, ...)
to schedule the task directly for the specified date and time. Crucially, we provide aDate
object here, whichnode-cron
supports for one-off tasks.cron.schedule
is what runs atsendAt
. It calls oursmsService.sendSms
function (defined next).scheduledJobs
after execution usingfinally
.timezone
explicitly (UTC is recommended for servers).scheduledJobs
.jobId
.cancelSms
: Finds a job by ID, stops the underlyingcron
task usingtask.stop()
, and removes it from memory.listScheduledJobs
: Returns details of all pending jobs (useful for an admin/status endpoint).4. Implementing the SMS Sending Service
This service encapsulates the interaction with the Vonage SDK to send the SMS.
Step 1: Create SMS Service (
src/services/smsService.js
)Explanation:
vonage
client instance.VONAGE_SMS_FROM_NUMBER
from environment variables and exits if not set.sendSms
function isasync
as the SDK call is asynchronous (vonage.messages.send
returns a Promise).vonage.messages.send
with the required parameters for SMS:channel
,message_type
,to
,from
,text
.try...catch
for error handling. It logs detailed errors (including response data from Vonage if available) and rethrows a generic error to signal failure to the calling function (the scheduler).message_uuid
returned by Vonage, which is useful for tracking.5. Building the API Layer (Express Route)
Now, let's create the Express route that will use our scheduler service.
Step 1: Create Schedule Route (
src/routes/scheduleRoutes.js
)Explanation:
express.Router
and ourschedulerService
.POST /
route:recipient
,message
, andsendAt
from the request body (req.body
).sendAt
is a valid ISO 8601 date string that represents a future time. For production, use a dedicated validation library likeexpress-validator
orjoi
.schedulerService.scheduleSms
with the validated data.202 Accepted
status code (appropriate for tasks accepted for later processing) and thejobId
. Note: The example response omits themessage
field for security reasons; returning potentially sensitive message content in API responses is generally discouraged in production.GET /
route: CallsschedulerService.listScheduledJobs
to return details of pending jobs.DELETE /:jobId
route: ExtractsjobId
from URL parameters (req.params
), callsschedulerService.cancelSms
, and returns success or 404 if the job wasn't found/cancelled.Step 2: Mount the Router in
app.js
Update
src/app.js
to use this router.We added the
scheduleRoutes
under the/api/schedule
path and included a basic global error handler middleware.6. Error Handling, Logging, and Retries
While basic error handling is included, production apps need more robustness.
app.js
) to catch unhandled errors. Define custom error classes if needed (e.g.,ValidationError
,NotFoundError
).console.log
/console.error
with a structured logger likepino
orwinston
. This enables different log levels (debug, info, warn, error), formatting (JSON), and sending logs to files or external services.pino
:npm install pino pino-pretty
schedulerService
logs errors but doesn't retry. For critical reminders, implement a retry mechanism within thecatch
block of thecron.schedule
task function.for
loop with delays.async-retry
can simplify this.7. Adding Security Features
express-validator
,joi
) for robust validation ofrecipient
(E.164 format),message
(length, content), andsendAt
. Sanitize inputs to prevent injection attacks (though less critical for SMS content itself, it's good practice)./api/schedule
endpoint from abuse. Useexpress-rate-limit
.helmet
(npm install helmet
) to set various HTTP headers for security (XSS protection, disabling content sniffing, etc.).8. Database Schema and Persistence (Production Consideration)
As highlighted, the in-memory
scheduledJobs
store is not persistent. For production:ScheduledJobs
with fields like:jobId
(Primary Key, UUID)recipient
(String)message
(Text)sendAt
(Timestamp/DateTime)status
(Enum: 'PENDING', 'SENT', 'FAILED', 'CANCELLED')createdAt
(Timestamp)updatedAt
(Timestamp)vonageMessageUuid
(String, nullable - store after successful send)failureReason
(Text, nullable)retryCount
(Integer, default 0)BullMQ
(Redis-based) orAgenda
(MongoDB-based) handle job persistence, scheduling, retries, concurrency, and worker processes more reliably thannode-cron
with manual persistence logic. They often integrate better with database storage.9. Verification and Testing
Step 1: Manual Verification (
curl
/ Postman)Start the server:
npm start
Schedule an SMS: Send a POST request to
http://localhost:3000/api/schedule
.curl
:YOUR_PERSONAL_PHONE_NUMBER
and adjust thesendAt
time to be a minute or two in the future using ISO 8601 format - UTC 'Z' or specify offset).Check Response: You should get a
202 Accepted
response with ajobId
.Check Logs: Monitor the console output where
npm start
is running. You should see logs for scheduling, and then later for execution and sending (or failure).Check Phone: Verify that you receive the SMS on
YOUR_PERSONAL_PHONE_NUMBER
at approximately the scheduled time.Step 2: List Pending Jobs
Send a GET request to
http://localhost:3000/api/schedule
. You should see the job you just scheduled (until it executes).Step 3: Cancel a Job
jobId
from the response.http://localhost:3000/api/schedule/YOUR_JOB_ID
./api/schedule
); the cancelled job should be gone.Step 4: Unit & Integration Tests (Recommended)
For robust applications, write automated tests:
jest
,mocha
): Test individual functions in isolation. Mock dependencies likenode-cron
and thevonage
SDK. Test validation logic, date parsing, error handling within services.supertest
): Test the API endpoints. Start the Express server, send HTTP requests usingsupertest
, and assert responses. Mock external dependencies (vonage
SDK) to avoid actual API calls.