← Back to blog
··14 min read

Inngest vs Trigger.dev vs BullMQ in 2026: Which Background Job System Should Your SaaS Use

InngestTrigger.devBullMQBackground JobsQueuesNext.jsSaaS
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?*

  • Inngest is durable functions with no queue infrastructure: a cloud orchestrator invokes your functions over HTTP with step-level retries and scheduling. Built for serverless. It answers _"I want real background jobs without running a queue."_
  • Trigger.dev is managed long-running tasks with deep run visibility: your tasks run on dedicated long-running compute, checkpoint, and resume — open-source, so you can self-host. It answers _"my jobs are long or heavy and I want to see every run."_
  • BullMQ is a self-hosted Redis queue you fully own: your worker, your Redis, your rules, no per-run fees. It answers _"I already run a process and want maximum control at the lowest cost."_
  • 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

    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:

    ts
    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":

    ts
    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:

    ts
    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.

  • Inngest and Trigger.dev (cloud) are usage-priced — you pay by runs/steps, and Trigger.dev also by the compute time long jobs consume. Both have genuinely usable free tiers (enough for a small app), and both scale roughly linearly with volume. That's a feature at low scale (pay for what you use, zero ops) and a cost to watch as job counts climb.
  • BullMQ has no per-run pricing. Your cost is a Redis instance plus the compute your worker already runs on. As a rough industry benchmark, a SaaS processing on the order of 500K jobs a month tends to land around $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

    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

  • Onboarding email sequences and scheduled reminders → Inngest. Its step.sleep handles multi-day delays on serverless with no worker, which is exactly this pattern. Works hand-in-glove with your email provider.
  • Stripe webhook processing and post-purchase workflows → Inngest or Trigger.dev. Take the webhook, enqueue durable work, and stop doing slow work inside the webhook handler — critical for a reliable Stripe billing flow.
  • Video/image processing and large file jobs → Trigger.dev. Long-running compute without timeouts is its home turf; pairs with media storage like Cloudinary or S3.
  • Multi-step AI pipelines (RAG, agents, batch generation) → Trigger.dev for visibility on long chains, or Inngest for step-durable orchestration; both pair with the Vercel AI SDK.
  • High-volume, self-hosted queues (a busy app already on a persistent host) → BullMQ. Flat cost, deep control, and a natural home for the worker on Railway/Fly/Render.
  • 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:

  • Match the default to the host. A serverless-targeted starter should default to Inngest — no Redis, no worker, deploys to Vercel out of the box. A starter built around a persistent host (Railway, Fly) can reasonably default to BullMQ, with the Redis requirement documented clearly.
  • No-op without a key. If the job layer's credentials are missing, it should be cleanly gated or no-op — never crash on boot or fire events to *your* account. This is the single most important resale detail, the same discipline as analytics, email, or monitoring.
  • Keys and URLs in env vars only. No hardcoded API keys, no hardcoded Redis URL. Ship an example env file and a thin init module so the buyer plugs in their own account without touching app code.
  • Ship safe defaults. Idempotent example jobs, sensible concurrency and retry caps, and clear docs on exactly which service the template expects and how to configure it — so the buyer doesn't inherit a runaway bill or a rate-limit ban on day one.
  • 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.

  • "I'm on serverless/Vercel and want real background jobs with zero queue infra." → Inngest (start here for most Next.js SaaS)
  • "My jobs are long or heavy — video, big imports, long AI chains — and I want to watch every run." → Trigger.dev (managed long-running compute, open-source, great dashboard)
  • "I already run an always-on worker and want the cheapest option with the most control." → BullMQ (self-hosted Redis queue, flat cost at scale)
  • "I want managed ergonomics but on infrastructure I own." → Trigger.dev self-hosted
  • "A boilerplate I'll sell that must read as clean and production-ready." → Inngest wired in, no-op without a key, keys in env vars, BullMQ documented as the persistent-host alternative
  • 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.

    Frequently asked questions

    Why can't I just run background jobs directly in Next.js API routes?

    Because serverless functions are the wrong shape for background work, and understanding why is the whole reason these tools exist. A Next.js API route (or route handler) on a platform like Vercel is a short-lived serverless function: it spins up to handle a request, runs for a limited number of seconds, and then is frozen or killed. That's perfect for returning a response, but it breaks for anything that needs to outlive the request. If you kick off a slow task — sending a sequence of emails, processing an upload, calling a rate-limited third-party API, running a multi-step AI pipeline — inside the request, the user waits, and if it runs past the timeout the function is terminated mid-flight with no retry, no state, and no record of where it stopped. Even 'fire and forget' doesn't help: once you return the response, the platform can freeze the function and your 'background' work simply stops. You also can't easily run anything on a schedule (there's no always-on process to hold a cron), you can't guarantee a failed step is retried, and you can't limit concurrency so you don't hammer a downstream API. Background-job systems solve exactly this: they move the work out of the request/response cycle into something durable that can run longer, survive restarts, retry failed steps, run on a schedule, and control concurrency. Inngest and Trigger.dev do it with a managed orchestrator so you don't run infrastructure; BullMQ does it with a Redis queue and your own always-on worker. The unifying idea is the same — real background work needs durability, retries, and scheduling that a bare serverless request can't provide.

    What is the core architectural difference between Inngest, Trigger.dev, and BullMQ?

    It comes down to who runs your job code and who owns the queue infrastructure, and that single split explains almost every trade-off. With BullMQ, you own everything: it's a library backed by Redis, so you provision and run a Redis instance, you write a worker process that stays alive and pulls jobs off the queue, and you deploy and monitor that worker yourself. Your code runs in your worker, on your infrastructure, and BullMQ gives you the queue primitives (concurrency, priorities, retries, repeatable jobs, flows). It's the most control and the lowest per-job cost, but you're responsible for the moving parts. With Inngest, a cloud orchestrator owns the queue and the durability, but your functions still run in your own deployment: you register functions, and when an event fires or a schedule hits, Inngest calls your function over HTTP, step by step, persisting state between steps and retrying failed steps automatically. There's no Redis and no worker to manage — it's designed to sit on top of serverless. With Trigger.dev, the platform owns both the orchestration and the compute: your tasks run on Trigger.dev's dedicated long-running infrastructure (not inside your serverless functions), which is why jobs can run for minutes or hours without hitting serverless timeouts, and it checkpoints state so a job can resume after an interruption; it's open-source, so you can self-host that infrastructure if you want to own it. So the spectrum is: BullMQ = your queue, your compute, your ops; Inngest = managed orchestration, your compute (serverless-friendly), no queue infra; Trigger.dev = managed orchestration and managed long-running compute, open-source if you'd rather host it. Pick based on how much infrastructure you want to own and where your jobs need to run.

    Which one is best for a serverless SaaS on Vercel?

    For a serverless-first SaaS on Vercel or a similar platform, Inngest is usually the best default, and the reason is that it was built for exactly this constraint. On Vercel you don't have an always-on process, which means running BullMQ is awkward — BullMQ needs a persistent worker to pull jobs off Redis, and a pure serverless deployment has nowhere clean to run that worker, so you'd end up hosting a separate always-on service just for the queue. Inngest sidesteps that entirely: it needs no Redis and no worker, because its cloud orchestrator handles the queue and simply invokes your functions over HTTP when there's work. You define functions with retryable steps right inside your Next.js app, deploy as normal, and get durable execution, scheduled (cron) functions, event-driven fan-out, concurrency limits, and automatic step-level retries — all without provisioning or babysitting any infrastructure. That's the least-ops path to real background jobs on serverless, which is why it's the natural first choice for most Vercel-hosted SaaS boilerplates. Trigger.dev is also a strong serverless-friendly option and becomes the better pick when your jobs are genuinely long-running or compute-heavy (video, large imports, long AI chains) or when you want its deep run-visibility dashboard, because it runs those jobs on its own long-running compute instead of trying to fit them into a function invocation. BullMQ only makes sense on Vercel if you're already committed to running a separate always-on worker elsewhere (say, a small service on Railway or Fly.io) and want Redis-level control and cost. The short version: serverless SaaS → start with Inngest; go to Trigger.dev for long/heavy jobs and run visibility; use BullMQ when you already run a persistent worker.

    When does BullMQ make more sense than the managed options?

    BullMQ makes the most sense when you already run persistent infrastructure and want maximum control at the lowest cost — and that's a very common situation once an app grows past pure serverless. The precondition is an always-on process: if your app already runs on a platform where a long-lived worker is natural — Railway, Fly.io, Render, a VPS, or a container platform like Kubernetes — then adding a BullMQ worker alongside your app is straightforward, and you're not paying for anything you don't already have. In that setup BullMQ shines: it's a mature, widely used library, it gives you fine-grained control over concurrency, rate limiting, job priorities, delayed and repeatable (cron) jobs, and flow graphs where a parent job fans out to children, and critically it has no per-run pricing. Your cost is just your Redis instance plus the compute your worker already uses, which is why at high volume BullMQ is typically far cheaper than a per-run managed platform — a busy app can process very large numbers of jobs for the price of Redis hosting rather than a bill that scales linearly with every execution. The trade-off is ownership: you manage the Redis connection and its reliability, you write and deploy the worker processes, you handle scaling those workers, and you build or wire up your own monitoring and dashboards, since BullMQ is a library, not a hosted product with a polished UI (though tools like Bull Board help). So the honest rule is: if you have (or want) an always-on worker and value control and cost over convenience, BullMQ is excellent; if you're serverless or you'd rather not run and monitor queue infrastructure at all, a managed option earns its price by removing that operational surface.

    How do retries, scheduling, and durability compare across the three?

    All three give you retries, scheduling, and durability, but they express them differently, and the differences matter in practice. On retries, Inngest's signature feature is step-level retries: you break a job into named steps, and each step is retried independently and idempotently, so if step three fails, steps one and two aren't re-run — the orchestrator remembers their results and resumes from the failure. Trigger.dev similarly offers durable, resumable tasks with checkpointing, so an interrupted long-running job can pick up where it left off rather than restarting from zero. BullMQ retries at the job level with configurable attempts and backoff strategies; if you want step-level resumability you structure it yourself (for example, by splitting work into multiple queued jobs or a flow), which is more manual but fully in your control. On scheduling, all three support cron-style recurring work: Inngest has scheduled functions, Trigger.dev has scheduled tasks, and BullMQ has repeatable jobs — the difference is that Inngest and Trigger.dev hold the schedule in their managed orchestrator (nothing of yours needs to stay awake), while BullMQ's repeatable jobs rely on your always-on worker and Redis being up. On durability, the managed platforms persist job state for you: Inngest stores step state so functions survive restarts and redeploys, and Trigger.dev checkpoints so long jobs resume after interruption. BullMQ's durability comes from Redis — jobs and their state live in Redis, so durability is as strong as your Redis persistence and reliability configuration, which is powerful but is now your responsibility to get right. The theme repeats: managed tools give you durability and resumable steps out of the box as part of the price; BullMQ gives you the primitives to build equivalent guarantees while owning the infrastructure that backs them.

    What are the biggest cost and reliability traps with background jobs?

    Two categories cause most of the pain: per-run costs that scale with volume, and self-hosted infrastructure you didn't fully account for. On cost, the managed platforms (Inngest and Trigger.dev cloud) are priced around usage — runs, steps, and for long jobs, compute time — which is great at low volume (both have usable free tiers) but grows roughly linearly as your job count climbs. A chatty design that triggers far more runs than necessary — firing a job per tiny event, retrying aggressively on expected failures, or fanning out into thousands of trivial steps — can turn a small feature into a surprisingly large bill. The fixes are deliberate: batch where you can, don't enqueue work that doesn't need to be a job, cap retries on errors that won't succeed on retry, and watch your run volume the way you'd watch any usage-priced service. BullMQ flips the trap: there's no per-run fee, so it's cheap at scale, but you inherit infrastructure risk. The classic failure modes are Redis running out of memory or losing data because persistence wasn't configured, a worker process crashing or being under-provisioned so the queue backs up, no dead-letter handling so failed jobs vanish silently, and no visibility because you never set up a dashboard. On reliability generally, the traps that bite everyone regardless of tool are non-idempotent jobs (a retry runs the side effect twice — charging a card or sending an email again), missing concurrency limits (a burst of jobs hammers a rate-limited API and gets you throttled or banned), and unbounded retries that turn a transient error into a storm. The discipline that pays off across all three: make jobs idempotent so retries are safe, set sensible concurrency and rate limits, cap and back off retries, handle permanent failures explicitly (dead-letter or alert), and — for self-hosted BullMQ — treat Redis persistence, worker scaling, and monitoring as first-class, not afterthoughts.

    Which background-job system should a SaaS boilerplate or template you sell ship with?

    For a boilerplate, SaaS starter, or app you intend to hand off or sell, the guidance mirrors every other infrastructure decision: default to what fits the template's host, is easy for the buyer to re-point at their own account, is safe by default, and won't hand them a surprise bill or a hidden dependency — and deviate only for a stated reason. Because most Next.js SaaS starters target serverless deployment, Inngest is usually the strongest default: it needs no Redis and no separate worker process, so the buyer can clone the repo and deploy to Vercel without standing up extra infrastructure, and it wires in cleanly keyed off the buyer's own Inngest credentials via environment variables with an example env file. Critically, it should no-op or be clearly gated without keys, so an unconfigured clone doesn't send events to your account or crash on boot — the same disabled-without-a-key discipline you'd apply to analytics, email, or error tracking. If your template is built around a persistent host (a Railway or Fly.io starter that already runs an always-on service), BullMQ can be the better fit, since the worker has a natural home and you avoid a third-party dependency entirely — just document the Redis requirement clearly and provide the connection via env vars, never hardcoded. Trigger.dev is a good default when the template's whole point is long-running or AI-heavy workflows where run visibility is a selling point, and its open-source nature means the buyer isn't locked into a vendor. Whatever you choose, the resale rules are the same as for any code you sell: no hardcoded API keys or Redis URLs, an example env file, the job layer disabled or no-op without configuration, sensible defaults for concurrency and retries so the buyer doesn't inherit a runaway bill or a rate-limit ban, idempotent example jobs, and clear docs on which service the template expects and how to plug in their own account. A background-job layer that's cleanly abstracted, key-free, safe by default, and documented does as much to make a codebase read as production-ready as any feature built on top of it.

    Related guides

    Browse Quality-Scored Code

    Every listing on CodeCudos is analyzed for code quality, security, and documentation. Find production-ready components, templates, and apps — or sell your own code and keep 90%.

    Browse Marketplace →