Add scheduled invoice delivery

This commit is contained in:
2026-08-17 13:54:45 -04:00
parent 29589c1f32
commit 67ab6b78bd
26 changed files with 2099 additions and 835 deletions
+4 -1
View File
@@ -2,10 +2,12 @@
The worker is a separate Bun process backed by the same PostgreSQL database as the web app. It follows the Racetix worker model: a durable database outbox, a lightweight scheduler, and horizontally safe polling with `FOR UPDATE SKIP LOCKED`.
Currently it schedules and generates due recurring invoices. New asynchronous workflows should enqueue a typed job through `apps/web/src/server/jobs/queue.ts` and add a handler in `src/index.ts`.
It generates due recurring invoices and delivers scheduled invoice emails. New asynchronous workflows should enqueue a typed job through `apps/web/src/server/jobs/queue.ts` and add a handler in `src/index.ts`.
Jobs have an idempotency key, scheduled run time, bounded exponential retries, and stale-lock recovery. Multiple worker replicas can run safely. Timer elapsed time is still derived from `startedAt`; the worker should only send time-clock reminders, never increment a counter every second.
Scheduled sends store an absolute UTC instant plus the IANA timezone selected by the client. The worker calls the app's secret-protected internal delivery endpoint through `APP_INTERNAL_URL`; that endpoint passes the job idempotency key to Resend, so a retry cannot send the same invoice twice.
```bash
# Uses the web app's .env/.env.local files
bun run dev
@@ -15,3 +17,4 @@ bun run start
```
`WORKER_POLL_MS` defaults to 2000 and `WORKER_SCHEDULE_MS` defaults to 60000.
`APP_INTERNAL_URL` defaults to `http://app:3000` in Compose, and `CRON_SECRET` authenticates worker requests to the app.