Background Jobs
The 19 scheduled and event-driven jobs that keep Royal Glow running — 14 QStash scheduled, 4 QStash triggered, 1 GitHub Actions cron.
Background Jobs
19 jobs total — 14 QStash scheduled + 4 QStash triggered + 1 GitHub Actions cron.
All scheduled and event-driven jobs run on QStash (HTTP → Next.js API routes
hosted in apps/admin, served from admin.theroyalglow.in/api/jobs/*). The
single exception is the pprd branch reset (Job 5), a Neon control-plane op on
GitHub Actions cron. Every job pings a BetterStack heartbeat on success.
Royal Glow runs 19 background jobs. All scheduled and event-driven jobs run
on QStash (HTTP → Next.js API routes) — including the pure-SQL maintenance jobs,
which execute their idempotent SQL via @rgss/db query functions. The single
exception is the pprd branch reset (Job 5), which is a Neon control-plane op and
runs on GitHub Actions cron.
pg_cron is not used. The 6 DB-maintenance jobs were originally planned on
pg_cron, but the free-tier prod Neon compute scales to zero after ~5 min idle
and pg_cron only fires while the compute is awake — the late-night windows
(11:30 PM–2:30 AM IST) would silently never run. QStash POSTs an endpoint that
wakes the compute, so the jobs run reliably at ₹0. The job SQL was ported
verbatim into packages/db/src/queries/jobs.ts.
Where jobs run
Upstash HTTP queue → Next.js API routes in apps/admin. Use for ALL scheduled
and triggered jobs (DB maintenance + external services).
Every QStash job calls a POST /api/jobs/... route and is auto-retried (3x,
exponential backoff) on a non-2xx response. Every job pings a BetterStack
heartbeat on success so silent failures get detected. The /api/jobs/* routes are
served from admin.theroyalglow.in/api/jobs/*; QStash schedules are registered
from the admin deploy.
GitHub CI. Control-plane only — Neon pprd branch reset + PII strip (Job 5).
A Neon Branch Reset API call cannot run inside a normal request, so it lives in CI.
All schedules are in UTC; the salon operates IST (UTC+5:30).
Not jobs (synchronous / external)
Three time-critical actions deliberately run in-request, not as jobs:
- Invoice email + PDF — fired immediately when the receptionist marks payment received.
- Gems-earned push — fired in the same request as invoice completion.
- Re-engagement email — handled inside Brevo automations (marketing), not counted here.
Job inventory
These run in the early IST hours when traffic is zero. Jobs 1–4, 6 and 7 run on
QStash (each POSTs its /api/jobs/... route, which runs the idempotent SQL via
a @rgss/db query function) — not pg_cron. Job 5 runs via GitHub Actions cron
(Neon branch reset) plus SQL anonymisation.
HTTP-triggered application jobs on a fixed cadence (the 6 DB-maintenance jobs are also QStash-scheduled, for 14 QStash-scheduled jobs in total).
Reminders fire only when the customer has the relevant preference enabled
(appointment_reminders_enabled, membership_alerts_enabled) or
marketing_consent for birthday/marketing sends. Each job writes a
notification row for idempotency so the same alert is never sent twice.
Event-driven — enqueued with a delay by an API route when a business event occurs, not on a fixed schedule.
Related pages
Jobs API
The QStash-triggered /api/jobs/* route contracts
Data Model
The tables these jobs scan and update
Deployment
How jobs are monitored via BetterStack heartbeats
Was this page helpful?