zazzy
Features

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 Inngest instance (features/jobs/server/client.ts) used for every inngest.send() and the serve route (app/api/inngest).
  • Event registryevents, one eventType() 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, and staticSchema gives 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:observability markers) 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.
  • Dependentsemail, billing, and usage each require: ["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.

On this page