Node.js + Inngest in 2026: Durable Background Jobs Without Infrastructure
product-development11 min readintermediate

Node.js + Inngest in 2026: Durable Background Jobs Without Infrastructure

Vivek Singh
Founder & CEO at Witarist · May 23, 2026

Every Node.js team eventually runs into the same wall: you ship a feature, traffic grows, and suddenly half your codebase is background work. Welcome emails. Webhook fan-out. Scheduled exports. Stripe reconciliation. AI pipeline calls that take 90 seconds. The HTTP request finishes in 200 ms, but the real work is happening somewhere else — and that somewhere else is where reliability quietly dies.

For years, the answer was "spin up Redis, install BullMQ, write a worker process." That still works, but it forces a Node.js team to operate a queue, a worker fleet, a dead-letter system, a retry strategy, and an observability layer — none of which has anything to do with the business problem. Inngest takes a different bet: durable execution as a managed runtime, with code that looks like a normal async function. In 2026 it has matured into one of the most pragmatic choices for Node.js background work, and this guide is the production playbook.

Why Background Jobs Are Painful in Node.js

A Node.js process is single-threaded. The event loop is excellent at I/O fan-out, but the moment you mix request handling with long-running work — image processing, AI calls, batched database writes — you pay for it in p99 latency and OOMs. The textbook fix is to move that work off the request path and into a queue. The textbook fix is also where most teams begin to suffer.

The hidden cost of "just add a queue"

A production-grade BullMQ setup is rarely just BullMQ. You need a Redis instance (with persistence, replication, and a memory eviction policy that won't silently drop your jobs). You need a separate worker process — usually one per queue, sometimes more — and a deployment pipeline for it. You need retry logic with exponential backoff. You need dead-letter handling. You need idempotency keys for the jobs that mutate state. You need a dashboard to see what is failing and why, otherwise you find out about a broken webhook from a Stripe support ticket two days later.

Cron is even worse

Scheduled jobs with node-cron or system crontabs add another dimension of pain: at-most-once vs at-least-once delivery, time zone bugs, missed executions when the worker restarts mid-tick, and the impossibility of safely backfilling a missed run. By the time a Node.js team has reinvented all of this in-house, they have effectively built a poor man's workflow engine.

What Inngest Actually Is — and What It Replaces

Inngest is a durable execution runtime for event-driven functions. You write a normal async function with `inngest.createFunction`, you trigger it by sending an event from your Node.js API, and Inngest invokes the function as many times as needed to drive it to completion — across crashes, deploys, and outages. The function code lives inside your existing application (Express, Fastify, NestJS, Next.js, Hono — anything that can serve an HTTP endpoint). There is no separate worker fleet to operate.

The replacement matrix

In one move, Inngest replaces a Redis queue, a worker process, a retry library, a dead-letter handler, a cron scheduler, and most of an observability dashboard. For teams already invested in Redis for caching, you keep using it for caching — you just stop using it as the brain of your job system.

The mental model

Think of Inngest as a server-side orchestrator that calls back into your code. When you call `step.run("send-email", fn)`, Inngest stores the result of `fn()` and never re-runs it if the function later resumes. When you call `step.sleep("24h")`, your function is genuinely suspended for 24 hours — no Redis-backed delayed queue, no worker keeping the connection alive. When you call `step.waitForEvent("user/clicked")`, your function waits for that event to be published, even if it takes a week.

Architecture diagram showing how a Node.js Inngest function executes durably across four steps with persistent state
Figure 1 — The execution model: events trigger functions, steps memoize their results, and the runtime resumes from the last completed step after any crash.

Setting Up Inngest in a Node.js Project

Installing Inngest takes about ten minutes for a basic setup. You install the SDK, create an Inngest client, write your first function, and mount an HTTP handler. The exact wiring depends on your framework, but the pattern is identical. Most Node.js backends we ship at HireNodeJS follow the structure shown in the code block below — a single `inngest.ts` file with the client, and one file per function group.

The minimal wiring

inngest-setup.ts
// inngest.ts — client setup
import { Inngest } from "inngest";

export const inngest = new Inngest({
  id: "billing-service",
  // Sign keys are loaded from env; never hardcode
});

// functions/process-invoice.ts
import { inngest } from "../inngest";

export const processInvoice = inngest.createFunction(
  { id: "process-invoice", retries: 5 },
  { event: "billing/invoice.created" },
  async ({ event, step }) => {
    // Each step.run is memoized. If the function crashes after
    // step 1, the rerun starts at step 2 — step 1 is NOT re-executed.
    const customer = await step.run("fetch-customer", () =>
      stripe.customers.retrieve(event.data.customerId)
    );

    const pdf = await step.run("render-pdf", () =>
      renderInvoicePdf(customer, event.data.lineItems)
    );

    // Durable sleep — the function literally goes to sleep for 6h.
    // No worker holds a connection.
    await step.sleep("wait-for-grace-period", "6h");

    await step.run("send-invoice-email", () =>
      resend.emails.send({
        to: customer.email,
        subject: "Your invoice is ready",
        attachments: [{ filename: "invoice.pdf", content: pdf }],
      })
    );

    return { sent: true, customerId: customer.id };
  }
);

// app.ts — mount on any HTTP framework
import express from "express";
import { serve } from "inngest/express";
import { inngest } from "./inngest";
import { processInvoice } from "./functions/process-invoice";

const app = express();
app.use("/api/inngest", serve({ client: inngest, functions: [processInvoice] }));
app.listen(3000);

Triggering a function

Once mounted, you trigger work by sending an event from anywhere in your codebase — typically from an HTTP route handler, a Stripe webhook, or another Inngest function. Events are typed if you use the TypeScript schemas (recommended), so the editor will refuse to let you send a malformed payload.

💡Tip
Use the Inngest Dev Server during local development (`npx inngest-cli@latest dev`). It runs entirely on your machine, dispatches events to your local HTTP endpoint, and gives you a real-time visualization of step execution — far better than tailing logs.
Figure 2 — Engineering hours per month a typical 5-engineer team reclaims by moving from hand-rolled BullMQ to Inngest.

Writing Durable Functions with Steps

Steps are the unit of durability in Inngest. Anything inside `step.run` is checkpointed: the return value is persisted, and if the function is re-invoked, that step is never re-executed. This is what makes Inngest functions safe to retry — only the work that has not yet been confirmed will run again.

Memoization and idempotency

The rule of thumb is: any side effect that should happen exactly once goes inside its own `step.run`. Database writes, Stripe charges, third-party API calls, email sends — each is a separate step. Pure computation can stay outside steps, since it will be re-executed on retry without any side-effect risk. Senior Node.js engineers who have worked with workflow engines before will recognize this pattern instantly; it is the same contract that Temporal, AWS Step Functions, and Cadence have used for years.

Fan-out and fan-in

`step.parallel` lets a single function dispatch many sub-steps concurrently and wait for all of them — useful for batched API calls, fan-out notifications, or processing every item in an export. Each parallel branch is itself memoized, so a single branch failing does not re-run the others.

⚠️Warning
Steps are stored as JSON. Do NOT return non-serializable values (Date objects, class instances, Buffers) from a step.run callback. Return primitives or plain objects, and rehydrate types from them on the next step. This is the #1 source of "my function works locally but fails in prod" bug reports.

Patterns You Get Free with Inngest

Ready to build your team?

Hire Pre-Vetted Node.js Developers

Skip the months-long search. Our exclusive talent network has senior Node.js experts ready to join your team in 48 hours.

Most of the patterns that take weeks to implement on top of BullMQ — and that then become the team's on-call burden — ship out of the box with Inngest. None of these require additional infrastructure; they are all part of the runtime.

Durable sleeps and time-based workflows

Trial-ending reminders, 24-hour onboarding sequences, and "check back in a week" flows are one-liners: `await step.sleep("7d")`. The function literally suspends — no worker holds a TCP connection, no timer-wheel lookup runs every 100 ms across millions of jobs.

Event coordination

`step.waitForEvent("user/email-verified", { timeout: "24h" })` blocks the function until that event arrives — perfect for human-in-the-loop flows, multi-step verifications, or webhook callbacks. If the timeout elapses, you handle the timeout branch explicitly; no missed-event detection logic needed.

Rate limiting and concurrency

Set `concurrency: { limit: 5, key: "event.data.userId" }` on a function and Inngest will only run five concurrent invocations per user — useful for outbound API calls where the third-party rate limit is per-account. The same primitive expresses tenant isolation in multi-tenant SaaS apps.

Figure 3 — Six-dimension scorecard comparing Inngest, BullMQ, Temporal, and RabbitMQ on factors that matter in production.

Choosing Inngest vs BullMQ vs Temporal vs RabbitMQ

There is no universal winner — there is the right tool for your team's constraints. BullMQ is unbeatable for teams that already run Redis, want full ownership of their queue, and are comfortable operating a worker fleet. Temporal is the strongest choice when you need polyglot workflows (Java, Go, and Node.js workers cooperating on the same workflow) or when on-prem deployment is non-negotiable. RabbitMQ wins when you genuinely need broker-grade messaging semantics: exchanges, topic-routing, and consumer groups for high-throughput streams.

Inngest wins when developer velocity matters more than operational ownership. Most product teams shipping web apps fall into this bucket. The trade-off is real: you outsource the runtime and pay per execution. For most teams the math works out, because the alternative is paying for an engineer's time to maintain the queue instead.

Horizontal bar chart comparing time to first production background job across Inngest, Trigger.dev, BullMQ, RabbitMQ, Temporal, and Kafka
Figure 4 — Median engineer-hours from `npm install` to a job running reliably in production, by stack.

When NOT to Use Inngest

Inngest is not the answer to every async problem. There are three scenarios where another tool is the better fit, and pretending otherwise will burn your team.

Sub-second, high-throughput streams

If you are ingesting millions of clickstream events per second and pushing them into a real-time analytics pipeline, you want Kafka or a purpose-built stream processor. Inngest is optimized for orchestrating business logic with seconds-to-hours latency, not microsecond throughput.

Bulk data movement

ETL jobs that move terabytes between data warehouses belong in something like Airflow, Dagster, or a managed ELT tool. Inngest can drive the orchestration, but the actual data movement should run on infrastructure designed for it.

Air-gapped or strict on-prem

Inngest offers a self-hosted runtime, but most teams use the cloud service. If your compliance posture forbids any third-party runtime touching your job metadata, Temporal Self-Hosted is the stronger fit.

🚀Pro Tip
It is common to run Inngest alongside BullMQ in the same codebase: Inngest for orchestration and human-facing workflows, BullMQ for high-throughput internal queues where you already own the Redis cluster. They are not mutually exclusive — choose per use case, not as an all-or-nothing decision.

Hiring Node.js Engineers Who Build Reliable Job Systems

The hardest part of background-job work is not the framework — it is the engineering judgment. Knowing when to use a step, when to fan out, when to make a write idempotent, and when to add a circuit breaker is the difference between a system that runs unattended for a year and a system that pages on-call every Sunday. When hiring Node.js developers for backend roles, screen for this judgment directly: ask the candidate to describe a job system they shipped, the failure modes they encountered, and how they would design it differently today.

A strong Node.js + TypeScript engineer in 2026 should be fluent in at least one durable-execution model (Inngest, Temporal, AWS Step Functions, or a hand-rolled equivalent), comfortable reasoning about exactly-once vs at-least-once semantics, and instinctively reach for idempotency keys when designing any state-mutating job.

Hire Expert Node.js Developers — Ready in 48 Hours

Building the right system is only half the battle — you need the right engineers to build it. HireNodeJS.com specialises exclusively in Node.js talent: every developer is pre-vetted on real-world projects, API design, event-driven architecture, durable execution patterns, and production deployments.

Unlike generalist platforms, our curated pool means you speak only to engineers who live and breathe Node.js. Most clients have their first developer working within 48 hours of getting in touch. Engagements start as short-term contracts and can convert to full-time hires with zero placement fee — useful when you want to validate the engineer on a real Inngest migration before committing.

💡Tip
Ready to scale your Node.js team with engineers who have shipped durable background-job systems in production? HireNodeJS.com connects you with pre-vetted engineers who can join within 48 hours — no lengthy screening, no recruiter fees. Browse developers at hirenodejs.com/hire

Conclusion: Durability as a Default

Background jobs are the part of a Node.js system most likely to fail silently and most expensive to fix in production. The instinct to reach for a queue, a worker, and a Redis box is correct — but in 2026 the alternative is so much simpler that the trade-off has shifted. Inngest turns durability into a property of the runtime, not a property your team has to implement and operate. For most product engineering teams shipping web applications, that is the right default.

Start with one function — the next welcome-email flow, the next webhook handler, the next scheduled export — and ship it on Inngest. Measure the on-call pages it generates over the next month against the equivalent BullMQ system. The numbers tend to tell the story on their own.

Topics
#Inngest#Background Jobs#Durable Execution#Node.js#BullMQ#Event-Driven#Serverless

Frequently Asked Questions

What is Inngest in Node.js, and how is it different from a job queue?

Inngest is a durable execution runtime for event-driven functions in Node.js. Unlike a traditional job queue (BullMQ, RabbitMQ), it does not require you to operate Redis, a worker fleet, or retry logic — the runtime memoizes each step of your function and resumes from the last completed step on retry.

Is Inngest a good replacement for BullMQ in 2026?

For most product-engineering teams running web apps, yes — Inngest replaces BullMQ, the Redis backing it, the worker process, and most retry plumbing with one managed runtime. Teams already invested in operating Redis at scale, or needing sub-millisecond throughput, may stay on BullMQ.

How much does Inngest cost compared to running BullMQ on your own infrastructure?

Inngest charges per function execution (with a generous free tier for development). For most small-to-mid Node.js apps the bill is lower than the cost of a managed Redis instance plus engineering time for retry, observability, and dead-letter handling.

Can Inngest handle long-running workflows, like a 7-day onboarding sequence?

Yes. `step.sleep("7d")` literally suspends the function for seven days — no worker holds a connection, no timer-wheel polls. Workflows lasting weeks or months are a first-class use case.

Does Inngest work with NestJS, Fastify, Hono, and Next.js?

Yes. Inngest ships official adapters for Express, Fastify, NestJS, Next.js, Hono, AWS Lambda, Cloudflare Workers, and most other Node.js HTTP frameworks. You mount a single endpoint and the runtime invokes your functions through it.

How do I hire a Node.js developer experienced with Inngest or durable execution?

HireNodeJS.com specialises in pre-vetted Node.js engineers. Screen for durable-execution experience by asking candidates about idempotency keys, exactly-once vs at-least-once semantics, and a job system they shipped. We can introduce a vetted engineer within 48 hours.

About the Author
Vivek Singh
Founder & CEO at Witarist

Vivek Singh is the founder of Witarist and HireNodeJS.com — a platform connecting companies with pre-vetted Node.js developers. With years of experience scaling engineering teams, Vivek shares insights on hiring, tech talent, and building with Node.js.

Developers available now

Need a Node.js engineer who can ship durable background-job systems?

HireNodeJS connects you with pre-vetted senior Node.js engineers experienced with Inngest, BullMQ, Temporal, and event-driven architectures — available within 48 hours. No recruiter fees, no lengthy screening.