Inngest: Durable Step Functions Without the Queue You Have to Run
The leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.
At a glance
- What is it?
- Inngest is a Go workflow engine plus language SDKs that turn ordinary functions into retryable, resumable steps. It suits teams that want durable background work without operating a broker, and it is the wrong choice if you need a general-purpose stream processor.
- Who is it for?
- Adopt Inngest if your background work is a sequence of steps that must survive restarts and you would rather write plain functions than operate a broker. Do not adopt it if you need a general-purpose event streaming platform, since its event stream exists to feed the Runner rather than to be consumed by your own subscribers.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Inngest solves: durable steps without owning a queue
Background work that spans more than one operation is where most teams quietly build infrastructure. A job copies images, then resizes them. If the resize fails, you want the copy to stay done and only the resize to retry. Doing that by hand means a queue, a state table, a scheduler and a retry policy, and the retry policy has to be idempotent against the state table. The README states that Inngest's durable functions "replace queues, state management, and scheduling" so that developers can write reliable step functions without touching infrastructure.
The target user is a product engineer who already has an HTTP service and wants background logic inside it. Inngest functions live in your codebase, are written in your language, and are invoked by the Inngest server over HTTPS when a triggering event arrives. That inversion matters: there is no worker binary to deploy next to your app, and no broker client to configure. The SDK you already installed is the whole integration surface.
It is a poor fit for teams that want a streaming platform. The event stream in this architecture is described as a buffer between the Event API and the Runner, not as a topic log your own consumers subscribe to. If your requirement is fan-out to many independent readers, Inngest is solving a different problem.
Triggers, flow control and steps: the three parts of an Inngest Function
The README names three components of a function. Triggers are events, cron schedules or webhook events. Flow control decides how runs are enqueued and executed: concurrency, throttling, debouncing, rate limiting and prioritization. Steps are the unit of durability, and the README says they let workflows "run for months and recover from failures."
Flow control is configured on the function, not in a separate policy file. The README's example sets a concurrency key of event.data.userId with a limit of 10, which means ten concurrent runs per distinct user rather than ten overall. That per-key scoping is the part teams underestimate: a global limit protects the downstream service, while a keyed limit protects each customer from each other.
Steps are where the retry semantics live. Wrapping work in step.run means the step is retried automatically on failure, and the README notes you can include numerous steps in one function. The practical consequence is that step boundaries are your idempotency boundaries. Anything outside a step is re-executed from the top when the function resumes, so side effects that must happen once belong inside a step.
Inside the server: Event API, event stream, Runner, queue, Executor, state store
The architecture section describes a pipeline. The Event API receives events from SDKs over HTTP and authenticates client requests with Event Keys, then publishes payloads to an internal event stream. The Runner consumes that stream and does four things: schedules new function runs based on event type and writes initial run state to the state store, resumes functions paused via waitForEvent when a matching expression arrives, cancels running functions matching cancelOn expressions, and writes ingested events to a database for historical record and replay.
The queue is described as multi-tenant aware and multi-tier, built for fairness and for the flow control methods plus batching. The Executor handles initial execution, step execution, incremental writes of run state to the state store, and retries after failures. The state store persists the triggering events, step output and step errors.
Two design choices stand out. First, the event stream is explicitly a buffer, which keeps the Runner decoupled from ingestion but also means replay is a database feature rather than a stream feature. Second, state is written incrementally as steps complete, which is what makes a months-long run survive a process restart. The cost is write volume: every step completion is a state write, so a function with twenty small steps writes twenty times.
Running the Inngest Dev Server and sending a first event
The README's getting started path is the CLI, run through npx. This starts the Dev Server, which the README describes as a complete local development experience with production parity. After it starts, the dashboard is served on port 8288.
npx inngest-cli@latest devOpen http://localhost:8288 to reach the dashboard. The README points to separate Next.js, Node.js and Python quick start guides for wiring an app to it, and the SDK list covers TypeScript/JavaScript, Python, Go and Kotlin/Java.
The function definition itself follows the shape in the README: a configuration object with an id, a trigger, and a handler that receives event and step. The trigger below is an event name, and the two step.run calls are the retryable units.
export default inngest.createFunction(
{
id: "import-product-images",
concurrency: {
key: "event.data.userId",
limit: 10
}
},
{ event: "shop/product.imported" },
async ({ event, step }) => {
const s3Urls = await step.run("copy-images-to-s3", async () => {
return copyAllImagesToS3(event.data.imageURLs);
});
await step.run("resize-images", async () => {
await resizer.bulk({ urls: s3Urls, quality: 0.9, maxWidth: 1024 });
});
}
);Sending an event is a single call with a name and a data payload. The README shows it being called from elsewhere in your code, such as an API endpoint, and the payload carries the userId that the concurrency key reads.
await inngest.send({
name: "shop/product.imported",
data: {
userId: "01J8G44701QYGE0DH65PZM8DPM",
imageURLs: [
"https://useruploads.acme.com/q2345678/1094.jpg",
"https://useruploads.acme.com/q2345678/1095.jpg"
],
},
});For a self-hosted server rather than the CLI, the repository ships a Dockerfile that builds cmd/main.go into a binary and runs it with the command inngest, plus an install.sh at the top level. The README's self-hosting section is where the deployment details live.
Where Inngest gets awkward: HTTPS ingress, step boundaries and self-hosting
The invocation model is the sharpest constraint. The README says Inngest invokes your functions securely via HTTPS whenever triggering events are received. Your function host must therefore be reachable from the Inngest server. A batch process on a private subnet with no ingress cannot be an Inngest function without something in front of it. This is a deliberate trade: you get no worker fleet to manage, and in exchange you take on an inbound endpoint.
Step boundaries are the second trap. Because step.run is what gets retried and what writes state, code placed outside a step runs again on resume. A function that sends an email before its first step will send it again after a failure. The README's example puts all business logic inside steps, and that is the pattern to copy rather than a stylistic preference.
Self-hosting is the third area where the README is thin. It lists a self-hosting section in the table of contents and mentions a self-hosted Inngest server as an option, and the repository contains a Dockerfile, an install.sh and a Makefile target named xgo that cross-compiles for linux/arm64, linux/amd64, darwin/arm64 and darwin/amd64. What the README does not document is the operational surface of a self-hosted deployment: which database the state store expects, how the event stream is backed, or what a rollback looks like. The Makefile's run target shows the dev server being started in test mode with a tick interval and polling disabled, which is a development configuration, not a production one. Treat self-hosting as a path you will need to read the source and the docs site to complete.
Inngest compared with Temporal, Kafka and n8n
The three comparisons people search for map onto three genuinely different approaches.
Temporal also gives you durable execution, but it does so by having your code run inside a worker process that the Temporal cluster dispatches to, with workflow and activity definitions as first-class concepts. Inngest inverts that: your function stays in your existing HTTP service, and the server calls it over HTTPS. If you already run a service and want background logic in it, Inngest's model means no separate worker deployment. If you want the workflow code separated from your request-serving code, Temporal's model is the more natural fit.
Kafka is not a workflow engine at all. It is a distributed log, and the README's event stream is explicitly an internal buffer between the Event API and the Runner rather than a log you consume from. Choosing Kafka means you own the consumers, the offsets, the retry topics and the state store. Choosing Inngest means those are the Runner's job. They are not substitutes; a team can plausibly run Kafka for data movement and Inngest for the step functions on top.
n8n is a visual automation tool, where workflows are assembled in a canvas and integrations are prebuilt nodes. Inngest workflows are code in your repository, using your language's SDK, with the flow control expressed as function configuration. The difference is who maintains the workflow: a canvas is editable by people who do not deploy code, while an Inngest function goes through your normal review and release process.
Maintenance, licensing and what upgrading costs you
The repository is not archived, and the last push was on 2026-09-20. Recent releases are v1.45.1 on 2026-09-17, v1.45.0 on 2026-09-17 and v1.44.0 on 2026-08-26, so the release cadence is frequent and the version numbers move in minor increments rather than large jumps. For a self-hoster, that cadence is the upgrade cost: you are tracking a moving server, and the CHANGELOG.md at the top level is where the changes are recorded.
The Go module declares go 1.26.4, and the Dockerfile builds on golang:1.26.4-alpine and runs on alpine:3.24. The repository vendors its dependencies, and the Makefile has a vendor target that runs go mod tidy, go mod vendor and modvendor, which means builds can be done with GOFLAGS=-mod=vendor as the Dockerfile does. That is a real advantage for reproducible builds, but it also means a dependency bump is a larger diff than in a non-vendored project.
Licensing is the item to check before you build a product on this. The repository's license is reported as NOASSERTION, which means the automated detection could not classify it, and the LICENSE.md file at the repository root is the authoritative text. Read it yourself and get your own advice on what it permits for your distribution model; nothing here should be read as legal guidance. The README also distinguishes the self-hosted server from the Inngest Platform, and those are separate things to evaluate.
Editorial conclusion
Adopt Inngest if your background work is a sequence of steps that must survive restarts and you would rather write plain functions than operate a broker. Do not adopt it if you need a general-purpose event streaming platform, since its event stream exists to feed the Runner rather than to be consumed by your own subscribers. Before committing, verify three things: that your language has an SDK (TypeScript, Python, Go, Kotlin), that your function host can be reached over HTTPS, and that the self-hosted path meets your requirements, because the repository ships a Dockerfile and an install.sh but the README defers self-hosting details to a separate section.
Frequently asked questions
What does Inngest do?
It runs durable step functions: you write a function in your own codebase, Inngest invokes it over HTTPS when a triggering event, cron schedule or webhook arrives, and each step inside the function is retried and its state persisted. The README describes it as replacing queues, state management and scheduling.
How do I use Inngest locally?
Run the CLI with npx inngest-cli@latest dev, then open the dashboard at http://localhost:8288. The README calls this the Inngest Dev Server and describes it as a complete local development experience with production parity.
What is the Inngest server?
It is the component that receives events through the Event API, buffers them in an internal event stream, schedules runs in the Runner, queues them, and executes them through the Executor while writing state to the state store. You can run it locally as the Dev Server or deploy a self-hosted Inngest server, and the repository ships a Dockerfile and an install.sh.
Is Inngest open source?
The inngest/inngest repository is public and not archived, and it contains a LICENSE.md at the root. The license is reported as NOASSERTION, so read LICENSE.md directly rather than relying on a classifier.
What is Inngest used for?
The README frames it around reliable background logic, from background jobs to complex workflows, with triggers, flow control and steps as the three parts of a function. The example given is an image import that copies files to S3 and then resizes them.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/inngest-inngest)