Docs
Scheduled Jobs

Scheduled Jobs

Every job, when it runs, what it does, how it authenticates, and how to run one by hand.

Daily Jobs on Vercel

Defined in vercel.json. Times are UTC. Each route requires Authorization: Bearer <CRON_SECRET>; Vercel adds it.

RouteUTCWhat it does
/api/cron/onboarding-nudges11:00One profile nudge and one assessment nudge per person, ever, to members more than three days old
/api/cron/lifecycle12:00Marks members dormant after 180 days and sends re-engagement; sends the digest on the first of every other month; idle mentor and volunteer nudges
/api/cron/matching-engine12:30Syncs members to Nexus and proposes matches for ready founders
/api/cron/send-invitations13:00Re-sends invitations for accepted applications that have none
/api/cron/event-digest13:00Daily, weekly and monthly event digests
/api/cron/match-notices13:30Closes expired matches, sends first notices and seven-day reminders
/api/cron/match-checkpoints13:45The "how is it going" check-in after a match

The Five-Minute Job on Supabase

/api/cron/event-reminders is called every five minutes by a Supabase pg_cron job through pg_net, because Vercel's plan allows daily jobs only. It sends event and session reminders, chases RSVPs and volunteers, marks sessions complete, and syncs attendance.

The schedule lives in the database, not in a migration file. select * from cron.job; shows it; select * from cron.job_run_details order by start_time desc limit 20; shows recent runs. If CRON_SECRET is rotated, the job's header must be updated too.

Run By Hand

/api/cron/nexus-outcomes is run by hand to resend match outcomes to Nexus; ?dry=1 lists what would be sent.

Any job can be triggered from a terminal that has the secret:

curl -H "Authorization: Bearer $CRON_SECRET" https://www.impacthubos.com/api/cron/lifecycle

The response is JSON saying what it did. Every job is safe to run twice: each records what it sent and skips it next time.

Writing a New Job

  1. Add app/api/cron/<name>/route.ts. Check the bearer token first; return 401 without it.
  2. Make it idempotent: record what was sent in a table keyed so a second run finds it.
  3. Log a summary at the end so Vercel's log shows what happened.
  4. Add the schedule to vercel.json (daily or less) or to pg_cron (more often).
  5. proxy.ts already lists /api/cron(.*) as public, which means it skips the sign-in check, not that it is open.

Checking a Job Ran

Vercel, Settings, Cron Jobs shows each job's last run and result. A 401 means the secret on Vercel does not match what was sent; redeploy after setting it.