Background jobs
Async job processing — steps, retries, and crons via Inngest.
Background jobs are powered by Inngest. The feature
is thin by design: it owns the shared inngest client, a typed event registry,
and the serve route at /api/inngest. It ships no jobs of its own — each
consuming feature registers its functions behind its own markers, so jobs is the
transport, not the workload.
Optional feature
Jobs is optional (core: false, requires: []) but is the substrate for
async work: email, billing, and usage all require it.
What it does
- Typed client — a single
Inngestinstance (features/jobs/server/client.ts) used for everyinngest.send()and the serve route (app/api/inngest). - Event registry —
events, oneeventType()per cross-feature event keyed<feature>/<subject>.<verb>(e.g.email/send,billing/seats.sync,usage/quota.warned). Each entry is marker-wrapped so it prunes with the feature that declares it, andstaticSchemagives compile-time payload types with no runtime validation dependency. - Steps, retries, crons — Inngest's durable execution: functions run as retriable steps and crons are scheduled functions (the rollup/retention crons live in the usage feature).
- Observability hook — an
InngestLoggingMiddleware(behind@feature:observabilitymarkers) binds each run's logger to the triggering event and run id.
Models it adds
None. Inngest holds run state; consuming features own their own idempotency
tables (e.g. email's EmailDispatch, billing's StripeEvent).
Env it needs
INNGEST_EVENT_KEY= # cloud mode: send key
INNGEST_SIGNING_KEY= # cloud mode: serve-route signing key
INNGEST_DEV= # local: set to 1 for the dev server (no keys needed)The SDK reads config from the environment directly. v4 defaults to cloud
mode and errors without a signing key, so local development must set
INNGEST_DEV=1 explicitly. The generated docker-compose.yml runs the Inngest
dev server (composeServices: ["inngest"]).
Composition
requires: []— jobs depends on nothing.- Dependents —
email,billing, andusageeachrequire: ["jobs"]. They declare their events in the jobs registry (behind markers) and register their functions on the serve route.
What pruning removes
Leaving jobs out drops features/jobs and the app/api/inngest serve route,
and removes the Inngest dev service from compose. Because email, billing,
and usage all require jobs, the picker won't let you keep any of them without
it — deselecting jobs deselects that whole subtree. An app with no async
features at all is the only configuration that prunes jobs cleanly.