Frequently Asked Questions
Use Next.js API routes to handle scheduling requests, store them in a database (like Vercel Postgres), and trigger sending via a cron job. The frontend UI collects recipient details, message, and scheduled time, while the backend manages storage and interaction with the Plivo SMS API.
Plivo is the cloud communications platform used to actually send the SMS messages. The Next.js app interacts with Plivo's API using the Plivo Node.js SDK, providing the recipient's phone number and message content.
Vercel Cron Jobs offer a serverless way to trigger the SMS sending process at specified intervals. The cron job calls a dedicated Next.js API route, which queries the database for pending messages and initiates sending via Plivo.
Use prisma db push
for initial setup and simple schema updates during development. For production environments or complex schema changes, use prisma migrate dev
(locally) and prisma migrate deploy
(in CI/CD) for safer, versioned migrations.
Create a Postgres database from the Vercel Dashboard Storage tab. Connect this database to your Vercel project, and then copy the provided POSTGRES_PRISMA_URL
and POSTGRES_URL_NON_POOLING
connection strings into your project's .env.local
file.
Prisma acts as the Object-Relational Mapper (ORM) simplifying database interactions. It enables type-safe data access, schema management, and handles the connection to the Vercel Postgres database.
After setting up a Plivo account and purchasing a number, use the Plivo Node.js SDK within your Next.js API route. Provide your Plivo Auth ID, Auth Token, sender number, recipient number, and message body to the client.messages.create
method.
The status
field in the Schedule
model tracks the state of each scheduled message (PENDING, SENT, or FAILED). This allows the cron job to identify which messages need processing and provides a record of successful and failed attempts.
The application stores all dates and times in UTC. The frontend handles time zone conversion, ensuring that the backend receives and stores times in UTC, preventing discrepancies and ensuring messages are sent at the correct time.
Yes, run npm run dev
to start the development server. Then use curl
, Postman, or Insomnia to send test POST requests to the API endpoint (/api/schedule
), providing the necessary data in JSON format.
Wrap your Plivo API calls in a try...catch
block. The sendPlivoSms
helper function already provides error handling and returns a result object with a success
flag and an optional error
message to handle specific issues.
Zod provides robust schema validation for incoming API requests, ensuring that data conforms to the expected format and preventing potential errors or security vulnerabilities from malformed data.
Store Plivo credentials (Auth ID, Auth Token), database URLs, and other secrets in environment variables (.env.local
). This file is automatically excluded from Git, preventing accidental exposure.
Vercel automatically captures console output from Serverless Functions and Cron Jobs. You can view these logs in your Vercel project dashboard under the deployments section for the corresponding function.
Build scheduled SMS reminders with Next.js and Plivo
This guide details how to build a production-ready application using Next.js and Plivo to schedule and automatically send SMS reminders. We'll cover everything from project setup and core logic implementation to deployment and monitoring.
The final application will enable users (or an admin) to define a phone number, a message, and a future date/time. At the scheduled time, the system will automatically send the specified message to the target phone number via SMS using the Plivo API. This is ideal for appointment reminders, event notifications, follow-ups, and more.
Project overview and goals
Goal: Create a reliable system for scheduling and sending SMS messages at specific future times.
Problem Solved: Automates the process of sending timely reminders, reducing no-shows for appointments, ensuring users receive critical information on time, and freeing up manual effort.
Technologies:
System Architecture:
(Note: The following diagram uses Mermaid syntax. Ensure your publishing platform supports Mermaid rendering, or replace this with a static image alternative.)
Prerequisites:
Final Outcome: A deployed Next.js application capable of accepting SMS scheduling requests via a simple UI or API, storing them, and reliably sending them at the scheduled time using Plivo and Vercel Cron Jobs.
1. Setting up the project
Let's initialize our Next.js project and install the necessary dependencies.
Create Next.js App: Open your terminal and run the following command, choosing options like TypeScript, Tailwind CSS, App Router, etc., when prompted.
Install Dependencies: Add Plivo, Prisma, and date-fns for date manipulation.
plivo
: Plivo Node.js SDK.@prisma/client
: Prisma database client.date-fns
&date-fns-tz
: Robust date/time handling and time zone support.zod
: Schema declaration and validation library.prisma
(dev): Prisma CLI for migrations and studio.Initialize Prisma: Set up Prisma with PostgreSQL as the provider.
This creates a
prisma
directory with aschema.prisma
file and a.env
file for your database connection string.Configure Environment Variables: Open the
.env
file (renamed automatically fromprisma/.env
to the project root bycreate-next-app
, or create.env.local
if preferred). Add placeholders for Plivo credentials and the database URL. We'll get the actual values later..env.local
is ignored by Git by default, keeping your secrets safe. Never commit files containing sensitive credentials.POSTGRES_PRISMA_URL
vsPOSTGRES_URL_NON_POOLING
: Vercel Postgres provides two URLs. The Prisma client uses the pooled connection (POSTGRES_PRISMA_URL
). Prisma Migrate needs the non-pooled version (POSTGRES_URL_NON_POOLING
). Ensure both are set, especially when deploying.CRON_SECRET
: A secret string you generate to verify that requests to your cron handler endpoint genuinely come from Vercel Cron Jobs.Project Structure: Your basic structure within the
app
directory will look like this:app/page.tsx
: Frontend UI for scheduling.app/api/schedule/route.ts
: API endpoint to handle schedule creation.app/api/cron/route.ts
: API endpoint triggered by Vercel Cron to send due messages.lib/
: Utility functions (Prisma client, Plivo client).prisma/schema.prisma
: Database schema definition.2. Implementing core functionality
We'll now build the key parts: the UI form, the API to save schedules, the Plivo integration, and the cron job logic.
2.1 Database Schema (
prisma/schema.prisma
)Define the model for storing scheduled SMS messages.
sendAt
: Stores the scheduled time in UTC to avoid time zone ambiguity.status
: Tracks the state of the scheduled message.plivoMessageId
: Useful for tracking the message status within Plivo if needed.error
: Logs any issues during sending.@@index
: Crucial for performance. The cron job frequently queries forPENDING
schedules around the current time.2.2 Prisma Client Setup (
lib/prisma.ts
)Create a reusable Prisma client instance.
2.3 Plivo Client Setup (
lib/plivo.ts
)Initialize the Plivo client and create a helper function for sending SMS.
try...catch
.messageUuid
or error message.response.messageUuid[0]
because the Plivo Node.js SDK returns the UUID(s) in an array.2.4 Scheduling UI (
app/page.tsx
)A simple form to create new schedules.
""use client"";
: Necessary for using React hooks (useState
) and event handlers.useState
for form inputs, loading state, and status messages.Intl.DateTimeFormat
.date-fns-tz
'szonedTimeToUtc
to convert the user's input date/time (parsed in their selected time zone) into a UTCDate
object before sending it to the backend API.fetch
to POST the schedule data to/api/schedule
.text-black
to inputs for visibility on light backgrounds; this might need adjustment for dark mode support.3. Building the API layer
We need an API endpoint to receive the schedule requests from the frontend (or other clients).
3.1 Schedule Creation API (
app/api/schedule/route.ts
)This Next.js Route Handler validates the incoming request and saves the schedule to the database.
route.ts
pattern.zod
to define a schema (scheduleSchema
) and validate the request body. This ensures the phone number format is basic E.164, the message isn't empty, andscheduledAt
is a valid ISO date string representing a future time. Returns specific error messages on failure. A note clarifies the basic nature of the regex and suggestslibphonenumber-js
for better validation.prisma
client tocreate
a new schedule record.scheduledAt
string received from the client is already in UTC (ISO 8601 format), as prepared by the frontend. It parses this string into aDate
object, which Prisma handles correctly as a UTC timestamp in the database.try...catch
block to handle potential errors during JSON parsing, validation, or database operations, returning appropriate HTTP status codes.201 Created
status with the ID of the newly created schedule on success, or error details on failure.3.2 Testing the API Endpoint
You can test this endpoint using
curl
or a tool like Postman/Insomnia after running your Next.js development server (npm run dev
).Curl Example:
curl
command.4. Integrating with third-party services
4.1 Plivo Setup
Sign Up: Create an account at Plivo.com.
Get Credentials:
Buy a Phone Number:
Phone Numbers
->Buy Numbers
in the Plivo console.+12025551234
).Update Environment Variables: Paste your Auth ID, Auth Token, and the purchased phone number into your
.env.local
file.Add Test Numbers: During the free trial, Plivo requires you to verify destination numbers. Go to
Phone Numbers
->Sandbox Numbers
and add the phone number(s) you intend to send test messages to.4.2 Vercel Postgres Setup
Storage
tab.Create Database
and selectPostgres
.plivo-scheduler-db
), and create it.Connect Project
..env.local
tab).POSTGRES_PRISMA_URL
andPOSTGRES_URL_NON_POOLING
..env.local
file, replacing the placeholders.4.3 Apply Database Schema
With the database connection string configured in
.env.local
, apply your Prisma schema to the Vercel Postgres database.prisma/schema.prisma
. It's suitable for development and simple production setups. For complex changes requiring explicit migration steps later, consider usingprisma migrate dev
(local) andprisma migrate deploy
(CI/CD).5. Implementing error handling, logging, and retries
Our current code has basic
try...catch
blocks andconsole.log
statements. Let's refine this./api/schedule
) uses Zod for specific validation errors and returns 400 status codes.sendPlivoSms
function returns a{ success: boolean, error?: string }
object, allowing the caller (cron job) to handle Plivo-specific failures.console.log
andconsole.error
are used throughout. For production, consider integrating a dedicated logging service (like Logtail, Datadog, Axiom) by replacingconsole
calls or using a library likepino
.console
output from Serverless Functions and Cron Jobs, viewable in the deployment logs.PENDING
(or is markedFAILED
but you add logic to retryFAILED
ones), the next scheduled run of the cron job will attempt to process it again. This relies on subsequent job invocations, not an immediate retry within the same failed invocation. For more sophisticated retries (e.g., exponential backoff for specific errors), a dedicated job queue system (like BullMQ with Redis) would be needed.Example: Testing Error Scenario
PLIVO_AUTH_ID
in your.env.local
./api/cron
function. You should see error messages from thesendPlivoSms
function indicating authentication failure. The schedule status in the database should ideally be updated toFAILED
.6. Database schema and data layer
We defined the schema (
prisma/schema.prisma
) and set up the Prisma client (lib/prisma.ts
) in Section 2. TheSchedule
model and its indices are designed for this use case.Entity Relationship Diagram (ERD): In this simple case, we only have one main entity:
Schedule
.(Note: The following diagram uses Mermaid syntax. Ensure your publishing platform supports Mermaid rendering, or replace this with a static image alternative.)
Data Access: All database interactions happen via the
prisma
client instance, providing type safety and abstracting SQL. The primary query pattern is in the cron handler (Section 12.1 - Note: Section 12.1 was referenced but not provided in the original text) where we fetch pending schedules due to be sent.Migrations: We used
prisma db push
for simplicity. For production evolution, useprisma migrate dev
locally to generate SQL migration files andprisma migrate deploy
in your deployment pipeline (CI/CD) to apply them reliably.Sample Data: You can use Prisma Studio to manually add test data.
This opens a web UI where you can view and manipulate your database data.
7. Adding security features
app/api/schedule/route.ts
provides strict validation of incoming data, preventing malformed requests and basic injection attempts. Sanitize or carefully handle message content if rendering it elsewhere later.PLIVO_AUTH_ID
,PLIVO_AUTH_TOKEN
,POSTGRES_...
,CRON_SECRET
) are stored securely in environment variables and not committed to Git (.env.local
). Use Vercel's environment variable management for deployment.