Overview
DoorStax runs 12 scheduled background jobs that handle recurring billing, notification delivery, financial reconciliation, and data maintenance. All jobs are protected by the withCronGuard middleware, which verifies the CRON_SECRET header, prevents overlapping executions, and records each run in theCronRun model.
Job Reference
The following table lists all scheduled jobs, their cron schedules, and what they do.
| Job | Schedule | Timing | Description |
|---|---|---|---|
| generate-charges | 0 6 1 * * | 1st of month, 6 AM UTC | Creates monthly rent CHARGE ledger entries |
| generate-statements | 0 8 1 * * | 1st of month, 8 AM UTC | Generates owner statement PDFs |
| recurring-expenses | 0 7 1 * * | 1st of month, 7 AM UTC | Duplicates recurring expenses and tenant invoicing |
| lease-expiration | 0 7 * * * | Daily, 7 AM UTC | Tiered lease expiry alerts (90/60/30/14/7 days) |
| reconciliation | 0 9 * * * | Daily, 9 AM UTC | Ledger balance verification and auto-fix |
| retry-webhooks | 30 9 * * * | Daily, 9:30 AM UTC | Retries failed webhook deliveries |
| generate-payouts | 0 10 2 * * | 2nd of month, 10 AM UTC | Calculates owner payouts |
| financial-reconciliation | 0 11 * * * | Daily, 11 AM UTC | Kadima to local payment matching |
| process-jobs | 0 12 * * * | Daily, 12 PM UTC | Drains queued background jobs |
| autopay-reminders | 0 14 * * * | Daily, 2 PM UTC | Pre-charge and enrollment reminders |
| rent-reminders | 0 15 * * * | Daily, 3 PM UTC | 3-day reminder for non-autopay tenants |
| overdue-notices | 0 16 * * * | Daily, 4 PM UTC | Escalating overdue notices (1/5/15/30 days) |
Cron Guard
All cron endpoints are wrapped with the withCronGuard middleware that provides three layers of protection:
| Protection | Description |
|---|---|
| Secret Verification | Validates the CRON_SECRET header matches the environment variable |
| Overlap Prevention | Checks for an active CronRun with status RUNNING and skips if found |
| Run Tracking | Creates a CronRun record with start time, updates with status and duration on completion |
import { withCronGuard } from "@/lib/cron-guard";
export const POST = withCronGuard("generate-charges", async () => {
// Find all active leases
const leases = await prisma.lease.findMany({
where: { status: "ACTIVE" },
include: { unit: true, property: true },
});
let created = 0;
for (const lease of leases) {
await prisma.ledgerEntry.create({
data: {
type: "CHARGE",
amount: lease.rentAmount,
leaseId: lease.id,
description: `Rent - ${format(new Date(), "MMMM yyyy")}`,
},
});
created++;
}
return { chargesCreated: created };
});Monitoring
Every cron execution is tracked in the CronRun model. This provides a full audit log of when each job ran, how long it took, whether it succeeded or failed, and a summary JSON with job-specific metrics.
{
"id": "crun_001",
"jobName": "generate-charges",
"status": "COMPLETED",
"startedAt": "2025-02-01T06:00:00.000Z",
"completedAt": "2025-02-01T06:00:12.340Z",
"durationMs": 12340,
"summary": {
"chargesCreated": 847,
"leasesProcessed": 847,
"errors": 0
}
}Best Practice: Monitor CronRun records for jobs with status FAILED or unusually long durations. Set up alerts for any job that has not run within its expected schedule window.