Frequently Asked Questions
You can schedule SMS messages by sending a POST request to the /schedule endpoint of your NestJS application. This endpoint accepts a JSON payload with the recipient's phone number, the message content, and the desired send time. The message will then be stored in the database and sent at the specified time via the Vonage Messages API.
The Vonage Messages API is a versatile API that enables sending and receiving messages through various channels, including SMS. In this NestJS application, it's the core component responsible for delivering the scheduled SMS messages to the recipients' phones.
PostgreSQL is used as the database for this SMS scheduling application because it's a robust and reliable open-source relational database. It's well-suited for persisting scheduled jobs and ensuring that message data is stored securely and efficiently.
You should never set synchronize: true
in a production TypeORM configuration. While convenient for development, it can lead to data loss in production. Always use migrations to manage schema changes safely and predictably.
Yes, you can use Prisma as an alternative ORM for this project. The article mentions Prisma as a viable option, though the provided code examples demonstrate the setup with TypeORM.
Obtain your API key and secret from the Vonage API Dashboard. Create a Vonage application, download the private key, enable the Messages capability, and obtain the Application ID. Purchase a Vonage phone number, link it to your application, and set the Messages API as the default for sending SMS. Don't forget to configure the 10DLC for US numbers in production.
The @nestjs/schedule
module provides a declarative way to schedule tasks (cron jobs, timeouts, intervals) within a NestJS application. This project uses it to periodically check for and send SMS messages that are due.
The application tracks message status using an enum with states like PENDING, SENT, FAILED, and optionally PROCESSING. This allows monitoring the lifecycle of each scheduled message and implementing features like retries or error handling.
The VonageService
in the application uses the @vonage/server-sdk
to send SMS messages. The sendSms
method constructs a request object with recipient, sender, and message details. The messages.send
method of the SDK then handles the actual sending process via the Vonage Messages API. This method returns a message ID or an error. You can use this ID to later query the message status via the Vonage API or Dashboard.
First, install the NestJS CLI and create a new project using nest new
. Install required dependencies like @nestjs/config
, @nestjs/schedule
, typeorm
, @vonage/server-sdk
. Set up environment variables in a .env
file. Configure the TypeORM connection and generate migrations to create database tables. Initialize the Vonage and scheduling modules and services.
NestJS provides the @nestjs/config
module to manage environment variables. You can create a .env
file to store configuration values and load them into the ConfigService
. Never commit this file to source control.
class-validator
and class-transformer
are used for robust request data validation. They ensure incoming data conforms to the expected format and data types, preventing common security and data integrity issues.
You'll need Node.js (LTS recommended), npm or yarn, a Vonage API account with a purchased number, access to a PostgreSQL database, basic command-line familiarity, and optionally Docker for containerization.
The article provides an example of a locking mechanism using a PROCESSING status. When a message is picked up by the scheduler, its status is updated to PROCESSING before sending. If another scheduler instance attempts to process the same message, it will detect the changed status and skip the message.
This guide provides a step-by-step walkthrough for building a robust SMS scheduling and reminder system using the NestJS framework and the Vonage Messages API. We will cover everything from initial project setup to deployment considerations, enabling you to reliably send time-sensitive SMS notifications.
By the end of this tutorial, you will have a NestJS application capable of:
This guide assumes you have a basic understanding of Node.js, TypeScript, and REST APIs. Familiarity with the NestJS framework is helpful but not strictly required, as we will explain concepts along the way.
Project Overview and Goals
Problem: Many applications need to send notifications or reminders via SMS at specific future times – appointment reminders, event notifications, follow-up messages, etc. Building this reliably requires careful handling of scheduling logic, persistence, and integration with an SMS provider.
Solution: We will build a dedicated NestJS service that exposes an API endpoint to schedule SMS messages. It will use a database (PostgreSQL) to store these scheduled messages and employ a task scheduler (
@nestjs/schedule
) to periodically check for and send messages that are due, leveraging the Vonage Messages API for delivery.Technologies:
@vonage/server-sdk
).@nestjs/schedule
: A built-in NestJS module for declarative task scheduling (cron jobs, timeouts, intervals).@nestjs/config
: For managing environment variables securely and efficiently.class-validator
&class-transformer
: For robust request data validation.System Architecture:
Prerequisites:
1. Setting up the Project
Let's initialize our NestJS project and install the necessary dependencies.
1.1 Install NestJS CLI:
If you don't have the NestJS CLI installed globally, run:
1.2 Create New NestJS Project:
Choose your preferred package manager (npm or yarn) when prompted.
1.3 Install Dependencies:
We need several packages for configuration, database interaction, scheduling, validation, and Vonage integration.
1.4 Configure Environment Variables:
Create a
.env
file in the project root. This file will store sensitive credentials and configuration settings. Never commit this file to version control. Add a.gitignore
entry for.env
if it's not already there.TZ=UTC
is crucial for consistent scheduling across different server environments.1.5 Set up Configuration Module:
NestJS provides a
ConfigModule
to load and manage environment variables.Modify
src/app.module.ts
:1.6 Basic Project Structure:
NestJS encourages a modular structure. We'll create modules for specific features:
scheduling
: Handles API endpoints, scheduling logic, and database interaction for messages.vonage
: Encapsulates interaction with the Vonage SDK.(We will create the files for these modules in the relevant sections).
2. Creating a Database Schema and Data Layer
We need a way to store the scheduled SMS messages. We'll use TypeORM and PostgreSQL.
2.1 Define the Entity:
Create a file for our database entity.
sendAt
stores the exact time the message should be sent (usingtimestamp with time zone
is crucial).status
tracks the message state.sendAt
andstatus
are vital for efficient querying by the scheduler.2.2 Configure TypeORM:
Update
src/app.module.ts
to configure and importTypeOrmModule
.TypeOrmModule.forRootAsync
to injectConfigService
and read database credentials from.env
.synchronize: false
is crucial for production. We'll use migrations.rejectUnauthorized: false
setting for SSL is insecure and should not be used in production without understanding the risks. Proper certificate validation is strongly recommended. Consult your database provider's documentation for secure SSL configuration.2.3 Set up Migrations (Recommended):
Create a TypeORM configuration file for the CLI.
Add scripts to your
package.json
:tsconfig.json
hasoutDir
set (usually""./dist""
).ts-node
andtsconfig-paths
installed (npm install --save-dev ts-node tsconfig-paths
).Now you can generate and run migrations:
npm run build
(to create thedist
folder)npm run migration:generate --name=InitialSchema
(ReplaceInitialSchema
with a descriptive name)src/migrations
. Review it carefully.npm run migration:run
(Applies pending migrations to the database)3. Integrating with Vonage
We need a dedicated service to handle communication with the Vonage API.
3.1 Create Vonage Module and Service:
3.2 Implement Vonage Service:
onModuleInit
using credentials fromConfigService
.sendSms
method constructs the request payload for the Messages API and handles potential errors, logging success or failure.3.3 Update Vonage Module:
Make the
VonageService
available for injection.3.4 Import VonageModule in AppModule:
4. Obtaining and Configuring Vonage Credentials
Follow these steps carefully in your Vonage API Dashboard:
API Key and Secret:
API key
andAPI secret
are displayed prominently on the main dashboard page (""Getting started"" section)..env
file forVONAGE_API_KEY
andVONAGE_API_SECRET
.Create a Vonage Application:
private.key
file that downloads. Place this file in your project root (or the path specified inVONAGE_PRIVATE_KEY_PATH
in your.env
). The public key remains with Vonage.https://example.com/vonage/status
,https://example.com/vonage/inbound
). If using ngrok for local testing of webhooks later:YOUR_NGROK_URL/webhooks/status
andYOUR_NGROK_URL/webhooks/inbound
..env
file forVONAGE_APPLICATION_ID
.Purchase and Link a Phone Number:
+12015550123
) and paste it into your.env
file forVONAGE_FROM_NUMBER
.Set Default SMS API (Important):
Your
.env
file should now be populated with valid Vonage credentials.5. Implementing Core Functionality (Scheduling Logic)
Now, let's build the module responsible for scheduling and processing the messages.
5.1 Create Scheduling Module, Service, and Controller:
5.2 Create Data Transfer Object (DTO) for API Requests:
We need a class to define the expected structure and validation rules for incoming schedule requests.
@IsPhoneNumber(null)
validates E.164 format (e.g.,+15551234567
).@Type(() => Date)
and@IsDate()
ensuresendAt
is a valid date.@MinDate(new Date())
prevents scheduling messages in the past.5.3 Implement Scheduling Service:
This service handles saving schedules to the database and processing due messages.
scheduleSms
: Creates and saves a newScheduledMessage
entity withPENDING
status.handleCron
: Decorated with@Cron
, this method runs automatically based on theCronExpression
(e.g.,EVERY_MINUTE
). It callsprocessDueMessages
.processDueMessages
:PENDING
messages wheresendAt
is less than or equal to the current time.LessThanOrEqual
from TypeORM.take: 100
).vonageService.sendSms
.SENT
orFAILED
based on the Vonage API response.PROCESSING
) needed to prevent race conditions if running multiple instances of the scheduler service.getMessageStatus
: Simple method to retrieve a message by ID.5.4 Implement Scheduling Controller (API Layer):
This controller exposes the HTTP endpoint to schedule messages.
@Controller('schedule')
to define the base route/schedule
.scheduleSms
method handlesPOST /schedule
requests.@UsePipes(new ValidationPipe(...))
automatically validates the incoming request body against theScheduleSmsDto
.transform: true
enables automatic conversion (like string to Date).whitelist: true
strips properties not defined in the DTO.@HttpCode(HttpStatus.ACCEPTED)
returns a 202 status code, indicating the request is accepted for processing but not yet complete.schedulingService.scheduleSms
and returns a success response with the new schedule ID.getStatus
method handlesGET /schedule/:id/status
requests to check a message's state.5.5 Update Scheduling Module:
Tie everything together in the module definition.
TypeOrmModule.forFeature()
to register theScheduledMessage
entity and make its repository injectable within this module.VonageModule
to gain access to theVonageService
.ConfigModule
ifConfigService
is needed directly within this module's providers (though often it's accessed via imported services likeVonageService
).5.6 Import SchedulingModule in AppModule:
Finally, import the
SchedulingModule
into the mainAppModule
.At this point, the core scheduling and sending logic is implemented. You should be able to start the application (
npm run start:dev
), send POST requests to/schedule
, and see messages being processed by the cron job and sent via Vonage.