Job Scheduling Without Bolting On a State Database
Architectural contrast to Inngest — pushes state responsibility to your existing database instead of adding Redis/MongoDB, worth understanding when evaluating where Inngest is overkill
Bree is a Node.js job scheduler built on worker threads — not child processes, not Redis queues, not MongoDB job collections. Each job runs in an isolated worker thread sandboxed from the main process. Cron expressions, ms duration strings, human-friendly intervals like "every 5 minutes", and specific dates all work out of the box via later.js.
The design decision that makes it interesting: Bree explicitly refuses to manage job state. The docs say it plainly — query your own database to prevent duplicate runs. If you’re sending welcome emails, only send to users where welcome_email_sent_at is null. Your database is the state store. Bree just fires the worker at the right time. This is the opposite of how Inngest thinks about the world — Inngest owns durable state, replays steps, memoizes results. Bree doesn’t care and doesn’t try.
Built by Forward Email for the Lad framework, it’s been production-tested on real workloads. Supports async/await, retries via p-retry, throttling via p-throttle, concurrency limits, stalled job detection, and graceful shutdown. There’s a plugin system too, though the core stays lean.
For the joelclaw stack, this represents the lighter end of a spectrum where Inngest lives at the durable-workflow end. For jobs that don’t need step replay or distributed durability — periodic cleanup tasks, simple cron pipelines, things with natural idempotency in the DB — Bree is a clean fit. Whether it’s worth running alongside Inngest or whether Inngest just handles everything is the real question.
Key Ideas
- Worker threads (not child processes, not Redis workers) as the isolation primitive — lower overhead, native to Node.js
- No external state layer required — delegates state responsibility entirely to your application database
- Scheduling syntax hierarchy: cron → ms strings → human-interval → specific dates, all valid
- Stalled job detection built in — if a worker doesn’t complete in time, Bree kills and optionally retries it
- Graceful reload: update job definitions without dropping in-flight work, via @ladjs/graceful
- Idiomatic comparison: Agenda requires MongoDB, Bull/BullMQ requires Redis, Bree requires neither
- TypeScript types included, ESM and CJS both supported
Links
- breejs/bree — GitHub repo
- jobscheduler.net — project site
- UPGRADING.md — v9 breaking changes
- Forward Email — production user / creator
- Lad framework — the broader ecosystem Bree lives in
- later.js fork — the cron/schedule parser underneath
- BullMQ — Redis-backed alternative for comparison
- Inngest — the durable workflow alternative in the joelclaw stack