Frequently Asked Questions
Use the `@nestjs/schedule` module with a cron job to trigger the `RemindersService` to check for due reminders and send them via the Plivo API. The cron job's frequency is configurable via the `REMINDER_CHECK_CRON_TIME` environment variable. The service queries the database for pending reminders and updates their status as they're processed.
Plivo is a cloud communications platform that provides the SMS sending functionality. The NestJS application integrates with Plivo's API using the official Plivo Node.js SDK to send SMS messages based on scheduled reminders stored in the database.
TypeORM simplifies database interactions by providing an object-relational mapper (ORM) for TypeScript. It allows you to define entities and interact with the database using familiar JavaScript methods, while PostgreSQL offers a robust and reliable database solution.
The `synchronize: true` setting in TypeORM should *always* be set to `false` in a production environment. While convenient for development, it can lead to data loss. Use TypeORM migrations to manage schema changes safely in production.
Yes, you can potentially adapt the code to use a different database. TypeORM supports various databases. You would need to change the `type` in the `TypeOrmModule` configuration and install the appropriate database driver for TypeORM.
Store your Plivo Auth ID, Auth Token, and sender number in a `.env` file. This file should be added to your `.gitignore` to prevent it from being committed to version control. The `@nestjs/config` module loads these variables into your application's environment.
These libraries provide robust validation and transformation for incoming request data in NestJS. `class-validator` allows you to define validation rules using decorators in your DTOs (Data Transfer Objects), while `class-transformer` automatically transforms request payloads into DTO instances.
The cron schedule is defined by the `REMINDER_CHECK_CRON_TIME` environment variable. Use cron expression syntax to specify the frequency. For example, `* * * * *` represents every minute, and `0 0 * * *` represents every day at midnight. See crontab.guru for help with cron syntax.
If Plivo's API returns an error during SMS sending, the application logs the error, updates the reminder's status to `FAILED`, and stores the error message in the database. An optional `retryFailedReminder` method can be used to attempt resending failed messages.
The article recommends using an LTS (Long-Term Support) version of Node.js for this project. LTS versions receive security updates and bug fixes for an extended period, ensuring stability and compatibility.
Migrations are managed using the TypeORM CLI. After installing it (`npm install -g typeorm`), you can generate migrations with `npm run migration:generate --name=YourMigrationName` and run them with `npm run migration:run`. Ensure you build the project first with `npm run build` to compile the migration files.
The `PROCESSING` status is used to prevent race conditions in a multi-instance deployment, ensuring that a reminder is not picked up and processed multiple times concurrently. The cron job acquires a lock on pending reminders by setting their status to `PROCESSING` before sending the SMS.
NestJS modules promote modularity and organization. Features like Plivo integration, database interaction, and reminder management are separated into distinct modules. This improves maintainability and testability.
The `sendAt` field in the `Reminder` entity uses the `timestamp with time zone` data type in PostgreSQL to store dates with timezone awareness. It is crucial to consistently store dates in UTC within your database to avoid timezone-related issues.
Your Plivo Auth ID and Auth Token can be found in your Plivo console. After logging in, they are displayed on the main dashboard. You'll also need a Plivo phone number to send SMS messages from, which can be purchased or found in the 'Numbers' section under 'Messaging'.
Automated reminders and scheduled notifications are crucial for user engagement, appointment management, and timely communication. Building a reliable system to handle this requires careful consideration of scheduling, third-party integration, error handling, and scalability.
This guide provides a complete walkthrough for building a robust SMS scheduling and reminder application using the NestJS framework and the Plivo communications API. We will cover everything from initial project setup to deployment and monitoring, equipping you with the knowledge to build a production-grade solution.
What We'll Build:
@nestjs/schedule
to check for due messages.Technologies Used:
@nestjs/schedule
: A NestJS module for handling cron jobs, timeouts, and intervals declaratively. Ideal for periodically checking for due reminders.@nestjs/config
: For managing environment variables securely and efficiently.class-validator
&class-transformer
: For robust request payload validation.Prerequisites:
System Architecture:
This diagram illustrates the flow: a client sends a request to the NestJS API to create a reminder. The
RemindersService
validates and saves it to the database. A separate scheduled job (@nestjs/schedule
) periodically queries the database via theRemindersService
for due reminders. If found, it uses thePlivoService
(which securely accesses API keys viaConfigService
) to call the Plivo API, sending the SMS to the user's phone.1. Setting up the Project
Let's initialize our NestJS project and install the necessary dependencies.
Create a new NestJS project:
Install Dependencies: We need modules for scheduling, configuration, Plivo integration, database interaction (TypeORM, PostgreSQL driver), and validation.
@nestjs/schedule
: For scheduling tasks.@nestjs/config
: For managing environment variables.plivo
: The official Plivo Node.js SDK.typeorm
,@nestjs/typeorm
,pg
: For database interaction with PostgreSQL using TypeORM.class-validator
,class-transformer
: For validating incoming request data.@types/cron
: Type definitions for the underlying cron library used by@nestjs/schedule
.Environment Variables Setup: Create a
.env
file in the project root for storing sensitive information and configuration. Never commit this file to version control. Add it to your.gitignore
file if it's not already there.Messaging
->Phone Numbers
->Numbers
in the left sidebar. Purchase or use an existing Plivo number. Copy the full number including the country code (e.g.,+12025550144
). This is yourPLIVO_SENDER_NUMBER
.scheduler_db
in this example) exists on your PostgreSQL server before running the application or migrations. You may need to create it manually (e.g., usingCREATE DATABASE scheduler_db;
).Configure Modules: Import the necessary modules into your main application module (
src/app.module.ts
).ConfigModule.forRoot
globally to access.env
variables anywhere.ScheduleModule.forRoot
enables task scheduling.TypeOrmModule.forRootAsync
sets up the database connection using credentials fromConfigService
.synchronize: true
is convenient for development as it automatically updates the schema. In production, disable it (synchronize: false
) and use TypeORM Migrations to manage schema changes safely.RemindersModule
andPlivoModule
.ValidationPipe
is configured to automatically validate incoming request bodies based on DTOs.Update
main.ts
: Enable shutdown hooks for graceful termination and set the application port from the environment variable.2. Creating a Database Schema and Data Layer
We need a way to store the reminders. We'll use TypeORM to define an entity and interact with the PostgreSQL database.
Define the
Reminder
Entity: Create the entity file that maps to our database table.sendAt
), and the status (PENDING
,SENT
,FAILED
,PROCESSING
).sendAt
usestimestamp with time zone
to correctly handle different time zones. Always store dates in UTC in your database.sendAt
andstatus
for efficient querying by the scheduler.plivoMessageUuid
anderrorMessage
are added for tracking and debugging.PROCESSING
status is optional but recommended to prevent a reminder from being picked up by multiple scheduler instances if the processing takes time.Create the
RemindersModule
: Generate the module, controller, and service using the Nest CLI.--flat
prevents the CLI from creating unnecessary subdirectories withinsrc/reminders
.Register Entity and Set Up Repository: Import
TypeOrmModule
intoRemindersModule
to make theReminder
repository available for injection.(Production) Setting up Migrations (Recommended): As mentioned,
synchronize: true
is unsafe for production. Use TypeORM migrations instead.npm install -g typeorm
(or usenpx typeorm
)package.json
:src/data-source.ts
): This file tells the TypeORM CLI how to connect to your database.src/migrations
. Review it.TypeOrmModule.forRootAsync
: Update theentities
andmigrations
paths to point to the compiled output (dist
) and setsynchronize: false
. Note: Using__dirname
relies on the compiled file structure; verify paths based on your build output.3. Integrating with Plivo
Let's create a dedicated module and service to handle interactions with the Plivo API.
Create the
PlivoModule
:Implement
PlivoService
: This service will initialize the Plivo client and provide a method to send SMS messages.OnModuleInit
to initialize the Plivo client once the module is ready.ConfigService
.sendSms
method wraps theclient.messages.create
call, performs basic E.164 validation, logs actions, and handles potential API errors gracefully, returning the message UUID or an error message.Register
ConfigService
inPlivoModule
:4. Implementing Core Functionality (Scheduling and Sending)
Now, let's implement the service logic for creating reminders and the scheduled task to send them.
Implement
RemindersService
: Inject theReminder
repository andPlivoService
. Add methods to create reminders and process due ones.create
: Takes validated DTO data, creates aReminder
entity instance (ensuringsendAt
is aDate
), sets the initial status toPENDING
, and saves it using the repository.handleCron
:@Cron
to run based on theREMINDER_CHECK_CRON_TIME
environment variable (defaulting to every minute). Important: The@Cron
decorator readsprocess.env
directly when the application loads, not thethis.reminderCheckCronTime
instance variable dynamically.this.reminderRepository.manager.transaction
) for finding and updating reminders to prevent race conditions in multi-instance deployments.status: ReminderStatus.PENDING
) whosesendAt
time is less than or equal to the current time (now
). It processes in batches (take: 100
) and prioritizes older reminders (order: { sendAt: 'ASC' }
).PROCESSING
within the transaction. It checks if the number of affected rows matches the number found to detect potential concurrency issues where another instance might have processed some reminders between thefind
andupdate
steps.plivoService.sendSms
.SENT
(with Plivo message UUID) orFAILED
(with an error message).findAll
,findOne
,remove
: Standard CRUD methods for managing reminders via the API, including logging andNotFoundException
handling.retryFailedReminder
: An optional method to reset aFAILED
reminder back toPENDING
so the cron job can attempt to send it again. Includes checks to ensure the reminder is actually in aFAILED
state.