Frequently Asked Questions
You can schedule SMS messages by sending a POST request to the `/schedule-sms` endpoint of your Fastify application. This endpoint expects a JSON payload with the recipient's number (`to`), the message content (`message`), and the scheduled send time (`sendAt` in ISO 8601 format).
The Vonage Messages API is a service that allows you to send messages through various channels, including SMS. In this project, it's the core component responsible for delivering the scheduled text messages after they are processed by the Fastify application and the scheduling service.
Fastify is a high-performance Node.js web framework chosen for its speed and extensibility. Its built-in schema validation and hook system make it well-suited for building robust and scalable API endpoints like the SMS scheduler described in the article.
An in-memory scheduler like `node-schedule` is suitable for simpler use cases or development where persistence isn't critical. For production systems, or when reliability is essential, a database-backed system with a queuing mechanism offers more robust scheduling and job management.
Yes, you can cancel a scheduled message by sending a DELETE request to `/schedule-sms/:jobId`, where `:jobId` is the unique identifier returned when you scheduled the message. This will remove the scheduled job from the in-memory store and it will not be sent.
You'll need a Vonage API key and secret, an Application ID, and a private key. Store these securely in a `.env` file in your project root, and ensure it's added to your `.gitignore` file to prevent accidentally committing credentials to version control.
`node-schedule` is a Node.js library that handles the scheduling logic. It allows the application to register a task (sending an SMS) to be executed at a specific future date and time, specified in the API request.
The example code demonstrates basic error handling using `try...catch` blocks around the `vonage.messages.send()` call. Production-ready systems should have robust error handling, including retry mechanisms and detailed logging of `err.response.data` from the Vonage API.
The `x-api-key` header provides a simple way to authenticate API requests. The provided key is compared against the `API_ACCESS_KEY` environment variable for authorization. This is a basic example; stronger authentication methods like JWT or OAuth are recommended for production.
The `scheduleSms` function checks for duplicate job IDs. If a job with the same ID already exists, it logs a warning and skips scheduling the new job. This behavior can be modified to allow updates or rescheduling if needed.
For production systems, using a message queue (like RabbitMQ or Redis) and a database to store job information is the recommended approach for retries. The queue manages the retry logic and the database maintains job state across restarts.
The article provides a conceptual database schema example using PostgreSQL. It includes fields for the job ID, recipient, message, send time, status, Vonage message UUID, timestamps, error details, and retry count. Adjust the schema to fit your specific needs and chosen database.
You can use tools like `curl` or Postman to send POST requests to the `/schedule-sms` endpoint. Be sure to include the necessary headers (like `x-api-key`) and a JSON body with the message details and a future `sendAt` time.
Graceful shutdown allows the scheduler to attempt to complete any currently running jobs before the server stops. This helps to ensure messages are sent even if the application is interrupted. It's especially crucial for in-memory schedulers that don't persist job information.
This guide provides a complete walkthrough for building a robust SMS scheduling and reminder application using Node.js with the Fastify framework and the Vonage Messages API. We'll cover everything from initial project setup to deployment considerations, focusing on creating a reliable system.
Project Goal: To create an API endpoint that accepts requests to send an SMS message at a specified future time. This system will be built with scalability and reliability in mind, although this specific implementation uses an in-memory scheduler suitable for simpler use cases or as a starting point.
Problem Solved: Automates the process of sending timely SMS notifications, reminders, or alerts without requiring manual intervention at the exact send time. Useful for appointment reminders, notification systems, marketing campaigns, and more.
Technologies Used:
@vonage/server-sdk
: The official Vonage Node.js library for easy API interaction.node-schedule
: A flexible job scheduler for Node.js (used for the core scheduling logic in this guide).dotenv
: To manage environment variables securely.pino-pretty
: To format Fastify's logs nicely during development.System Architecture:
Prerequisites:
ngrok
(optional, useful for testing incoming Vonage webhooks, such as delivery receipts or inbound messages, during local development if you extend this guide's functionality).npm install -g @vonage/cli
Final Outcome: A running Fastify application with a
/schedule-sms
endpoint that takes a recipient number, message content, and a future send time, schedules the SMS usingnode-schedule
, and sends it via the Vonage Messages API at the designated time.1. Setting up the project
Let's initialize our Node.js project, install dependencies, and configure the basic Fastify server and Vonage credentials.
1. Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
2. Initialize Node.js Project: Create a
package.json
file.3. Install Dependencies: We need Fastify, the Vonage SDK,
node-schedule
for scheduling, anddotenv
for environment variables. We'll also addpino-pretty
as a development dependency for readable logs.4. Configure
package.json
Start Script: Modify thescripts
section in yourpackage.json
to easily start the server, usingpino-pretty
for development logs.5. Create Project Structure: Organize the code for better maintainability.
6. Configure
.gitignore
: Prevent sensitive files and generated folders from being committed to version control.7. Set up Environment Variables (
.env
): Create a.env
file in the project root. This file will store your Vonage credentials and other configurations. Never commit this file to Git.8. Obtain Vonage Credentials and Set Up Application:
.env
file.private.key
file into your project's root directory (or a secure location referenced byVONAGE_PRIVATE_KEY_PATH
). The public key is stored by Vonage. For this local development guide, we save it to the project root (and ensure it's in.gitignore
). In production, never commit this file; use secure methods like environment variables or secret management systems.https://example.com/status
andhttps://example.com/inbound
. If you later want delivery receipts or inbound messages, you'll need to update these with publicly accessible endpoints (usingngrok
for local development)..env
file (VONAGE_APP_ID
).private.key
in the current directory):.env
file..env
file (VONAGE_NUMBER
).9. Basic Fastify Server Setup: Add the initial Fastify server code to
src/server.js
.You can now run
npm run dev
in your terminal. You should see log output indicating the server is listening, and visitinghttp://localhost:3000/health
should return{"status":"ok"}
. PressCtrl+C
to stop the server.2. Implementing Core Functionality (Scheduling Service)
We'll create a separate module to handle the scheduling logic using
node-schedule
and interact with the Vonage SDK.1. Create the Scheduler Module: Populate
src/scheduler.js
.Explanation:
node-schedule
,Vonage
SDK, andfs
.APP_ID
,PRIVATE_KEY_PATH
,NUMBER
). Exits if crucial variables are missing. API Key/Secret are passed to the SDK constructor but not strictly validated here as they aren't essential for the JWT auth path shown.Vonage
instance using the Application ID and Private Key method, which is generally preferred for server applications. It reads the private key file content.scheduledJobs
: An in-memory object to keep track of activenode-schedule
jobs. This is the core limitation – if the server restarts, all jobs in this object are lost.scheduleSms
:jobId
, recipient, message, andDate
object for the send time.jobId
already exists.schedule.scheduleJob(sendAt, callback)
to schedule the task.async
callback function:vonage.messages.send()
with the required SMS parameters (message_type
,to
,from
,channel
,text
).message_uuid
for tracking).err.response.data
). Note: Robust retry logic is complex for in-memory scheduling and often better handled by external queues.finally
block to remove the job fromscheduledJobs
after execution (or failure).node-schedule
job object inscheduledJobs
.true
on successful scheduling,false
otherwise.cancelSmsJob
: Allows cancelling a job by ID usingjob.cancel()
and removing it fromscheduledJobs
.SIGINT
(Ctrl+C) andSIGTERM
(common process termination signal) to callschedule.gracefulShutdown()
. This attempts to allow running jobs to complete before exiting.3. Building the API Layer
Now, let's create the Fastify route to accept scheduling requests.
1. Update
server.js
: Modifysrc/server.js
to include the scheduler, define the API route, add validation, and basic authentication.Explanation:
dotenv.config()
: Ensures environment variables are loaded first.randomUUID
: Imported for generating unique job IDs if the client doesn't provide one.scheduler.js
module is imported.onRequest
):x-api-key
header on all requests (except/health
).process.env.API_ACCESS_KEY
.401 Unauthorized
response and stops further processing for that request. This is basic; use proper auth in production.scheduleSmsSchema
):POST /schedule-sms
request body using Fastify's schema validation.to
,message
, andsendAt
as required.jobId
is optional.format: 'date-time'
forsendAt
, expecting an ISO 8601 string.202
) and errors (400
,500
). Fastify uses this for automatic validation and serialization./schedule-sms
Route (POST
):scheduleSmsSchema
for automatic validation.request.body
.sendAt
ISO string into aDate
object. Includes crucial validation to ensure it's a valid date and in the future.jobId
usingrandomUUID()
if one isn't provided in the request.scheduler.scheduleSms
with the validated data.202 Accepted
response if scheduling is successful, including thejobId
and the scheduled time.400 Bad Request
if the scheduler module returnsfalse
(e.g., duplicate ID).try...catch
block for unexpected errors during scheduling, returning a500 Internal Server Error
./schedule-sms/:jobId
Route (DELETE
): (Optional) Provides an endpoint to cancel a scheduled job using thecancelSmsJob
function from the scheduler module.setErrorHandler
): A catch-all handler for errors not caught within specific routes. Logs the error and sends a generic 500 response in production or more details in development.0.0.0.0
to be accessible from outside its container/machine in deployed environments.Testing the API Endpoint:
Start the server:
npm run dev
Use
curl
(or Postman/Insomnia) to send a request. Replace placeholders with your actual data and API key. Schedule it a minute or two in the future to test.Check your server logs for scheduling confirmation and execution logs when the time arrives. Verify the SMS is received on the target phone.
4. Integrating with Vonage (Covered in Section 2)
The core integration happens within
src/scheduler.js
where the@vonage/server-sdk
is initialized and used..env
file (VONAGE_APP_ID
,VONAGE_PRIVATE_KEY_PATH
,VONAGE_NUMBER
,VONAGE_API_KEY
,VONAGE_API_SECRET
).privateKey
is provided in the constructor.vonage.messages.send({...})
method is used within the scheduled job's callback. Key parameters:message_type: 'text'
to
: Recipient number (from API request)from
: Your Vonage number (from.env
)channel: 'sms'
text
: Message content (from API request).env
and accessed viaprocess.env
. Theprivate.key
file should also be treated as a secret and have appropriate file permissions (readable only by the application user). In production, consider injecting these via environment variables directly rather than a.env
file.5. Error Handling, Logging, and Retry Mechanisms
400 Bad Request
./schedule-sms
) and scheduler module (scheduleSms
). Invalid inputs or scheduler failures return400
. Unexpected errors return500
.try...catch
block of the scheduled job's callback inscheduler.js
. Errors are logged. The loggederr?.response?.data
often contains structured JSON error information directly from the Vonage API, which is very useful for debugging.setErrorHandler
for uncaught exceptions.logger: true
).pino-pretty
via thenpm run dev
script.scheduler.js
for scheduling, execution, success, and errors.node-schedule
callback (e.g., usingsetTimeout
in thecatch
block) is complex to manage correctly, can block the event loop if synchronous operations occur within the retry logic, and crucially, does not solve the fundamental problem of losing scheduled retries if the server restarts.6. Database Schema and Data Layer (Recommendation)
This guide uses an in-memory approach for simplicity. For any production system, persisting scheduled jobs is essential.
Why a Database is Crucial:
Conceptual Schema (e.g., PostgreSQL):
Implementation Approach (If adding DB):
pg
,node-postgres
,Sequelize
,Prisma
,Mongoose
).fastify-postgres
,@fastify/mongodb
).node-schedule
, create a database polling mechanism or use a queue.setInterval
or a cron job) queries thescheduled_sms
table for jobs wherestatus = 'scheduled'
andsend_at <= NOW()
.