Scheduled Jobs & Background Processing

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

The kinds of work that run on a schedule

AreaTypical work
BillingSubscription sync, invoice day, dunning enforcement, payment-failure reminders, suspension of overdue accounts
Disputes & StripeDispute reconciliation, evidence auto-submit, Connect account health sweeps
Member-facingAlert dispatch, class check-in and no-show cleanup, waitlist expiry, coach session generation
Insights & AIDaily body scores and their retry pass, engagement scoring
HousekeepingPush 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

  1. Check cron.job_run_details for the job's last run and its status.
  2. If it ran but did nothing, read that function's logs — most functions log a fatal line when a run writes zero rows.
  3. If it never ran, confirm the job exists in this environment and that app_config holds 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.