Every SaaS has a short list of tasks that need to run on a schedule -- and most founders discover them one by one, usually after something breaks. A free trial expires on paper but the user keeps full access because nothing reset their plan. A reminder email never goes out because you forgot to check manually. A soft-deleted records table quietly grows to half a million rows because nothing cleaned it up.
You do not need Bull, BullMQ, or Inngest for any of this. If you are already deployed on Vercel, you have Vercel Cron available today at no extra cost.
Before adding infrastructure, ask what you actually need to automate. Most early SaaS products need exactly three things on a recurring basis:
A job queue like Bull or Inngest is the right answer when tasks are triggered by user events, need retries on failure, fan out into dozens of sub-tasks, or run for more than a few minutes. Cron is the right answer when tasks run on a fixed schedule and finish in under a minute.
For the three categories above, a cron job calling a Next.js API route is almost always enough -- and it is the only option that adds zero new infrastructure.
Vercel Cron calls a URL in your deployed app on whatever schedule you define in vercel.json. The URL is an ordinary API route. Your job logic runs inside it and returns 200 when done.
{
"crons": [
{ "path": "/api/cron/expire-trials", "schedule": "0 1 * * *" },
{ "path": "/api/cron/trial-reminders", "schedule": "0 9 * * *" },
{ "path": "/api/cron/cleanup", "schedule": "0 3 * * 0" }
]
}
0 1 * * * means 1am UTC every day. 0 3 * * 0 means 3am UTC every Sunday.
Before running any job logic, your route should verify the request actually came from Vercel. Vercel sends an Authorization: Bearer <CRON_SECRET> header. Check it first:
export async function GET(req: Request) {
if (req.headers.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response("Unauthorized", { status: 401 });
}
await expireTrialsService();
return Response.json({ ok: true });
}
Set CRON_SECRET to any random string in your Vercel environment variables. Nothing else to configure.
Expire trials. Runs at 1am UTC daily. Finds all users where trialEndsAt < now() and plan = trial, then sets their plan to free. One Drizzle update query. Users still inside their trial window are untouched.
Trial reminders. Runs at 9am UTC daily. Finds users whose trial ends exactly 3 days from now and who have not already received a reminder (add a reminderSentAt column to track this). For each matching user, it calls your email service. Your Resend transactional email setup already handles delivery -- this job just decides who gets the message and when.
Database cleanup. Runs at 3am UTC every Sunday. Finds soft-deleted records where deletedAt < now() - 30 days and hard-deletes them table by table (posts, comments, notifications). Running table-by-table keeps each query fast and predictable.
Vercel Cron invocations time out at 60 seconds on the Hobby plan and 15 minutes on Pro. If you try to process all 50,000 users in one shot on Hobby, you will hit the limit.
The fix is to process in batches -- 500 rows per run -- using a processedBefore cursor to page through the table. For most early-stage products under 10,000 users, a single run finishes in well under 5 seconds.
If you hit the 60-second ceiling, it means your SaaS has real scale. At that point upgrading to Vercel Pro or moving to a dedicated queue (the pattern covered in How to Run Background Jobs in Your Next.js SaaS) is a decision you can easily justify.
The Next.js SaaS Boilerplate deploys to Vercel and includes Drizzle ORM, Resend, and a Stripe subscription setup -- everything the three jobs above need to work. See the deployment guide for how to set Vercel environment variables including CRON_SECRET.
Start with trial expiration. It directly affects revenue if it is missing and takes about 20 lines to implement. Add the reminder email next -- it keeps users from being surprised when access changes. Add cleanup last, once you have enough data to know what accumulates.
One cron job running on a schedule is better than five things you do manually once a month.
Get the boilerplate and ship your first scheduled task at boilerplate.iteam-company.com.