Inngest Review (2026)
Inngest is an event-driven workflow platform for TypeScript. Send events → Inngest runs functions in response. Each function is composed of steps — if a step fails, Inngest retries from that step, not from the beginning. This "durable execution" model is what separates Inngest from simple job queues.
What It Does
| Feature | Description |
|---|---|
| Event-driven functions | Functions triggered by events |
| Step functions | Break workflows into retryable steps |
| Durable execution | Resume from the last successful step |
| Scheduling | Cron jobs and delayed execution |
| Concurrency | Per-function concurrency limits |
| Throttling | Rate limit function execution |
| Fan-out | One event triggers multiple functions |
| Dashboard | Monitor events, runs, and step execution |
How It Works
Define a Function
import { inngest } from "./client";
export const processSignup = inngest.createFunction(
{ id: "process-signup" },
{ event: "user/signup" },
async ({ event, step }) => {
// Step 1: Create account
const account = await step.run("create-account", async () => {
return await db.accounts.create({ email: event.data.email });
});
// Step 2: Send welcome email
await step.run("send-welcome", async () => {
await resend.emails.send({
to: event.data.email,
subject: "Welcome!",
template: "welcome",
});
});
// Step 3: Wait 3 days, then send onboarding email
await step.sleep("wait-for-onboarding", "3 days");
await step.run("send-onboarding", async () => {
await resend.emails.send({
to: event.data.email,
subject: "Getting started",
template: "onboarding",
});
});
}
);
The Durable Execution Advantage
If Step 2 fails (email service down), Inngest retries only Step 2. Step 1 isn't re-executed — the account isn't created twice. This is the core value proposition.
Traditional approach: Task fails → retry the entire task → guard against duplicate side effects → manage idempotency manually.
Inngest approach: Step fails → retry that step → previous steps are cached → no duplicate work.
Send an Event
await inngest.send({
name: "user/signup",
data: { email: "user@example.com", plan: "pro" },
});
Multiple functions can listen to the same event. user/signup might trigger: account creation, welcome email, analytics tracking, and Slack notification — each as separate functions.
What's Great
Step Functions Are Powerful
Steps aren't just retryable units — they're building blocks:
step.run()— Execute code, cache the resultstep.sleep()— Wait for a duration ("3 days", "2 hours")step.sleepUntil()— Wait until a specific timestep.waitForEvent()— Pause until another event arrivesstep.invoke()— Call another Inngest function
step.waitForEvent() is unique:
// Wait up to 7 days for payment confirmation
const payment = await step.waitForEvent("wait-for-payment", {
event: "payment/confirmed",
match: "data.userId",
timeout: "7d",
});
if (payment) {
await step.run("activate-account", () => { ... });
} else {
await step.run("send-reminder", () => { ... });
}
Build complex workflows that span days or weeks with simple, readable code.
Event-Driven Architecture
Events decouple your application:
- Your API sends events ("order/created", "user/upgraded")
- Functions react to events independently
- Add new functions without modifying existing code
- One event → multiple reactions (fan-out)
Works with Any Framework
Inngest serves functions via an HTTP endpoint. Works with Next.js, Express, Fastify, Hono, Remix, SvelteKit — any framework that handles HTTP routes.
// Next.js App Router
import { serve } from "inngest/next";
import { inngest } from "./client";
import { processSignup } from "./functions";
export const { GET, POST, PUT } = serve({
client: inngest,
functions: [processSignup],
});
Dashboard
Every event, every function run, every step — visible in the dashboard:
- Event timeline (what events arrived, when)
- Function runs (status, duration, step-by-step execution)
- Step details (input, output, retry count)
- Replay failed runs with one click
Where It Falls Short
Mental Model Shift
If you're used to BullMQ or simple job queues, Inngest's event + step model requires rethinking. "Send event → function triggers → steps execute durably" is more powerful but takes time to internalize.
Step Execution Model
Each step is a separate HTTP request. Inngest calls your function, executes until a step completes, stores the result, then calls your function again for the next step. This means:
- Steps can't share in-memory state (use step results)
- Each step invocation has cold start potential
- Complex functions with many steps make many HTTP requests
Pricing
| Plan | Runs/month | Price |
|---|---|---|
| Free | 25K | $0 |
| Pro | Custom | Usage-based |
| Enterprise | Custom | Custom |
Free tier is half of Trigger.dev's (25K vs 50K). Pro pricing is usage-based — costs can grow unpredictably with high-volume applications.
Self-Hosting Complexity
Self-hosting is available but requires more infrastructure than Trigger.dev's Docker setup. Enterprise feature for most teams.
Limited to TypeScript/JavaScript
No Python, Go, or other language SDKs. If your backend isn't TypeScript/JavaScript, Inngest isn't an option.
Inngest vs Trigger.dev
| Feature | Inngest | Trigger.dev |
|---|---|---|
| Model | Event-driven + steps | Task queue |
| Durable execution | ✅ Step-level | ⚠️ Task-level retry |
| Sleep/wait | ✅ Built-in | ⚠️ Limited |
| Event fan-out | ✅ | ❌ |
| Free tier | 25K runs | 50K runs |
| Dashboard | Excellent | Excellent |
| Learning curve | Steeper | Easier |
| Long-running | ✅ Hours/days | ✅ Hours |
Choose Inngest when: You need durable multi-step workflows, event-driven architecture, or waitForEvent patterns (approval flows, multi-day sequences).
Choose Trigger.dev when: You need straightforward background tasks with simple retry logic. Queue-based mental model. Larger free tier.
Use Cases
Multi-Day Onboarding Sequences
Event: user signs up → Step 1: welcome email → Sleep 2 days → Step 2: tips email → Sleep 5 days → Step 3: check if active → Branch: send re-engagement or success email.
Approval Workflows
Event: expense submitted → Step 1: notify manager → waitForEvent: manager approves/rejects (7-day timeout) → Step 2: process approval or send rejection.
AI Processing Pipelines
Event: document uploaded → Step 1: extract text → Step 2: chunk text → Step 3: generate embeddings → Step 4: store in vector DB → Step 5: notify user. If Step 3 fails (API rate limit), retry from Step 3 only.
Webhook Processing
Event: Stripe webhook → multiple functions: update subscription, send receipt email, sync to CRM, update analytics. Each function independent.
FAQ
Is Inngest production-ready?
Yes. Stable and used in production by thousands of applications. The event-driven model is well-suited for production workloads.
Can I use Inngest without events?
Yes. You can trigger functions directly via inngest.send() with custom events, or use cron schedules. But the event-driven model is where Inngest shines.
How does pricing scale?
Each step execution counts as a "run." A function with 5 steps counts as 5 runs. High-step-count functions consume the free tier faster.
What happens if my server goes down during a function?
Inngest retries the current step. Previous step results are cached. No data loss, no duplicate work.
Bottom Line
Inngest is the best choice when you need durable, multi-step workflows in TypeScript. The step function model — with sleep, waitForEvent, and automatic step-level retries — handles complex workflows that simple job queues can't express cleanly.
Start with: Replace your most fragile multi-step process (onboarding sequence, order processing, webhook handling). Define each step explicitly. See how step-level retries and the dashboard improve reliability. Expand to more workflows as the pattern clicks.