TL;DR: Inngest is a service that runs your background jobs — emails, data processing, scheduled tasks — without you managing queues, workers, or Redis. You write a function, trigger it with an event, and Inngest handles retries, scheduling, and observability. It's serverless, works natively in Next.js, and AI tools generate it automatically when you describe async workflows. Think of it as the plumbing between "user clicked the button" and "the thing that needed to happen in the background actually happened."

The Problem: What Happens After the Button Click

Here's a scenario every builder runs into. Your app has a signup form. User fills it out, hits submit, and gets a "Welcome!" screen. Simple, right?

Except — you also need to send a welcome email. And add them to your CRM. And maybe trigger a Slack notification to your sales team. And if they signed up for the pro plan, kick off an account provisioning job that takes 10 seconds to run.

You could do all of that synchronously — pile it all into the same API route that handles the signup. The user waits. And waits. Maybe it times out. Maybe one of those external API calls fails halfway through and you're left with a half-provisioned account. It's a mess.

The right answer is: do the fast stuff immediately (save the user to your database, return success), then do all the slow stuff in the background. The user gets their "Welcome!" screen instantly. The welcome email goes out a few seconds later. The account provisioning runs on its own. Everything happens, but nothing blocks anything else.

That's the problem Inngest solves. It's the system that runs the stuff that needs to happen after the button click, without making the user wait for it.

If you've heard of message queues like BullMQ, that's the traditional solution — but it requires running a Redis database and a separate worker process, configuring them together, and keeping both alive in production. Inngest does the same job with none of that infrastructure.

What Inngest Actually Is

Inngest is an event-driven job orchestration platform. That's a mouthful, so let's break it down into three parts:

Event-driven means jobs run because something happened — a user signed up, a payment completed, a file was uploaded — not on a fixed schedule (though it can do that too).

Job orchestration means it manages the sequencing, retrying, and scheduling of your background tasks. If a step fails, it retries it. If you need to wait between steps, it handles that. You don't write the retry logic yourself.

Platform means there's infrastructure Inngest runs on their end. You write the code; they handle the machinery.

The way it fits into your app is elegant. Your Inngest functions live inside your existing project as regular API route handlers. Inngest's servers call those routes when they need to execute a job. You're not running a separate process anywhere — the function is just a normal route that Inngest happens to know about.

This is the key reason AI tools reach for Inngest when you're building on Next.js or any other serverless framework: it requires zero additional server infrastructure. It's the same model as your app's existing API routes, just with Inngest handling when and how often to call them.

Related: if you're fuzzy on how serverless works in general, read our explainer on what serverless actually means.

How It Works: Events, Functions, and Steps

There are three concepts you need to understand to read Inngest code: events, functions, and steps.

Events

An event is a signal that something happened. You send events from your app code — from your API routes, your webhook handlers, wherever something worth responding to occurs.

Events have a name and a data payload. The name describes what happened. The data is whatever information the background function will need.

// Send an event from your API route (e.g. after a user signs up)
await inngest.send({
  name: "user/signup.completed",
  data: {
    userId: "usr_123",
    email: "alex@example.com",
    plan: "pro"
  }
});

That's it. Your API route is done. It returns a response to the user immediately. Inngest receives the event and hands it off to any functions that are listening for it.

Functions

A function is the code that runs in response to an event. You define it once — Inngest knows to call it whenever it sees the matching event name.

// Define a background function
export const sendWelcomeEmail = inngest.createFunction(
  { id: "send-welcome-email" },
  { event: "user/signup.completed" },
  async ({ event, step }) => {
    // This runs in the background — the user already got their response
    await step.run("send-email", async () => {
      await resend.emails.send({
        from: "hello@yourapp.com",
        to: event.data.email,
        subject: "Welcome to the app!",
        html: `

Hey, welcome aboard!

` }); }); } );

The function receives the event and its data. The step.run() wrapper is important — that's what gives Inngest the ability to retry individual pieces if they fail.

Steps

Steps are the building blocks of multi-part workflows. Each step.run() call is a discrete chunk of work that Inngest tracks independently. If step 2 fails, Inngest retries step 2 — it doesn't re-run step 1, because that already completed successfully.

Think of it like subcontractors on a job. If the electrician's work needs to be redone, you don't tear down the walls the framer already built. You call the electrician back for just that piece. Inngest steps work the same way.

Steps can also wait. You can tell Inngest "pause here for 24 hours, then continue." This is useful for things like: send a welcome email immediately, then wait one day, then send a follow-up.

export const onboardingSequence = inngest.createFunction(
  { id: "onboarding-sequence" },
  { event: "user/signup.completed" },
  async ({ event, step }) => {
    // Step 1: Send welcome email immediately
    await step.run("welcome-email", async () => {
      await sendWelcomeEmail(event.data.email);
    });

    // Step 2: Wait 24 hours
    await step.sleep("wait-one-day", "24h");

    // Step 3: Send follow-up (runs the next day, automatically)
    await step.run("followup-email", async () => {
      await sendFollowUpEmail(event.data.email);
    });
  }
);

No cron jobs. No scheduled tasks. No database column tracking "has this user received their day-2 email yet." Inngest handles all of that state for you.

Real Example: The Complete Welcome Email Flow

Let's walk through a complete, production-ready signup flow using Inngest. This is exactly the kind of code AI will generate when you ask for "send a welcome email after user signup in Next.js."

Step 1: Install and Configure Inngest

npm install inngest

Create an Inngest client (usually lib/inngest.ts):

// lib/inngest.ts
import { Inngest } from "inngest";

export const inngest = new Inngest({ id: "my-app" });

Step 2: Create the API Route That Serves Your Functions

Inngest needs a single endpoint in your app that it can call. In Next.js App Router:

// app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest } from "@/lib/inngest";
import { sendWelcomeEmail } from "@/lib/functions/welcome";

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [sendWelcomeEmail],
});

This one file is the entire Inngest integration point. All your background functions get registered here. Inngest's servers call this endpoint whenever they need to run a job.

Step 3: Define the Background Function

// lib/functions/welcome.ts
import { inngest } from "@/lib/inngest";
import { Resend } from "resend";
import { db } from "@/lib/db";

const resend = new Resend(process.env.RESEND_API_KEY);

export const sendWelcomeEmail = inngest.createFunction(
  {
    id: "send-welcome-email",
    retries: 3, // Inngest retries automatically on failure
  },
  { event: "user/signup.completed" },
  async ({ event, step }) => {
    const { userId, email, name } = event.data;

    // Step 1: Fetch user from DB (retried independently if it fails)
    const user = await step.run("fetch-user", async () => {
      return await db.user.findUnique({ where: { id: userId } });
    });

    // Step 2: Send the welcome email
    await step.run("send-email", async () => {
      await resend.emails.send({
        from: "hello@yourapp.com",
        to: email,
        subject: `Welcome to the app, ${user.name}!`,
        html: `
          

Welcome, ${user.name}!

Your account is ready. Here's how to get started...

`, }); }); // Step 3: Update DB to mark welcome email as sent await step.run("mark-email-sent", async () => { await db.user.update({ where: { id: userId }, data: { welcomeEmailSentAt: new Date() }, }); }); return { success: true, email }; } );

Step 4: Trigger the Function from Your Signup Route

// app/api/auth/signup/route.ts
import { inngest } from "@/lib/inngest";
import { db } from "@/lib/db";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const { email, name, password } = await req.json();

  // Create user in database
  const user = await db.user.create({
    data: { email, name, password: hashPassword(password) }
  });

  // Fire the event — returns immediately, doesn't wait for email to send
  await inngest.send({
    name: "user/signup.completed",
    data: { userId: user.id, email: user.email, name: user.name }
  });

  // User gets this response right away — no waiting for email processing
  return NextResponse.json({ success: true, userId: user.id });
}

That's the complete picture. The signup route creates the user and fires an event in milliseconds. The user sees "Welcome!" instantly. The welcome email goes out a moment later, handled entirely by Inngest in the background.

Prompt that generates this pattern:

"After a user signs up, send them a welcome email using Resend. Don't block the signup response — run the email in the background. Use Inngest for the background job. I'm using Next.js App Router and Prisma."

Scheduled Tasks: Inngest as a Cron Replacement

Inngest also handles scheduled jobs — things that need to run on a recurring schedule rather than in response to an event. The syntax uses a cron expression (the same format traditional cron jobs use) but you get all the same retry logic, observability, and step-based execution.

// lib/functions/weekly-digest.ts
export const weeklyDigest = inngest.createFunction(
  { id: "weekly-digest-email" },
  { cron: "0 9 * * MON" }, // Every Monday at 9am UTC
  async ({ step }) => {
    // Get all active users
    const users = await step.run("fetch-active-users", async () => {
      return await db.user.findMany({
        where: { status: "active", digestEnabled: true }
      });
    });

    // Send each user their digest (Inngest handles the fan-out)
    await step.run("send-digests", async () => {
      await Promise.all(
        users.map(user => sendDigestEmail(user))
      );
    });

    return { sent: users.length };
  }
);

Compare this to a traditional cron job. With a cron job, you need a server that's always running, you need to set up the cron schedule in that server's configuration, and if the job fails, you find out by checking logs manually (or not at all).

With Inngest, the function is in your codebase alongside everything else. If it fails, you see it in the Inngest dashboard. You can replay it with one click. You can see the full history of every run. The infrastructure for running it on schedule lives on Inngest's end, not yours.

Inngest vs. The Alternatives

When AI generates code for background jobs, it might reach for several different solutions depending on your stack. Here's how Inngest compares to the main alternatives.

Inngest vs. BullMQ

BullMQ is the most popular Node.js queue library. It's powerful, battle-tested, and handles very high throughput. But it requires Redis — you need a Redis server running and accessible, and a separate worker process that stays alive to pick up jobs from the queue.

That's fine if you're running a persistent server (like a Node.js app on a VPS). But if you're on Vercel, Netlify, or any serverless platform, a persistent worker process doesn't fit the model. Functions spin up and down — there's no "always-on" process to run your worker.

Inngest is the answer to that problem. It works via HTTP: Inngest's servers call your function endpoint when there's work to do. No persistent process required. No Redis to manage.

Feature Inngest BullMQ
Works on Vercel/serverless ✅ Yes ❌ No
Requires Redis No Yes
Built-in retries ✅ Yes ✅ Yes
Step-level retries ✅ Yes No (job-level only)
Built-in dashboard ✅ Yes Via Bull Board (add-on)
Multi-step workflows ✅ First-class Manual chaining
Max throughput High (platform limits apply) Very high (self-hosted)

Inngest vs. Cron Jobs

Traditional cron jobs are scripts you run on a schedule — every night at midnight, run this. They've been around forever and work fine for simple cases. The problems show up when: the job fails silently, you want to see what happened, or you need more than one step. Inngest's cron support gives you all the same scheduling options but with visibility, retries, and the ability to build multi-step workflows on top.

Inngest vs. Temporal

Temporal is a heavyweight workflow orchestration system used at companies like Uber, Netflix, and Stripe. It's extremely powerful and can handle workflows that run for months or years with guaranteed execution semantics. It also requires self-hosting a Temporal server cluster, learning a specific programming model, and significant operational overhead.

For most indie projects and early-stage SaaS apps, Temporal is like hiring a structural engineering firm to build a garden shed. Inngest is the right tool — most of the capability at a fraction of the complexity. If you eventually outgrow Inngest (rare), Temporal will still be there.

Why the Serverless Model Changes Everything

The reason Inngest has caught on so quickly in the Next.js ecosystem comes down to how modern apps are deployed.

When you deploy to Vercel, Netlify, or Cloudflare Pages, you're not renting a server that runs 24/7. You're deploying functions that spin up on demand, handle a request, then shut down. This is the serverless model, and it has a lot of advantages — you pay for what you use, you scale automatically, you don't maintain servers.

But it creates a problem for background jobs. Traditional approaches assume you have a persistent process running — a worker that's always awake, always polling the queue. Serverless functions don't work that way. They wake up, do something, and go back to sleep.

Inngest's model fits the serverless world perfectly because it drives your functions rather than requiring your functions to poll. When Inngest has work to do, it makes an HTTP request to your function endpoint. Your function wakes up, runs the job, returns a response. Done. No persistent process, no polling loop, no background thread.

This is the same pattern webhooks use — instead of your server asking "is there new data?", the external system calls your server when there is. Inngest is basically a webhook system for your own background jobs.

Local Development with the Inngest Dev Server

One of the best parts of Inngest for vibe coders is the local development experience. When you're building locally, you run the Inngest Dev Server alongside your app:

npx inngest-cli@latest dev

This spins up a local version of the Inngest platform at localhost:8288. It automatically discovers your functions (by calling your app's Inngest endpoint), and gives you a dashboard where you can:

  • Send test events manually to trigger your functions
  • Watch functions run step by step in real time
  • Inspect the full event payload and function output
  • Replay any failed run with one click
  • See the complete history of every function execution

This observability is something you'd have to build yourself with BullMQ (using a tool like Bull Board). With Inngest, it's just there.

Good prompts that produce Inngest code:

  • "After a user purchases, send them a receipt email and notify the team on Slack. Don't block the API response. Use Inngest."
  • "I need a weekly digest email that goes out every Monday. Use Inngest cron for the scheduling."
  • "When a user uploads a file, process it in the background — resize the image, upload to S3, update the database. Use Inngest with multiple steps so each part retries independently."

When Inngest Isn't the Right Choice

Inngest is the right default for most Next.js and serverless apps. But there are cases where something else fits better.

Very high throughput. If you need to process tens of thousands of jobs per second, BullMQ with a dedicated Redis cluster will outperform Inngest. Inngest is optimized for correctness and developer experience, not extreme volume.

Long-running compute jobs. Inngest functions run inside your serverless function, which means they're subject to your platform's timeout limits (Vercel has a 5-minute max on Pro, for example). If you need to run a job that takes 30 minutes — like a video transcoding pipeline — you need a different approach (a dedicated worker, or a service like Modal or Fly.io).

You're on a persistent server anyway. If your app runs on a VPS or managed Node.js host where you already have a persistent process, BullMQ is excellent and you may prefer having everything self-hosted.

For everything else — async workflows in Next.js apps, email sequences, webhooks triggering multi-step jobs, scheduled reports — Inngest is the pragmatic choice.

  • What Are Message Queues? — The traditional approach Inngest replaces. Good to understand BullMQ and Redis queues even if you end up using Inngest instead.
  • What Is Serverless? — The deployment model that makes Inngest's HTTP-based approach so powerful. Understand why serverless functions can't run persistent workers.
  • What Is a Webhook? — Inngest uses the same pattern webhooks use — the platform calls your function, not the other way around. This explains the mental model.

Frequently Asked Questions

What is Inngest in simple terms?

Inngest is a service that runs code in the background for you — things like sending welcome emails, processing uploaded files, or generating reports — without making the user wait. Instead of managing a queue server, a worker process, and all the infrastructure that connects them, you write a function, tell Inngest what event should trigger it, and Inngest handles everything else including retries when something fails.

Do I need to manage any servers or infrastructure to use Inngest?

No. Inngest is serverless — your functions live inside your existing Next.js (or other framework) app as API routes. Inngest calls those routes when it needs to run a job. You don't spin up a separate worker process, you don't manage a Redis instance, and you don't configure a message broker. The Inngest platform handles orchestration, scheduling, and retries.

How is Inngest different from a cron job?

A cron job runs on a fixed schedule regardless of what's happening in your app — think "run this every night at 2am." Inngest can do scheduled tasks too, but its real power is event-driven: a job runs because something happened, like a user signing up or a payment completing. Cron jobs are also completely dumb about failures — if the job crashes, nothing catches it. Inngest automatically retries failed steps, logs what happened, and lets you see the full history in a dashboard.

When should I use Inngest instead of BullMQ?

Use Inngest if you're building on Next.js, Vercel, or any serverless platform and want zero infrastructure overhead. Inngest handles everything through HTTP — your function is just an API route. Use BullMQ if you have a persistent Node.js server running 24/7 and you need very high throughput or fine-grained control over worker concurrency. For most indie projects and early-stage SaaS apps, Inngest is dramatically simpler to set up and maintain.

Can I use Inngest for free?

Yes. As of early 2026, Inngest has a generous free tier suitable for most small projects and development work. You get a limited number of function runs per month at no cost. Paid plans scale up from there based on usage. During local development, you run the Inngest Dev Server on your machine at no cost, which gives you the full experience including the dashboard, event history, and replay tools.