Inngest for SaaS Background Jobs in 2026: When and How
BookBed's Stripe subscription handler is one webhook function that syncs accountType to Firestore and stops. That's the entire background job, and it doesn't need Inngest. Reaching for a durable-execution platform on a job that simple would be the kind of over-engineering that makes a stack slower to reason about, not faster. Inngest for SaaS background jobs earns its place somewhere else entirely: the moment one business event has to fan out into several dependent steps, each of which can fail on its own, each of which might need to wait hours or days for something outside your control.
What Actually Is Inngest, And Why Would A SaaS Reach For It?

Inngest is an event-driven platform that runs your SaaS functions as durable, step-based workflows with built-in retries, instead of as fire-and-forget background queue jobs. You send an event, Inngest finds every function listening for it, and each one runs with automatic per-step retries. Its own docs describe the model plainly: wrap logic in step.run(), and a failure three steps in retries only that step, not the whole function from scratch.
The part that actually changes how you write code is step.sleepUntil(). A function can pause for six hours, or three days, waiting on a trial-expiry date or a human approval, without you running a worker process the whole time or building your own polling table. State persists across restarts and redeploys. You stop hand-rolling the "did this job actually finish" bookkeeping that every queue-based system eventually grows.
Queues Guarantee Delivery. Durable Workflows Guarantee Completion.

Read that sentence twice, because it's the entire decision. Inngest's own engineering blog draws this line explicitly: a queue promises your message gets picked up by a worker. It says nothing about whether the multi-step process that message kicks off actually finishes, survives a crash halfway through, or resumes correctly after your Redis instance restarts.
For atomic, self-contained work, that distinction doesn't matter. Send a receipt email, write one row, ping a webhook, and if it fails, retry the whole thing from scratch, cheaply. A plain queue is the right tool here, full stop. Adding step-level durability to a job that never had intermediate state to lose is pure ceremony.
The distinction starts mattering the moment a job has a middle. Enrich a lead, score it, find a contact, draft an outreach email, wait for a human to approve it, send it. Six chained jobs on a raw queue, with your own state table tracking which one is "done," plus a poller checking whether the human ever approved anything. One Inngest function, with each step.run() retried independently and the approval wait costing nothing while it sits idle. AI-driven features push teams toward this shape constantly now, since non-deterministic model output and human-in-the-loop review are exactly the "long wait, no clean restart" pattern a queue was never built for.
Inngest vs BullMQ: Which One Actually Fits Your Stack?

Inngest fits serverless deploys with multi-step or AI-driven workflows; BullMQ fits teams already running Redis who need fine-grained control over concurrency and job priority. Comparison charts on this topic have exploded over the last year, mostly because both tools solved real problems and neither one is obviously "better."
| | Inngest | BullMQ |
|---|---|---|
| Infrastructure | None you manage — cloud-orchestrated, or self-hosted with SQLite + in-memory Redis by default | Redis you provision and operate yourself, plus a persistent worker process |
| Retry model | Per-step, automatic, built into step.run() | Per-job, configurable, you write the retry logic |
| Long waits / human-in-loop | Native (step.sleepUntil(), no infra cost while paused) | Requires your own delayed-job or polling pattern |
| Pricing | Free up to 50,000 executions/month, 5 concurrent | No per-run cost; you pay for Redis hosting instead |
| Ecosystem | 1.2M weekly npm downloads | 5.7M weekly npm downloads |
| Best fit | Serverless deploys (Vercel, Netlify), multi-step or AI workflows | Existing Redis infra, high-throughput, complex job priorities |
BullMQ pulls roughly 4.6x the weekly npm downloads Inngest does, a 2026 package comparison from PkgPulse found, which tells you something honest: most background-job work in production today is still simple enough that a Redis queue with a worker handles it fine, and doesn't need step-level durability at all. Popularity here is a proxy for "how much of this work is actually complex," and the answer is: less than the comparison-blog volume would suggest.
When You Don't Need Inngest At All
Pizzeria Bestek handles five to ten orders a day through Resend webhook emails with CONFIRM and DECLINE buttons built in. No queue, no retries beyond what Resend itself provides, no durable workflow of any kind. At that volume, on that job shape, adding Inngest would be solving a problem the business doesn't have yet, and might never have.
The Execution-Counting Trap On The Free Tier
You've probably read "50,000 free executions a month" and mentally filed Inngest as free for anything you'll build this year. Slow down on that math. Inngest's own pricing page defines an execution as one function run plus every step.run() call inside it — a five-step function burns six executions per invocation, not one. Run that function 200 times a day and you've spent your entire monthly Hobby allotment by the second week, with five concurrent executions as the hard ceiling on top of it. Sketch out your actual step count per function before you assume the free tier covers your MVP.
The Self-Hosting Trap Nobody Mentions In The Comparison Posts
Every "avoid vendor lock-in" argument for Inngest eventually points at self-hosting as the escape hatch, and it does exist. Inngest's own self-hosting docs are candid about what you actually get: a single CLI binary running in-memory Redis and SQLite by default, no built-in authentication, and an explicit statement that it's meant for development or controlled internal environments, not multi-tenant production traffic. Support isn't guaranteed either. Self-host Inngest expecting Inngest Cloud minus the bill, and you'll hit that gap in production, at the worst possible time.
How Do You Actually Migrate A BullMQ Job To Inngest?
Migrate one job at a time: wrap its existing steps in step.run() calls, then run the old and new paths side by side before cutting over. That pattern, from a multi-tenant SaaS I've built, holds up across most Next.js or serverless backends:
- Pick one job with real multi-step state, not your simplest one. A single-step email send proves nothing about whether the migration is worth it.
- Replace the queue's
add()call with an Inngest event send, keeping your existing event payload shape so nothing else in the app needs to change yet. - Wrap each discrete unit of work in its own
step.run(): specifically the part that currently has its own try/catch and its own "mark this step done" flag. - Convert any manual delay, cron poll, or "check again in an hour" loop into
step.sleep()orstep.sleepUntil(), and delete the polling table it replaces. - Run both the old queue path and the new Inngest function in parallel behind a flag for one full billing cycle, comparing completion rates before you cut over.
- Decommission the old worker process only after the parallel run shows equal or better completion, not before.
Do not migrate everything at once. You'll spend a week debugging which system actually owns a given job's state, and that week is avoidable.
What This Looks Like On A Tenant-Isolated SaaS
Callidus OS runs strictly-isolated multi-tenant billing for UK aesthetic clinics, each one a separate tenant with its own Stripe account. A per-tenant nightly invoice reconciliation job is exactly the shape that benefits from step-level retry: fetch this clinic's Stripe data, compare against internal records, flag discrepancies, notify the clinic admin. Picture it running at 3am on a Tuesday, working through the client list alphabetically, and clinic fourteen's Stripe call comes back rate-limited. On a plain queue, depending on how the job was written, that can take the whole nightly run down with it, or silently skip everyone after clinic fourteen. On Inngest, only that one step for that one clinic retries. Everyone else's reconciliation already finished and stays finished. That's the actual argument for wiring background jobs into a B2B SaaS instead of a single-tenant app: one function serving many tenants means one flaky external call shouldn't cost you the whole batch.
None of this works without idempotency keys on the Stripe calls themselves, which is worth saying out loud because the comparison posts rarely do. Durable retries assume the operation being retried is safe to run twice. If step three isn't idempotent, Inngest's retry model just means you double-charge clinic fourteen instead of once. That's not an Inngest problem specifically, it's a distributed-systems problem that a retry-friendly platform makes easier to trip over if you weren't already handling it.
If your event source is a third-party webhook instead of an internal event, the pattern doesn't change much, only the trigger does. And if you're already running Supabase Edge Functions for anything latency-sensitive, keep them there. Inngest is for the durable, multi-step, can-wait-a-while work sitting next to them, not a replacement for every function in your stack.
Actually — that undersells how narrow this decision usually is. Most SaaS products need exactly one or two genuinely durable workflows in their entire lifetime, not a platform migration.
Still deciding what belongs where in your stack at all? Get the rest of your SaaS MVP stack settled before you pick a background-jobs tool, since the job queue decision changes very little once auth, billing, and data isolation are already locked in.
Look at the last background job you shipped. Did it fail cleanly and retry the whole thing, or did it leave something half-done that you had to fix by hand?
