The moment you write setTimeout(() => doExpensiveWork(), 0) in a Next.js API route, you're making a bet. The request completes, the connection closes, and the background work either finishes or disappears with no record of which happened.
Every B2B SaaS eventually needs background jobs: invoice generation, webhook retries, nightly aggregations, email sequences, PDF exports, AI enrichments that take 30 seconds and cannot live inside a user-facing request. The question isn't whether you need them. It's which layer should own them — and picking the wrong one is expensive in both money and debug time.
Three credible paths exist today: Inngest, Trigger.dev, and rolling your own with Vercel Cron plus a database queue. I've evaluated all three for production serverless SaaS. Here is where each one actually breaks.
Why Vercel Cron Alone Isn't a Background Job System

Vercel Cron won't retry a failed invocation. Period. That alone rules it out as a primary background job strategy.
Vercel's Pro plan caps function execution at 60 seconds, Hobby at 10. A 1:00 AM cron fires "between 1:00 and 1:59 AM" per Vercel's own documentation — no precision guarantee. Hobby accounts get 5 cron jobs with hourly minimum cadence. No per-job failure alerting. No replay. UTC only. For daily digest emails on small datasets: fine. For anything needing retry logic, sub-minute scheduling, or failure visibility: outgrown before you've started.
Does Inngest Fit Your SaaS Job Pattern?

Inngest fits event-triggered, multi-step workflows — its step-level checkpointing means only the failing step needs to be idempotent, not the entire function.
You've probably felt this pain. A five-step background job fails on step 4. Do you roll back steps 1–3? Re-run from the top idempotently? Write compensating transactions? Inngest solves this at the platform level: each step.run() call is checkpointed on success. If the function fails midway, Inngest replays completed steps from their cached results and only re-executes the failing step. The idempotency burden shrinks to a single step. A step inserting a user needs to be an upsert; a step sending an email needs a sent-email guard. Two guards instead of ten.
The local dev experience is a genuine strength. The dev server shows every event received, every function triggered, step timelines, and per-step outputs without a cloud connection. Debugging a complex multi-step flow in local dev takes minutes instead of the deploy-log-reproduce cycle.
Where it bites: billing. According to Inngest's 2026 pricing page, an execution is one function run plus each step inside it. A function with 5 step.run() calls uses 6 executions. At 10,000 jobs/month with an average of 4 steps each, you're at 50,000 executions — exactly the free tier ceiling. At 100,000 jobs, you're paying $75/month on Pro. At 1M jobs with complex workflows, the math compounds faster than the $75 headline suggests.
| Tier | Price | Executions/month | Concurrency | |---|---|---|---| | Hobby | $0 | 50,000 (pauses at limit) | 5 slots | | Pro | $75/mo | 1,000,000 + $50/additional 1M | 100+ | | Enterprise | Custom | Custom | Custom |
The other constraint is harder to negotiate: Inngest is cloud-first. No production self-hosted path. If your compliance posture requires infrastructure control — HIPAA, SOC 2 with specific hosting requirements, EU data residency — that gap shows up on security questionnaires.
When Does Trigger.dev Win?

Trigger.dev v3 wins when tasks exceed 60 seconds, you need system packages like ffmpeg or Puppeteer, or compliance requires self-hosted job infrastructure.
The architectural shift in Trigger.dev v3 matters. Version 2 ran code inside serverless functions — same timeout ceiling as Vercel, forcing developers to split jobs into timeout-safe chunks with code that was "hard to write and confusing" (their characterization). Version 3 moved to dedicated managed infrastructure. Tasks run indefinitely. No splitting video transcoding into chunks. No timeout gymnastics for a Puppeteer crawl across a hundred pages.
Let me back up on the retry tradeoff. Trigger.dev v3's task model is simpler for non-orchestration work: write a task, call trigger(), it runs. No step decomposition required. The cost: the entire task function re-executes on retry. Every database write needs to be an upsert; every external API call needs idempotency key handling. A real design requirement, not a footnote.
Version isolation is the feature that saves 3am incidents. Each Trigger.dev deploy is separate — in-flight runs complete on the version they started. Push a breaking schema change mid-flight and running jobs don't inherit it. BullMQ and custom queues require you to engineer this yourself, or accept the gap.
Trigger.dev v3 Pro runs $50/month base, 100+ concurrent runs, 30-day log retention, Apache 2.0 license, Docker-based self-hosted path. If compliance requires running job infrastructure inside your own cloud account, this path exists — unlike Inngest.
The Custom Path: BullMQ on Redis
BullMQ is the flat-cost option at volume. MIT-licensed, Redis-backed, zero per-run billing. Managed Redis at $5–$50/month depending on provider. At 1M jobs/month, your cost is the Redis instance — nothing else.
The ops work is real:
- A persistent Redis instance — Upstash, Redis Cloud, or ElastiCache, not ephemeral.
- Dedicated worker processes with concurrency tuning and graceful shutdown logic.
- Stalled-job recovery configuration (Redis locks break on unexpected worker restarts).
- Observability via Bull Board or BullMQ Pro's dashboard — neither ships by default, and Bull Board is functional at best.
- Dead-letter queue design and maintenance you build and own.
On pure serverless deployments like Vercel, BullMQ needs a sidecar: a persistent worker on Railway, Render, or Fly. Another service to deploy, monitor, and page for at odd hours.
The cost crossover is roughly 500,000 jobs/month. Below that threshold, Inngest Pro at $75/month is almost certainly cheaper than the engineering hours Redis ops consume. Above it, BullMQ's flat rate becomes genuinely compelling — assuming the bandwidth to operate it exists on your team.
Which SaaS Background Job Platform Has the Best Observability?
Inngest leads on built-in observability; Trigger.dev v3 Pro is solid; BullMQ requires you to build or buy your own dashboard.
Inngest's UI shows per-function run history, step timelines, per-step inputs and outputs, and replay controls. When your on-call engineer gets a PagerDuty alert for a failed job, Inngest is the platform where they'll identify and replay the failure fastest. The local dev server mirrors this fully — complete visibility without a cloud connection.
Trigger.dev v3 Pro includes 30-day log retention and a run inspector. Adequate for most production SaaS needs on that tier.
BullMQ ships with nothing. Bull Board is the standard open-source UI add-on: functional, not polished. BullMQ Pro's commercial dashboard adds run history and more granularity at additional cost. Neither reaches what Inngest ships out of the box. For teams using Supabase Edge Functions as a cron trigger layer, you get function logs and that's the extent of visibility.
How to Pick Without Overthinking It
Most early SaaS products don't need the permanent answer at launch. They need the lowest-overhead option covering current volume with a credible upgrade path when volume or complexity demands it.
Use Inngest when jobs are event-triggered and multi-step, you're on Vercel or another serverless platform, and compliance doesn't require self-hosted infrastructure. Watch the execution billing math as job volume grows.
Use Trigger.dev v3 when tasks run longer than 60 seconds, you're building AI pipelines that can't be decomposed into timed steps, or your security questionnaire requires infrastructure control.
Use BullMQ when you're above 500,000 jobs/month, have Redis ops bandwidth, and need zero per-run pricing.
On Callidus, a booking SaaS for UK aesthetic clinics, the job volume at launch was too small to justify any dedicated platform. Short webhook-triggered state transitions ran synchronously after returning 200 in the route handler. The one genuinely async job — a monthly billing summary email to clinic owners — ran via Vercel Cron plus a Postgres queue pattern. The graduation to Inngest was always planned. It happened later than I expected: month seven, when the jobs table had accumulated enough failed rows that the ad-hoc SQL queries I was writing to diagnose failures became their own embarrassment.
The best SaaS MVP stack defers infrastructure costs until they're a real constraint. Background jobs are the classic case. Ship the simple thing first. You'll know when to graduate because debugging production failures through raw Postgres queries at midnight will tell you exactly when the simple version is done.
