Durable background functions: steps that survive restarts, retries, sleeps that can last days, and events that fan out to many handlers.
Build me durable background functions that replace Inngest — and know why this is ALMOST. The idea is genuinely valuable: **a function that sleeps for three days and resumes exactly where it stopped, across a deploy.** Reproducing it means checkpointed steps, a lease queue, and event fan-out, and none of that is exotic — but the correctness is unforgiving, because a step replayed twice does something twice in the real world. That is the verdict: buildable, and easy to get subtly wrong. STACK - Node 20+ with Fastify for the API and dashboard - SQLite through better-sqlite3, WAL mode - A worker process, or several - Caddy in front THE DATA MODEL - functions: id, key, version, config_json, event_triggers_json, cron, concurrency_key, concurrency_limit, throttle_json, is_active - events: id, name, payload_json, idempotency_key, received_at — the event log, append-only, and the thing that makes fan-out possible - runs: id, function_key, function_version, event_id, status, attempt, queue, priority, scheduled_for, started_at, finished_at, output_json, error_json, parent_run_id - steps: id, run_id, key, attempt, status, input_json, output_json, error, started_at, finished_at — one row per checkpoint, and the step cache - waits: id, run_id, kind, resume_at, event_name, match_json, token, resumed_at - queue: id, run_id, run_at, attempts, locked_by, lease_expires_at, priority - Every step's output stored. When a function does the wrong thing at three in the morning, that record is the only thing that answers why CHECKPOINTED STEPS, WHICH IS THE WHOLE DESIGN - A function is ordinary code, and each meaningful operation is a named step - A step that already succeeded returns its stored output instead of running again. A retry after a failure at step seven resumes at seven; one to six do not repeat - Step keys must be deterministic — from the call order, or given explicitly. A key that changes between attempts breaks resumption silently, so validate it and fail loudly rather than quietly re-running - A step calling something non-idempotent must pass an idempotency key through to that service. Sending an email twice is annoying; charging a card twice is a refund and an apology - Never put a side effect outside a step. That rule is the one people break, and it is what makes replay dangerous SLEEPING WITHOUT HOLDING A PROCESS - `sleep for three days` records a resume time, releases the worker, and the run is picked up later. It is not a timer in memory, and it survives a deploy because nothing is in memory - `wait for event` records a name and a match expression; an incoming event that matches resumes the run. With a timeout, always - `wait for child run` resumes when a spawned run finishes - This is what lets one small worker manage tens of thousands of runs that are mostly waiting EVENTS AND FAN-OUT - Send an event; every function subscribed to that name gets a run. One producer, many consumers, decoupled - Idempotency key on the event, so a webhook delivered twice produces one set of runs - The event log is queryable, which makes 'what happened to this order' one question rather than five CONTROL - Concurrency limits per function and per key, so one customer cannot starve the rest - Throttling for functions calling a rate-limited service - Debounce and batching for events that arrive in bursts - Cancellation that stops at the next step boundary and marks the run cancelled rather than failed - Retries with exponential backoff and jitter, a maximum, and a dead-letter state with a one-button replay RELIABILITY - The queue is a table with a lease. A worker claims a run, extends the lease, and a crashed worker's run is reclaimed when the lease expires - Kill a worker mid-run and confirm it resumes at the right step, exactly once. That test is the acceptance criterion for the whole system THE DASHBOARD - Runs filterable by function, status, event and time, failures first - A run view with every step's input, output, duration and error, and the waits and retries in the timeline - Replay a run, and replay from a chosen step SECRETS AND RETENTION - Payloads are stored, so redact by key name and value match before writing, including encoded forms - A stated retention with a sweeper — payloads are the bulk of the data OPERATIONS - .env: DATABASE_PATH, BASE_URL, SESSION_SECRET, EVENT_KEY, MAX_CONCURRENCY - Migrations on boot, each once; nightly backup off the machine - Health endpoint reporting queue depth, oldest unlocked run and expired leases WHAT MATTERS MOST The step cache and the lease. Get both right and everything else follows; get either wrong and the system will occasionally do something twice, which is every reason you had for building it.
What you lose
- Durable execution, where a function sleeping for three days resumes exactly where it stopped after a deploy
- Concurrency and throttling controls per function
- A run history showing every step's input and output
If you would rather not build
- A Postgres job table, which is what the prompt builds
What it costs
read from their page 15 Aug 2026
| Plan | Billed monthly | Billed yearly | Last read |
|---|---|---|---|
| — | $99/mo | — | 15 Aug 2026 |
Their pricing page is where these came from. Seeing a different price? Tell us.
The escape hatch
open source · no votes, no paid placement
Temporal
$0Durable execution as a platform, self-hostable but operationally serious.
temporalio/temporalfree · open source
BullMQ
$0A Redis-backed job queue with retries, delays and concurrency.
taskforcesh/bullmqfree · open source
Why this verdict
our own opinion · changed only by a person
58/100
Verdict kinda at 58. Durable execution is a step log and a replay rule; getting it right is a weekend, and the run history is what makes it debuggable.
History
tracked since 10 Aug 2026 · nothing is ever overwritten
Questions about Inngest
answered from the record above
Is Inngest free?
No — the plan we track is $99 a month. Pro from $99/month with a million executions included, then priced on usage; a free tier covers 50,000.
Can you replace Inngest by building your own?
ALMOST. A weekend of work, and real gaps remain. Replacement score 58 out of 100, build time a weekend. Read what you lose before you decide.
How much does Inngest cost?
$99 a month on Basic — $1,188 a year. Recorded 14 Aug 2026.
What do you lose by replacing Inngest?
Durable execution, where a function sleeping for three days resumes exactly where it stopped after a deploy; Concurrency and throttling controls per function; A run history showing every step's input and output. If any of those carry weight for you, keep paying.
Is there an open-source alternative to Inngest?
Yes: Temporal, BullMQ. The prompt on this page is for when you want it your way instead.
Related entries
same category first, most replaced first
Every week, something stops being worth paying for.
New verdicts, prices that moved, entries added. One email a week. Unsubscribe in one click. Nothing is being sent yet — your address is kept here, and the first issue is the first thing it is used for.
free forever · no tracking pixel · stored here, never passed to anyone

