
Inngest is the best background-job platform for TypeScript and Python teams that need durable workflows, scheduled jobs, and fan-out parallelism without running their own queue infrastructure. It wins on developer experience (the local Dev Server is genuinely great), step-level retries, and a generous free tier (50,000 events/month at $0). It loses for heavy CPU work, GPU jobs, and anyone who needs sub-100ms job pickup latency.
If you ship product code in Next.js, Remix, or FastAPI and you keep reaching for "I need a queue but I don't want to run Redis + BullMQ + a worker dyno," Inngest is probably what you actually want.
Inngest is a durable execution platform. You write functions in your application code, decorate them with an Inngest SDK call, and Inngest handles the queue, the retries, the scheduling, the fan-out, and the observability.
The model is event-driven. You send an event (inngest.send({ name: "user.signed_up", data: { userId } })), and any function subscribed to that event runs. Functions can also run on a cron schedule, or be triggered manually from the dashboard.
The thing that makes Inngest different from a plain queue: every function is broken into steps, and each step is independently retried, cached, and durably checkpointed. If your function does five things and the fourth one fails, Inngest re-runs only the fourth step on retry, not the whole function. The previous three steps return their cached values instantly.
This is the same execution model as Temporal or AWS Step Functions, except you write it inline in your normal application code instead of in a separate workflow DSL.
Most background-job libraries (BullMQ, Sidekiq, Celery, Resque) give you one retry boundary: the whole job. If anything fails, the whole job re-runs. You handle idempotency yourself, usually by checking "did I already do this?" at the top of every side-effect.
Inngest's step.run flips that. Here's the shape:
export const onSignup = inngest.createFunction(
{ id: "on-signup" },
{ event: "user.signed_up" },
async ({ event, step }) => {
const user = await step.run("load-user", () =>
db.users.findById(event.data.userId)
);
await step.run("create-stripe-customer", () =>
stripe.customers.create({ email: user.email })
);
await step.run("send-welcome-email", () =>
resend.emails.send({ to: user.email, subject: "Welcome" })
);
await step.sleep("wait-3-days", "3d");
await step.run("send-day-3-followup", () =>
resend.emails.send({ to: user.email, subject: "How's it going?" })
);
}
);
If create-stripe-customer succeeds and send-welcome-email fails, Inngest re-runs only the email step. Stripe doesn't get a duplicate customer. The step.sleep("wait-3-days") is durable: Inngest puts the function to sleep and resumes it 3 days later without any worker process holding state.
This is the thing that makes Inngest feel like cheating once you've used it. Idempotency stops being a thing you constantly worry about and becomes a property of how you structure the function.
Inngest's cron support is one line:
inngest.createFunction(
{ id: "nightly-cleanup" },
{ cron: "0 3 * * *" },
async ({ step }) => { /* ... */ }
);
No node-cron, no Vercel Cron, no Render Cron Job. The schedule lives in your code, it deploys with your code, and the execution shows up in the same dashboard as your event-driven jobs.
If you've ever debugged "why did this cron not run last Tuesday" across three different systems (the host's crontab, a Vercel cron config, a third-party uptime checker), having all your scheduled work in one place with a real execution log is a meaningful upgrade. For startups that have outgrown Vercel Cron's 1-minute granularity and 2-minute timeout, Inngest is the obvious next step.
When a job needs to do 50 things in parallel (send 50 emails, regenerate 50 thumbnails, hit 50 APIs), Inngest gives you step.parallel:
await Promise.all(
users.map((user) =>
step.run(`notify-${user.id}`, () =>
sendNotification(user)
)
)
);
Each step.run becomes an independent, retryable unit. If 47 of 50 succeed and 3 fail, only those 3 retry. Inngest's concurrency controls let you cap how many parallel steps run at once (so you don't blow through your SendGrid rate limit) and set per-user fairness so one customer's 10,000-row import doesn't starve everyone else.
For background jobs that involve big data fan-out, this is significantly easier than writing the equivalent in BullMQ with parent/child jobs and tracking the completion state yourself. Similar fan-out patterns show up when teams build feature flag rollouts with LaunchDarkly or Statsig, where you want to fan out to thousands of cache invalidations per release.
Run npx inngest-cli@latest dev and you get a local web UI at localhost:8288 that shows every event, every function run, every step, every retry, with full payload inspection.
You can hand-fire events from the UI to test specific code paths. You can re-run a failed step with the same input to debug. You can see the exact order steps executed in, how long each took, and what payload each step returned.
This is the part of Inngest that people consistently underrate until they use it. Most background-job systems have terrible local DX (you run Redis + a worker + tail logs + hope). Inngest's Dev Server is closer to using the Stripe CLI than to using BullMQ Board: it's an actual debugging environment, not a dashboard.
For a typical Next.js app, you boot Inngest in dev with one extra terminal tab and you can iterate on workflow logic at the same speed as you iterate on UI code. That tightness matters for shipping speed.
Inngest's pricing is event-based. The free tier gives you 50,000 events per month, with each function execution counting as one event regardless of how many steps it has.
| Plan | Monthly cost | Events / month | Step executions | Concurrency | History |
|---|---|---|---|---|---|
| Free | $0 | 50,000 | 250,000 | 25 | 7 days |
| Basic | $20 | 250,000 | 2,500,000 | 50 | 30 days |
| Pro | $50+ | 1M+ | 10M+ | 100+ | 90 days |
| Enterprise | Custom | Unlimited | Unlimited | Custom | 1 year |
For a startup running webhooks, transactional emails, scheduled reports, and the usual onboarding sequences, 50,000 events typically covers the first few thousand active users. Most teams hit the $20 Basic tier before they hit any real product-market-fit signal.
Compared to running your own BullMQ + Redis on Railway ($10-30/month for Redis + $7-20/month for a worker dyno), Inngest's pricing is comparable at the low end and meaningfully cheaper once you factor in the engineering time you'd otherwise spend on observability, retry logic, and dashboards.
The Pro tier ($50+) is where companies with serious volume land. Above 1M events, you're talking to sales, and your bill scales with usage. This is also the point where you should be honest with yourself about whether you're using Inngest for what it's good at (durable workflows) or trying to make it do a job that belongs on Kafka or SQS.
| Tool | Best for | Where it loses |
|---|---|---|
| Inngest | Durable workflows, scheduled jobs, fan-out, great DX | Heavy CPU, GPU jobs, sub-100ms latency |
| Trigger.dev | Long-running jobs (15+ min), code-as-config workflows | Newer ecosystem, fewer integrations |
| Defer | Simple Node.js background jobs, Vercel-style DX | Smaller team, less battle-tested |
| Hatchet | Self-hosted durable execution, Postgres-backed | More ops burden, less polished UI |
| Temporal | Mission-critical workflows at scale, multi-language | Steep learning curve, heavy infra |
| BullMQ + Redis | Simple FIFO queues, full control | You build retries, observability, dashboards |
| AWS SQS + Lambda | Cheap at scale, AWS-native | DIY orchestration, painful local DX |
A few specifics worth calling out:
Inngest vs Trigger.dev. Trigger.dev is the closest competitor and a great product in its own right. Trigger.dev wins for very long-running jobs (their architecture supports jobs that run for hours without timing out), and for teams that prefer their resumability model. Inngest wins on event-driven semantics, fan-out ergonomics, and (in our experience) a more responsive Dev Server. They're closer to each other than either is to anything else; pick based on which SDK feels better in your editor.
Inngest vs Hatchet. Hatchet is the right pick if you need to self-host on your own Postgres (compliance, data residency, cost predictability at huge scale). Inngest is hosted-only on the standard plans, so if you have a hard "no SaaS for job execution" rule, Hatchet wins by default.
Inngest vs Temporal. Temporal is the right pick if you're building a workflow system at scale (think Stripe's payment retry logic or DoorDash's order state machines) and you need polyglot SDKs across Go, Java, Python, and TypeScript. Inngest is the right pick if you're a TypeScript team that wants 90% of Temporal's durability with 10% of the operational complexity.
Be honest about the weaknesses, because tool reviews that pretend their pick is perfect waste everyone's time.
Heavy CPU work. Inngest's pricing model is event-based, not compute-based, which is great until you try to run a 30-minute video encode in a function. You'll hit step timeouts (15 minutes max on most plans) and burn through your concurrency quota. Use a real worker (Trigger.dev for the longer steps, or a dedicated container on Fly or Railway) and let Inngest just trigger it.
GPU jobs. Inngest doesn't run on GPU instances. For ML inference, transcription, image generation, or any GPU-bound workload, use Replicate, Modal, or Beam and let Inngest orchestrate the trigger and the post-processing.
Sub-100ms job pickup. Inngest's event-to-execution latency is typically a few hundred milliseconds. For "real-time" jobs (game backend events, trading systems, high-frequency notifications), you want an in-memory queue, not a hosted durable executor. Realtime messaging tools like Ably or Pusher are the right primitive for that layer.
Vendor lock-in. Your job code is portable (it's just functions), but the orchestration, retries, and observability are Inngest's. If you ever want to leave, you're rewriting the boundary between your app and the queue. This is true of every hosted workflow tool, but it's worth saying out loud.
The clear yes:
step.run removes 80% of the "did I already do this?" logic from your codebase.The clear no:
If you're evaluating Inngest, the honest path is: install the SDK in a side project, run the Dev Server, port one of your existing background jobs to it, and feel how the step boundaries change the way you write the code. Most teams know within 30 minutes of using the Dev Server whether Inngest is for them.
If you're shipping a new product right now and you just want background jobs to work, start on Inngest's free tier. You'll know by month two whether you're hitting limits, and the migration to a different system (if it ever happens) is cheap because your job code is just normal functions.
If you don't have the time or the in-house experience to evaluate this category yourself, every engineer on Cadence is AI-native by default, vetted on Cursor / Claude / Copilot fluency before they unlock bookings, and the Mid tier ($1,000/week) is the right level for porting an existing job system to Inngest and writing the first 5-10 workflows. Senior ($1,500/week) is the right tier if you also need someone to architect the event taxonomy and decide where Inngest stops and your real workers begin.
For teams comparing the whole background-job category before committing, our roundup of the best monitoring tools for startups in 2026 covers the observability piece that pairs naturally with Inngest's execution logs.
If you're 60 minutes deep into picking a background-job stack and you want a second opinion before you commit, audit your tooling with Ship-or-Skip for an honest take on whether Inngest is the right call for your stack or whether you should hold off.
Yes, for any TypeScript or Python team running more than 5 background jobs that need retries, scheduling, or fan-out. The free tier (50,000 events/month) covers most startups through their first thousand active users, and the $20 Basic tier is cheaper than the engineering hours you'd spend building equivalent observability on BullMQ.
They're the closest competitors and both are good. Pick Inngest if you want event-driven semantics, fan-out ergonomics, and the better Dev Server. Pick Trigger.dev if your jobs are very long-running (hours, not minutes) or if you prefer their resumability model. Try both for a day; you'll know.
Yes. The free tier includes 50,000 events per month, 250,000 step executions, 25 concurrent runs, and 7-day history. This is enough for most pre-product-market-fit startups and many side projects. You pay $20/month when you cross into Basic, which is a soft transition with no surprises.
Yes, this is its sweet spot. Inngest functions run on your existing Vercel, Netlify, AWS Lambda, or Cloudflare Workers deployment. Inngest handles the queue and the retries; your serverless platform handles the compute. You don't need a separate worker process.
Your jobs keep running. Inngest doesn't drop events when you hit the limit; they bill you for overage at the standard rate or prompt you to upgrade. There's no "your background jobs stopped at 3am on Sunday because you used 50,001 events" failure mode, which is the right call for an infrastructure tool.
Yes. Inngest runs production workloads for thousands of companies, including teams handling millions of events per month. The SLA on paid plans is 99.99%, and the Pro tier includes 90-day execution history for compliance and debugging. The honest caveat: it's still a younger company than AWS, so if your business is regulated to the point where you need a 10-year-old vendor, it's not the right pick.
Web developer at withRemote. Writes on accessibility, responsive design, and the boring-but-correct front-end fundamentals.