← Back to articles

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

FeatureDescription
Event-driven functionsFunctions triggered by events
Step functionsBreak workflows into retryable steps
Durable executionResume from the last successful step
SchedulingCron jobs and delayed execution
ConcurrencyPer-function concurrency limits
ThrottlingRate limit function execution
Fan-outOne event triggers multiple functions
DashboardMonitor 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 result
  • step.sleep() — Wait for a duration ("3 days", "2 hours")
  • step.sleepUntil() — Wait until a specific time
  • step.waitForEvent() — Pause until another event arrives
  • step.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

PlanRuns/monthPrice
Free25K$0
ProCustomUsage-based
EnterpriseCustomCustom

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

FeatureInngestTrigger.dev
ModelEvent-driven + stepsTask queue
Durable execution✅ Step-level⚠️ Task-level retry
Sleep/wait✅ Built-in⚠️ Limited
Event fan-out
Free tier25K runs50K runs
DashboardExcellentExcellent
Learning curveSteeperEasier
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.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.