Inngest vs Trigger.dev vs BullMQ in 2026: Which Background Job System Should Your SaaS Use
The Problem Serverless Created
Every SaaS eventually needs to do work that doesn't fit inside a web request. Send a five-email onboarding sequence over a week. Process an uploaded video. Retry a flaky call to a third-party API until it succeeds. Run a nightly cleanup. Fan a single "user signed up" event out into a dozen downstream actions. None of these belong in the request/response cycle — the user shouldn't wait, and the work shouldn't die when the response is sent.
On a traditional always-on server this was easy: you had a long-lived process, so you ran a queue and a worker next to your app. But the modern default for a Next.js app is serverless — short-lived functions that spin up, handle a request under a timeout, and freeze. There's no persistent process to hold a queue, no place to run a worker, and no guarantee your "background" task survives past the response. That mismatch is why background-job systems exist, and why the three names below keep coming up.
In 2026 the three that dominate the TypeScript conversation are Inngest, Trigger.dev, and BullMQ. They get compared as rivals, but like most tooling trios they aren't identical — they sit at different points on a single spectrum: *how much infrastructure do you want to own, and where does your job code run?*
Put simply: Inngest removes the infrastructure, Trigger.dev manages the heavy compute, and BullMQ hands you the whole engine. The rest of this guide matches each to the question you actually have.
Data center servers and network connections representing distributed background work
Three Shapes of "Run This Later"
Before comparing features, it helps to see the three architectures underneath, because they explain every trade-off that follows.
Inngest: orchestrator in the cloud, code in your app. You register functions in your Next.js deployment. When an event fires or a cron schedule hits, Inngest calls your function over HTTP — step by step. Each step is persisted and retried independently, so a failure resumes from where it stopped rather than re-running everything. There is no Redis, no worker process, nothing always-on that you operate. The trade you're making: you accept a managed orchestrator (and usage-based pricing) in exchange for zero queue infrastructure. This is the model built specifically for the serverless world.
Trigger.dev: orchestrator and compute both managed. Your tasks don't run inside your serverless functions at all — they run on Trigger.dev's own dedicated, long-running compute. That's the key difference: because the platform owns the compute, jobs aren't bounded by serverless timeouts, so a video encode or a large data import can run for minutes or hours, checkpoint its state, and resume after an interruption. It's open-source (Apache-2.0), so if you want to own that infrastructure you can self-host it. The trade: you get long-running durability and an excellent run dashboard, at the cost of running (or paying for) compute sized to the work.
BullMQ: your queue, your worker, your Redis. BullMQ is a library, not a hosted service. You run a Redis instance, you write an always-on worker process that pulls jobs off the queue, and you deploy and monitor it yourself. In return you get the richest low-level control — concurrency, rate limiting, priorities, delayed and repeatable jobs, and flow graphs (parent-child dependencies) — and no per-run pricing, just your Redis and compute bill. The trade is ownership: everything that the managed platforms do for you is now yours to build and keep healthy.
The reason this matters: a serverless-native tool can't give you a long-lived worker's cost profile, a managed platform's convenience isn't free, and a bare library won't operate itself. Picking the wrong shape means fighting your infrastructure instead of your product.
Developer Experience with Next.js and TypeScript
All three are TypeScript-first and integrate with Next.js, but the day-to-day feel differs.
Inngest
You install the SDK, create an Inngest client, define functions that respond to events or schedules, and serve them from a single API route. The mental model is functions and steps:
export const onboarding = inngest.createFunction(
{ id: "onboarding" },
{ event: "user/signed-up" },
async ({ event, step }) => {
await step.run("send-welcome", () => sendWelcome(event.data.email));
await step.sleep("wait-a-day", "1d");
await step.run("send-tips", () => sendTips(event.data.email));
}
);That step.sleep("1d") is the magic: the function doesn't hold a process open for a day. Inngest suspends the run and resumes it a day later, calling your endpoint again — durable, serverless-safe delays with no worker. Local development runs against the Inngest dev server so you can see events and replays. For most serverless SaaS this is the smoothest path to real background work.
Trigger.dev
You define tasks, and the CLI links your project to the platform. The feel is "write a task, deploy it, watch it run":
export const processVideo = task({
id: "process-video",
run: async (payload: { url: string }) => {
const file = await download(payload.url);
const out = await transcode(file); // can run for minutes
return await store(out);
},
});Because the task runs on Trigger.dev's long-running compute, that transcode step can take real time without a timeout. The standout is the dashboard: every run is visible, inspectable, and replayable, with logs and state — which is why it's a favorite for long AI, media, and integration workflows where you need to see what happened, not just that it finished. Pairs naturally with the Vercel AI SDK for multi-step LLM pipelines.
BullMQ
You write a queue and a worker, and you run the worker as its own process:
const queue = new Queue("emails", { connection });
// in an always-on worker process:
new Worker("emails", async (job) => {
await sendEmail(job.data);
}, { connection, concurrency: 5 });The API is clean and the primitives are powerful — concurrency, limiter for rate limits, repeat for cron, priority, and flows for parent-child jobs. The catch is operational, not ergonomic: that Worker has to run somewhere always-on, which is trivial on Railway, Fly.io, or Render and awkward on pure Vercel. You'll also want a dashboard (Bull Board) since BullMQ ships no UI.
Pricing and Cost at Scale
This is where the spectrum shows up most sharply, so match the model to your volume.
$15–50 in Redis hosting on BullMQ versus $75–150 on a managed cloud plan — the gap widens as volume grows.The honest read: at low volume the managed platforms are effectively free and save you real operational time, which is worth more than the money. At high, steady volume BullMQ's flat infrastructure cost wins decisively — *if* you already run a worker. The break-even isn't just dollars; it's whether you have (or want) an always-on process and the appetite to operate Redis. (Trigger.dev's open-source self-hosting is a middle path: managed-style ergonomics on infrastructure you own.)
Developer reviewing job runs and logs on multiple screens
The Guarantees That Actually Matter
Whichever tool you pick, background jobs live or die on a few properties. Get these right and any of the three is production-grade; get them wrong and all three will hurt.
Idempotency
A retried job runs your side effect again. If "send invoice email" or "charge the card" isn't idempotent, a retry double-sends or double-charges. Design every job so running it twice is safe — key off an idempotency token, check-then-act, or make the operation naturally repeatable. This is the single most important habit, and it's on you regardless of tool.
Retries and Backoff
Transient failures (a flaky API, a brief network blip) should retry with backoff; permanent failures (a 400, a validation error) should not retry at all — retrying them just burns runs and, on usage-priced platforms, money. Inngest and Trigger.dev retry at the step/task level automatically; BullMQ retries at the job level with configurable attempts and backoff. Cap retries and handle the give-up case explicitly (dead-letter or alert) rather than letting jobs vanish.
Concurrency and Rate Limits
A burst of jobs can hammer a downstream API and get you throttled or banned. All three let you cap concurrency — Inngest and Trigger.dev as configuration, BullMQ via concurrency and limiter. Set these deliberately for anything that touches a rate-limited service like an email provider or a payments API.
Durability
Managed platforms persist state for you — Inngest remembers completed steps across redeploys, Trigger.dev checkpoints long runs so they resume. With BullMQ, durability is Redis's job, which means you must configure Redis persistence and reliability yourself. Powerful, but now it's your responsibility.
Common Use Cases, Matched to a Tool
step.sleep handles multi-day delays on serverless with no worker, which is exactly this pattern. Works hand-in-glove with your email provider.What This Means If You Build to Sell
If you're packaging a SaaS starter kit, Next.js boilerplate, or dashboard template to sell on CodeCudos, your background-job choice signals a lot about the codebase — and about what the buyer inherits the moment they clone it. Buyers notice:
For most serverless templates, Inngest wired in but no-op-without-a-key, with BullMQ documented as the persistent-host alternative, is the resale-safe default. These are the same standards that make any code read as production-ready, and they compound with the rest of a credible build: a coherent tech stack, clean analytics, sane monitoring, and a sensible host.
The Bottom Line
There's no universal winner — there's a right tool for where your app runs, how heavy your jobs are, and how much infrastructure you want to own.
Whichever you choose, the habit that outlasts the decision is the same: make jobs idempotent so retries are safe, cap concurrency and retries, watch your run volume on usage-priced platforms, treat Redis persistence and worker health as first-class on self-hosted ones, and keep every key in env vars. That discipline costs little and pays back on every job that fails, retries, and quietly succeeds anyway — and every buyer who clones your repo.
Ready to turn what you build into income? List your SaaS or template on CodeCudos, see how background jobs fit the wider stack in our best tech stack for web apps in 2026 guide, wire up monitoring with Sentry vs LogRocket vs Datadog, pick your host with Vercel vs Netlify vs Railway, or make sure the whole build reads as production-ready.
