Durable Jobs and Readiness

Operate the PostgreSQL job queue, cron runner, retries, deduplication, liveness and dependency-readiness probes.

Synced with starter commit 7d452a6.

Why Work Is Persisted

Serverless instances may freeze as soon as a response is returned. Work that must happen—email delivery, credit grants, object deletion, account export, and erasure—is written to the jobs table instead of being left in queueMicrotask or an un-awaited promise.

Current job types include welcome and payment emails, organization invitations, signup credits, Slack events, storage deletion, reservation confirmations, account exports, export expiry, and account erasure.

Enqueue Safely

await enqueueJob("welcome_email", { email, name, userUuid }, {
  dedupeKey: `welcome:${userUuid}`,
  subjectUserUuid: userUuid
});

dedupeKey makes enqueueing idempotent. Subject UUIDs let privacy workflows cancel or scrub queued work. Handlers must also be idempotent because a provider may accept a request before the worker sees its response.

Runner Behavior

GET /api/cron/jobs is protected by Authorization: Bearer $CRON_SECRET. vercel.json invokes it every five minutes. Outside Vercel, schedule the same authenticated request yourself.

The runner:

  • claims one due job at a time with FOR UPDATE SKIP LOCKED;
  • allows overlapping cron calls without double execution;
  • uses a five-minute lease and reclaims stale workers;
  • limits each handler to 20 seconds and the drain to 40 seconds;
  • retries up to five times by default with exponential backoff from 30 seconds;
  • keeps succeeded/failed jobs for 14 days;
  • also cleans stale upload reservations and sweeps stuck Stripe events.

Never expose the cron route publicly without CRON_SECRET, and generate a different secret from BETTER_AUTH_SECRET.

Liveness vs Readiness

GET or HEAD /api/health is a cheap process liveness check. It does not touch dependencies.

GET /api/ready verifies:

  • production environment configuration;
  • database access and compiled migration availability;
  • Redis-backed distributed rate limiting;
  • queue health, including stale running or failed jobs.

Missing database, migrations, Redis, or required environment configuration returns 503. Queue failures mark the report degraded but do not remove the web application from service.

Use /api/health for a platform's frequent liveness probe and /api/ready before routing production traffic or completing a deployment.

Operational Checks

  1. Set CRON_SECRET to at least 32 random bytes.
  2. Confirm the scheduler calls the route every five minutes.
  3. Watch structured cron.jobs logs for claimed, retrying, failed, and lease-lost counts.
  4. Alert on a non-200 readiness response and on a growing failed queue.
  5. Keep handlers additive and payload changes backward compatible; queued JSON may outlive the deploy that created it.
Durable Jobs and Readiness · Sushi SaaS