Frequently Asked Questions
You can schedule SMS reminders using Node.js by building an application with Fastify, Prisma, and the Sinch SMS API. The application stores reminder details in a PostgreSQL database and uses node-cron to trigger messages via Sinch at the specified time. This setup ensures reliable delivery of time-sensitive communications.
The Sinch SMS API is used to send the actual SMS messages to recipients. The application integrates with Sinch via the @sinch/sdk-core package, allowing you to send messages globally. This simplifies the process of sending SMS messages from your Node.js application.
Fastify is a high-performance web framework known for its speed and extensible plugin architecture. Its efficiency makes it ideal for handling API requests and integrating with external services like the Sinch SMS API and PostgreSQL database.
A production-ready SMS scheduler is ideal when you need reliable, automated delivery of SMS messages at specific times. Use cases include appointment reminders, notifications, marketing campaigns, and other time-sensitive communications requiring accurate scheduling and error management.
The tutorial utilizes PostgreSQL, a powerful and reliable open-source relational database, to store the SMS reminder details. This is combined with Prisma, an ORM that simplifies database interactions and ensures type safety within your Node.js application.
The tutorial provides step-by-step instructions for setting up a Sinch SMS scheduler with Node.js. This involves installing necessary packages like Fastify, @sinch/sdk-core, and Prisma, configuring environment variables with your Sinch credentials, setting up the database schema, and creating the scheduler job.
Prisma is used as an Object-Relational Mapper (ORM) to simplify interactions with the PostgreSQL database. It streamlines database access, schema migrations, and provides type safety, making database operations cleaner and more efficient.
Node-cron is a task scheduler that enables the application to periodically check the database for due reminders. It's crucial for the automation aspect of the project, as it ensures reminders are sent at the correct scheduled times.
Error handling is implemented throughout the application using try...catch blocks and strategic logging. This includes handling potential errors during API requests, database interactions, and SMS sending via Sinch, ensuring the application remains stable and informative during issues.
Yes, you can modify the scheduler's frequency by changing the CRON_SCHEDULE environment variable. The default value is ' *', which means the scheduler runs every minute, but you can customize it using standard cron syntax.
While the provided example doesn't include automatic retries, you can implement this by adding a retryCount field to the database schema and modifying the scheduler to check and retry failed messages up to a maximum retry limit. For advanced retry logic, consider using a dedicated job queue library.
Node.js version 20.0.0 or later is required for this project. This is primarily because Fastify v5, a key dependency used as the web framework, necessitates Node.js 20 or higher to work properly.
Pino is the logger used by Fastify, offering efficient structured logging capabilities. Pino-pretty is a development dependency that formats Pino's JSON output into a human-readable format, making debugging easier. In production, pino is usually configured for JSON output compatible with logging systems.
Sinch credentials (Project ID, Key ID, and Key Secret) should be stored in a .env file in your project's root directory. Never commit this file to version control. The application loads these credentials from the .env file during startup. Obtain your credentials from your Sinch Customer Dashboard under Access Keys and Numbers > Your Numbers or SMS > Service Plans for the Sinch number.
This guide provides a step-by-step walkthrough for building a robust SMS scheduling and reminder application using Node.js, the Fastify web framework, the Sinch SMS API, and PostgreSQL with Prisma. You'll learn how to create an API endpoint to schedule reminders, store them in a database, and use a background job to send SMS messages via Sinch at the designated time.
We aim to create a reliable system capable of handling scheduled messages, including proper error handling, logging, and configuration management, suitable for production environments.
Project Overview and Goals
What We'll Build:
POST /reminders) to accept SMS reminder requests (recipient number, message, scheduled time).node-cron) to periodically check for due reminders.@sinch/sdk-coreto send the SMS messages.Problem Solved:
This application addresses the need to reliably send SMS messages at a future specified time, a common requirement for appointment reminders, notifications, marketing campaigns, and other time-sensitive communications.
Technologies Used:
@sinch/sdk-core): Provides the functionality to send SMS messages globally. Chosen for its direct integration capabilities via the Node.js SDK.node-cron: A simple cron-like job scheduler for Node.js. Chosen for its ease of use in running periodic tasks.dotenv: A zero-dependency module that loads environment variables from a.envfile. Essential for managing configuration and secrets securely.pino-pretty: A development dependency to make Pino logs human-readable.System Architecture:
Prerequisites:
curlor a similar tool (like Postman) for testing the API.Final Outcome:
By the end of this guide, you will have a functional Node.js application that can:
1. Setting up the Project
Let's initialize the Node.js project, install dependencies, and configure the basic structure.
1. Create Project Directory:
Open your terminal and create a new directory for the project.
2. Initialize Node.js Project:
This creates a
package.jsonfile with default settings.3. Install Dependencies:
We need several packages for our application:
fastify: The web framework.@sinch/sdk-core: The official Sinch Node.js SDK.dotenv: Loads environment variables from.env.@prisma/client: The Prisma database client.node-cron: The job scheduler.pino: Fastify's default logger.@fastify/rate-limit: Plugin for API rate limiting.prisma: The Prisma CLI (development dependency).pino-pretty: Makes logs readable during development.nodemon: Automatically restarts the server on file changes during development.4. Configure
package.jsonScripts:Open your
package.jsonfile and add/modify thescriptssection:start: Runs the application in production.dev: Runs the application usingnodemonfor auto-reloading and pipes logs throughpino-pretty.prisma:migrate:dev: Creates and applies database migrations during development.prisma:generate: Generates the Prisma Client based on your schema.prisma:deploy: Applies pending migrations in production environments."type": "module": Allows us to useimport/exportsyntax instead ofrequire.5. Initialize Prisma:
Set up Prisma to manage our database connection and schema.
This command does two things:
prismadirectory with a basicschema.prismafile..envfile (if it doesn't exist) and adds aDATABASE_URLplaceholder.6. Configure Environment Variables (
.env):Open the
.envfile created by Prisma (or create one if it doesn't exist) and add the following variables. Never commit this file to version control.DATABASE_URL: Replace the placeholder with your actual PostgreSQL connection string.SINCH_PROJECT_ID,SINCH_KEY_ID,SINCH_KEY_SECRET: Obtain these from your Sinch Customer Dashboard under Access Keys. Navigate to your Sinch account > APIs > Access Keys. Create a new key if needed.SINCH_NUMBER: This is the phone number or Service Plan ID you will send SMS from. You can find assigned numbers or create Service Plan IDs in your Sinch Customer Dashboard. For SMS, navigate to Numbers > Your Numbers or SMS > Service Plans.PORT: The port your Fastify server will listen on.NODE_ENV: Controls application behavior (e.g., logging).CRON_SCHEDULE: Defines how often the scheduler job runs.* * * * *means every minute.ENABLE_SCHEDULER: Allows disabling the scheduler via environment variable.7. Create Project Structure:
Organize your code for better maintainability. Create the following directories:
src/: Contains the main application code.src/routes/: Holds API route definitions.src/services/: Contains modules for interacting with external services (like Sinch) or database logic.src/jobs/: Contains background job definitions (like the scheduler).Your project structure should now look similar to this:
Ensure
.envandnode_modules/are listed in your.gitignorefile.2. Creating a Database Schema and Data Layer
We'll use Prisma to define our database schema and interact with the database.
1. Define Prisma Schema:
Open
prisma/schema.prismaand define the model for storing reminder information.id: Unique identifier using CUID.phoneNumber: Stores the recipient's number. Store in E.164 format (e.g.,+14155552671) for consistency.message: The text of the SMS.scheduledAt: The core field for scheduling. Store this in UTC to avoid timezone issues. The application logic should handle conversion if necessary.status: Tracks the reminder's state using a Prismaenum. Defaults toPENDING.sentAt: Records when the message was processed.error: Stores details if sending fails.createdAt,updatedAt: Standard timestamp fields.@@index([status, scheduledAt]): Crucial for performance. The scheduler job will query based on these fields, so an index significantly speeds this up.2. Apply Database Migration:
Run the Prisma migrate command to create the initial migration files and apply the changes to your database.
prisma/migrations/directory.DATABASE_URL.prisma generateautomatically.3. Initialize Prisma Client:
Create a reusable Prisma Client instance. Create
src/services/prisma.js:This centralizes the Prisma Client initialization.
3. Implementing Proper Error Handling, Logging, and Retry Mechanisms
Robust logging and error handling are essential for production applications.
1. Setup Centralized Logger:
Fastify uses Pino for logging. Let's configure it properly. Create
src/services/logger.js:NODE_ENV.pino-prettyonly in development for readability. In production, standard JSON logs are usually preferred for log aggregation systems.2. Integrate Logger with Fastify:
Modify the main server file (
server.js, which we'll create fully in Section 7) to use this logger instance. Fastify v4+ uses theloggeroption passed as an instance.3. Error Handling Strategy:
try...catchin handlers or Fastify'ssetErrorHandler.try...catcharound its main logic (fetching reminders, sending SMS) to prevent the entire scheduler process from crashing due to a single failed reminder. Log errors and update the reminder status toFAILED.4. Retry Mechanisms (Conceptual):
Directly implementing complex retry logic with exponential backoff can add significant complexity. For this guide, we'll keep it simple:
statusis set toFAILED, and the error is logged. The scheduler will not automatically retry that specific message in the next run.retryCountfield to theRemindermodel.FAILEDreminders withretryCount < MAX_RETRIES.Example Error Logging (in
sendSms):We will log errors with context in
src/services/sinch.js(defined in the next section).4. Integrating with Sinch
Now_ let's create a service module to handle interactions with the Sinch SMS API.
1. Create Sinch Service Module:
Create
src/services/sinch.js:SinchClientinstance using credentials from.env.sendSmsFunction:to) and message (body) as arguments.sms.batches.send). Note: Always refer to the official@sinch/sdk-coredocumentation for the specific version you are using.try...catchblock for error handling.5. Building the API Layer
Let's create the Fastify API endpoint to schedule new reminders.
1. Define API Route:
Create
src/routes/reminders.js:bodyand the successfulresponse(status 201). Includes validation rules (required fields_ string formats_ date-time format_ phone number pattern). Corrected regex pattern.POST /remindersroute usingfastify.post.createReminderSchemato the route options. Fastify automatically validates the incoming request body and serializes the response according to the schema.scheduledAtis in the future.prismaclient tocreatea new reminder record.try...catchblock to handle potential database errors during creation.201 Createdstatus with the newly created reminder's details (filtered by the response schema). Returns appropriate error codes (400_ 500) on failure.6. Implementing Core Functionality (Scheduler Job)
Now_ let's create the background job that checks for due reminders and triggers the SMS sending.
1. Create Scheduler Job:
Create
src/jobs/scheduler.js:node-cronto schedule theprocessDueRemindersfunction based onCRON_SCHEDULEfrom.env. Runs in UTC.isJobRunningflag. Note: This simple flag prevents overlap on a single instance but does not handle concurrency across multiple application instances in a distributed environment. More robust solutions like database advisory locks or dedicated job queues (e.g._ BullMQ) are needed for multi-instance deployments.PENDINGreminders wherescheduledAtis less than or equal to the current time (now). Usestakefor batching andorderBy.sendSms_ determinesupdateData_ and updates the reminder status (SENTorFAILED) in the database.try...catchblocks around DB query_ individual SMS sending_ and individual DB updates. Usesfinallyto ensure the lock is released.7. Server Setup (
server.js)We need the main entry point to tie everything together. Create
server.jsin the root directory:.envusingdotenv.@fastify/rate-limitplugin and thereminderRoutes(prefixing them with/api). Usesawaitfor registration as it can be asynchronous.SIGINTandSIGTERMsignals to close the server and disconnect Prisma properly before exiting.asyncfunctionstartServerto listen on the configured port and host (0.0.0.0makes it accessible externally, adjust if needed).startScheduler()only ifENABLE_SCHEDULERis true.try...catcharoundfastify.listento handle startup errors.