Inngest Architecture: One Durable Workflow Explained¶

Inngest separates the event that requests work from the function that performs it. Between them, its architecture buffers events, schedules functions, applies flow control, persists progress, and retries failed steps. That matters when your serverless process stops mid-run or an external API fails.
This guide traces one AI enrichment workflow through the complete system. It also shows where durable execution helps—and where you still need application-level idempotency.
1. Inngest architecture in one diagram: separate the event from the function¶
Start with the main distinction:
- The event is data. A producer sends it over HTTP.
- The function is code. Inngest schedules and executes it in response to the event.
They have separate lifecycles. Receiving an event does not require the entire workflow to finish inside the original HTTP request.
sequenceDiagram
participant P as Producer
participant API as Event API
participant S as Event stream
participant R as Runner
participant Q as Queue
participant E as Executor
participant DB as State store
participant F as Serverless function endpoint
P->>API: Send enrichment.requested event
API->>S: Publish authenticated payload
S->>R: Deliver buffered event
R->>DB: Create initial execution state
R->>Q: Schedule matching function
Q->>E: Release work under flow-control rules
E->>F: Execute function or step
E->>DB: Store output, error, and progress
According to the Inngest self-hosting architecture, the system includes an Event API, event stream, Runner, queue, Executor, State store, database, GraphQL and REST APIs, and a dashboard.
The Event API receives events from SDKs through HTTP requests. It authenticates them with Event Keys, then publishes their payloads to the internal event stream. That stream buffers events between the Event API and the Runner.
The Runner consumes the event. It finds functions configured for that event type, creates their initial execution state, and schedules them. The queue then decides when the work may run. The Executor performs the function’s steps and progressively writes execution state.
If you want to compare this managed orchestration path with a webhook, stream, and worker design, see the Python guide to event-driven AI agents.
2. One durable workflow, end to end: from webhook to completed result¶
Consider a lead enrichment service. Your application has consented lead data and needs to:
- Validate the incoming record.
- Call an external enrichment API.
- Wait for human approval.
- Publish the approved result.
The producer emits an event such as:
{
"name": "lead.enrichment.requested",
"data": {
"job_id": "lead_acme_2026_09_11",
"lead_id": "lead_acme",
"company_domain": "acme.example"
}
}
The job_id is a stable identifier chosen by your application. Later, you can use it to protect external side effects from duplicate execution.
Here is the workflow as conceptual Python-like pseudocode. The method names represent Inngest durable-step primitives, but this is deliberately not tied to a specific SDK version:
async def enrich_lead(event, step):
validated = await step.run(
"validate-input",
lambda: validate(event["data"]),
)
enriched = await step.run(
"call-enrichment-api",
lambda: fetch_enrichment(validated),
)
approval = await step.waitForEvent(
"wait-for-approval",
event="lead.enrichment.approved",
match={"job_id": validated["job_id"]},
)
return await step.run(
"publish-result",
lambda: upsert_result(
job_id=validated["job_id"],
enrichment=enriched,
approval=approval,
),
)
The execution path is:
- The Event API accepts
lead.enrichment.requested. - The event stream buffers it.
- The Runner identifies
enrich_leadas a matching function. - The Runner creates the initial execution state.
- The queue applies the function’s flow-control configuration.
- The Executor runs each available step.
- The State store records progress.
The documented State store retains data for pending and active function runs. That includes the initial trigger events, step outputs, and step errors.
This is more than message transport. After validation finishes, its output becomes part of the durable execution state. The workflow can use that result when it continues instead of treating every invocation as a fresh job.
3. How durable steps survive failures, waits, and serverless limits¶
The useful unit of recovery is the step.
Inngest states that durable steps run once, cache their results, and can be retried independently after failure. The Executor writes execution progress to the State store as the function advances.
Apply that model to the enrichment workflow:
validate-input -> completed; output stored
call-enrichment-api -> failed; error stored
call-enrichment-api -> retried
call-enrichment-api -> completed; output stored
wait-for-approval -> suspended
publish-result -> not started
If the enrichment API fails, the completed validation step does not need to be repeated. The failed API step can be retried independently.
If execution is interrupted, durable state provides the information needed to resume progress rather than starting the whole function without context. This is the same core concern covered in our guide to persistent state for serverless jobs.
Inngest exposes several primitives for these boundaries:
step.runwraps a durable unit of work whose result is cached.step.sleepsuspends execution for periods ranging from seconds to months, without polling costs or idle workers.step.waitForEventsuspends execution until an approval, webhook, or user action arrives.step.invokecalls another function as a durable step with separate tracing and retries.
For this workflow, step.waitForEvent is the important boundary. The function can stop after enrichment while approval is pending. When the matching approval event arrives, Inngest resumes it.
You should still choose step boundaries carefully. Put an unreliable external call in its own step. Put the final database publication in another. A large step containing validation, API access, and publication creates a larger retry surface.
Inngest also records state for each step as a trace, including its duration, input, output, and retries, according to its durable execution documentation. That gives you a workflow-level view without relying only on logs from a short-lived serverless process.
4. Retries, idempotency, duplicates, and concurrency: what reliability does—and does not—mean¶
A retry makes another attempt. It does not automatically make an external side effect safe.
Suppose publish-result sends data to your CRM. The CRM accepts the write, but the request times out before your function receives the response. A retry may send the same write again.
Protect that boundary with an application-level strategy:
def publish_result(db, job_id, payload):
existing = db.find_by_job_id(job_id)
if existing is not None:
return existing
return db.upsert(
key=job_id,
value=payload,
)
In production, the lookup and write need an atomic guarantee appropriate to your database. The key principle is stable identity. The same logical operation must keep the same idempotency key across retries.
Other useful patterns include:
- A conditional write that succeeds only when no result exists.
- An upsert keyed by the workflow’s stable job ID.
- A deduplication record written before a non-repeatable side effect.
- Passing the stable key to an external API when that API supports idempotent requests.
Retries and backoff are configurable per function in Inngest. Retries can also be disabled for processing where another attempt would be inappropriate. Backoff can reduce pressure on an unstable API.
Inngest’s documented flow-control patterns include four relevant primitives: throttle, concurrency, debounce, and idempotency. They solve different problems:
- Throttle limits how quickly work proceeds.
- Concurrency limits simultaneous work.
- Debounce delays closely related triggers so they can be handled under that policy.
- Idempotency addresses repeated logical execution.
Do not treat those controls as proof that every external system sees exactly one effect. Duplicate events can still reach application boundaries. Concurrent runs can target the same record. Events can arrive in an order your business logic did not expect. A fan-out workflow can also have completed branches alongside failed branches.
Design and test those cases explicitly. The idempotent AI jobs guide covers stable run IDs, upserts, safe retries, staged execution, and concurrency control in Python.
5. The queue and execution layer: scaling without persistent workers¶
The queue sits between scheduling and execution. It is not just a FIFO list.
Inngest documents its queue as multitenant and multilevel. It supports fairness, concurrency controls, throttling, prioritization, debouncing, rate limiting, and batching. The Runner adds executions to queues based on each function’s flow-control configuration. The Executor takes released work and executes the corresponding functions or steps.
For lead enrichment, you might use queue controls to prevent a traffic spike from overwhelming the external enrichment API. Concurrency controls how many calls are active. Throttling or rate limiting controls the release rate. Prioritization can let interactive requests run ahead of routine background enrichment.
The architecture uses an HTTP model across edge, serverless, and traditional environments. Inngest says this does not require persistent workers. Your function endpoint can remain part of a serverless deployment while orchestration state lives outside the function process.
That separates Inngest from a basic task queue conceptually:
| Basic task queue | Inngest durable execution |
|---|---|
| Transports work to a consumer | Schedules functions from events |
| Consumer typically owns progress tracking | State store tracks workflow progress |
| Delayed continuation needs application logic | Durable sleep and event waits are primitives |
| Retry often applies to the queued task | Failed steps can retry independently |
| Outputs require separate persistence design | Step outputs are retained in execution state |
This does not remove your operational responsibilities. Your endpoint can still be unavailable. External APIs can still fail. Your side effects still need safe retry behavior. The orchestration layer gives those failures a durable structure.
6. Operating the architecture: managed versus self-hosted and an implementation checklist¶
Managed Inngest reduces the infrastructure you operate. Self-hosting gives you responsibility for the execution system’s availability, scaling, persistence, monitoring, backups, and recovery.
The documented self-hosted defaults make this concrete:
- The Inngest server started through the CLI uses port
8288by default. - The Connect gateway is available on port
8289by default. - The default setup uses an in-memory Redis server for the queue and State store.
- SQLite provides persistence.
- The SQLite database is stored at
./.inngest/main.dbby default.
These defaults are useful for getting the system running. A production self-hosted deployment needs explicit decisions around durable queue and state storage, database persistence, backup procedures, service availability, capacity, and recovery testing. Do not assume a development default is your production durability plan.
Use this checklist before shipping an event-driven workflow:
- [ ] Define and validate the event schema.
- [ ] Include a stable job or idempotency key.
- [ ] Separate the trigger event from the function’s execution logic.
- [ ] Wrap unreliable external calls in clear durable steps.
- [ ] Make database writes and external side effects safe to retry.
- [ ] Configure the function’s retry and backoff policy.
- [ ] Disable retries where another attempt would be unsafe.
- [ ] Set concurrency, throttling, or rate limits for constrained APIs.
- [ ] Test duplicate event delivery.
- [ ] Test concurrent runs against the same business record.
- [ ] Test interruption and restart recovery.
- [ ] Test long waits and approval events.
- [ ] Inspect each step’s duration, input, output, error, and retry trace.
- [ ] For self-hosting, test backups and recovery instead of only service startup.
Conclusion¶
Inngest architecture separates event ingestion, scheduling, queueing, execution, and state persistence. The event asks for work. The function defines the work. Durable steps create recovery boundaries between them.
That model fits serverless AI jobs that call unstable APIs, pause for approval, or exceed a single process lifetime. It does not eliminate duplicate delivery or unsafe side effects. You still need stable identifiers, idempotent writes, bounded concurrency, and failure tests.
Once those application guarantees are in place, you can run the function in a serverless environment—such as an isolated agent deployment—while Inngest coordinates its durable lifecycle.