How scheduled work runs
Recurring platform work is driven by pg_cron jobs in the database. A job does not contain a URL or a key: it calls public.invoke_edge_function(fn, body), which reads the target base URL and service key from the app_config table and invokes the edge function. That indirection is deliberate — it is what makes the same schedule portable between the development and production databases.
Rules for anyone adding a job
- Always call
public.invoke_edge_function. Never hardcode a project URL or an anon key into a cron command. - Schedule jobs through a migration so they travel with the deploy. Do not create them ad hoc in a SQL console.
app_configmust be seeded in a new environment before any job will succeed. A brand-new environment with unseeded config fails silently at the first tick.
The kinds of work that run on a schedule
| Area | Typical work |
|---|---|
| Billing | Subscription sync, invoice day, dunning enforcement, payment-failure reminders, suspension of overdue accounts |
| Disputes & Stripe | Dispute reconciliation, evidence auto-submit, Connect account health sweeps |
| Member-facing | Alert dispatch, class check-in and no-show cleanup, waitlist expiry, coach session generation |
| Insights & AI | Daily body scores and their retry pass, engagement scoring |
| Housekeeping | Push token pruning, expired SAR pack purge, expired alert archiving, HTTP response cleanup |
There are roughly forty jobs in total. Treat the migrations as the authoritative list rather than memory — query cron.job in the environment you care about when you need the exact schedule, because schedules do get tuned.
When something has not run
- Check
cron.job_run_detailsfor the job's last run and its status. - If it ran but did nothing, read that function's logs — most functions log a fatal line when a run writes zero rows.
- If it never ran, confirm the job exists in this environment and that
app_configholds the current base URL and key.
Functions that backfill (for example daily insight scores) will catch up missed days on their next successful run, so a short outage usually self-heals once the cause is fixed.