Job Scheduling Without Bolting On a State Database

repojavascriptnodejob-schedulerinfrastructureworker-threadsinngest

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