Cron jobs automate recurring tasks like sending digest emails, cleaning up old data, or generating reports.
Step 1: Configure vercel.json
{
"crons": [
{
"path": "/api/cron/daily-digest",
"schedule": "0 9 * * *"
},
{
"path": "/api/cron/cleanup",
"schedule": "0 0 * * 0"
},
{
"path": "/api/cron/weekly-report",
"schedule": "0 8 * * 1"
}
]
}
Common cron schedules:
0 9 * * *— Every day at 9:00 AM UTC0 */6 * * *— Every 6 hours0 0 * * 0— Every Sunday at midnight0 0 1 * *— First day of every month
Step 2: Secure Your Cron Endpoints
// lib/cron.ts
import { NextResponse } from "next/server";
export function verifyCronSecret(req: Request) {
const authHeader = req.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return null;
}
Step 3: Daily Digest Email
// app/api/cron/daily-digest/route.ts
import { NextResponse } from "next/server";
import { verifyCronSecret } from "@/lib/cron";
import { db } from "@/db";
import { users, activities } from "@/db/schema";
import { Resend } from "resend";
import { gte } from "drizzle-orm";
const resend = new Resend(process.env.RESEND_API_KEY);
export async function GET(req: Request) {
const error = verifyCronSecret(req);
if (error) return error;
const yesterday = new Date(Date.now() - 86400000);
// Get users who opted in to daily digests
const subscribers = await db
.select()
.from(users)
.where(gte(users.dailyDigest, true));
for (const user of subscribers) {
const recentActivities = await db
.select()
.from(activities)
.where(gte(activities.createdAt, yesterday));
if (recentActivities.length === 0) continue;
await resend.emails.send({
from: "digest@yourdomain.com",
to: user.email,
subject: `Daily Digest — ${recentActivities.length} updates`,
html: `
<h2>Your Daily Digest</h2>
<ul>
${recentActivities
.map((a) => `<li>${a.description}</li>`)
.join("")}
</ul>
`,
});
}
return NextResponse.json({
success: true,
emailsSent: subscribers.length,
});
}
Step 4: Data Cleanup Job
// app/api/cron/cleanup/route.ts
import { NextResponse } from "next/server";
import { verifyCronSecret } from "@/lib/cron";
import { db } from "@/db";
import { sessions, logs, tempFiles } from "@/db/schema";
import { lt } from "drizzle-orm";
export async function GET(req: Request) {
const error = verifyCronSecret(req);
if (error) return error;
const thirtyDaysAgo = new Date(Date.now() - 30 * 86400000);
const sevenDaysAgo = new Date(Date.now() - 7 * 86400000);
// Delete expired sessions
const deletedSessions = await db
.delete(sessions)
.where(lt(sessions.expiresAt, new Date()));
// Delete old logs
const deletedLogs = await db
.delete(logs)
.where(lt(logs.createdAt, thirtyDaysAgo));
// Delete temp files
const deletedTemp = await db
.delete(tempFiles)
.where(lt(tempFiles.createdAt, sevenDaysAgo));
return NextResponse.json({
success: true,
cleaned: {
sessions: deletedSessions.rowCount,
logs: deletedLogs.rowCount,
tempFiles: deletedTemp.rowCount,
},
});
}
Step 5: Weekly Report Generation
// app/api/cron/weekly-report/route.ts
import { NextResponse } from "next/server";
import { verifyCronSecret } from "@/lib/cron";
import { db } from "@/db";
import { orders, users } from "@/db/schema";
import { gte, count, sum } from "drizzle-orm";
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
export async function GET(req: Request) {
const error = verifyCronSecret(req);
if (error) return error;
const oneWeekAgo = new Date(Date.now() - 7 * 86400000);
const [orderStats] = await db
.select({
totalOrders: count(),
totalRevenue: sum(orders.amount),
})
.from(orders)
.where(gte(orders.createdAt, oneWeekAgo));
const [userStats] = await db
.select({ newUsers: count() })
.from(users)
.where(gte(users.createdAt, oneWeekAgo));
await resend.emails.send({
from: "reports@yourdomain.com",
to: "admin@yourdomain.com",
subject: `Weekly Report — ${new Date().toLocaleDateString()}`,
html: `
<h2>Weekly Report</h2>
<table>
<tr><td>New Orders</td><td>${orderStats.totalOrders}</td></tr>
<tr><td>Revenue</td><td>$${orderStats.totalRevenue}</td></tr>
<tr><td>New Users</td><td>${userStats.newUsers}</td></tr>
</table>
`,
});
return NextResponse.json({ success: true });
}
Step 6: Testing Cron Jobs Locally
// scripts/test-cron.ts
async function testCron(path: string) {
const res = await fetch(`http://localhost:3000${path}`, {
headers: {
Authorization: `Bearer ${process.env.CRON_SECRET}`,
},
});
console.log(`${path}:`, res.status, await res.json());
}
testCron("/api/cron/daily-digest");
testCron("/api/cron/cleanup");
testCron("/api/cron/weekly-report");
Need Automated Business Workflows?
We build web applications with scheduled tasks, automated reports, and workflow automation. Contact us to discuss your project.