Why Inngest Over BullMQ and Trigger.dev
January 25, 2025 · Magnus Rødseth
Why Inngest Over BullMQ and Trigger.dev
Background jobs are one of those infrastructure decisions that don't feel urgent until they become critical. You need to send emails, process payments, run AI pipelines, sync data — and suddenly you're debugging Redis connection issues at 2 AM.
When choosing a background job solution for Eden Stack, I evaluated the major options: BullMQ (the Redis workhorse), Trigger.dev (the modern challenger), and Inngest (the durable execution platform). Here's why Inngest won.
The State of Background Jobs in 2025
Let's set the stage. Most developers reach for one of three approaches:
- Roll your own — setTimeout, cron jobs, or a basic queue
- Redis-based queues — BullMQ, Bull, Bee-Queue
- Managed platforms — Inngest, Trigger.dev, Temporal
The first option is fine until your server restarts mid-job. The second works but requires infrastructure management. The third abstracts away the pain — but each platform has a different philosophy.
BullMQ: The Reliable Workhorse
BullMQ deserves respect. With 7,400+ GitHub stars and 1.5 million weekly downloads, it's battle-tested. If you know Redis, you know BullMQ.
// BullMQ: Traditional queue pattern
import { Queue, Worker } from 'bullmq';
const queue = new Queue('email');
// Producer
await queue.add('send-welcome', { userId: '123' });
// Worker (separate process)
const worker = new Worker('email', async (job) => {
await sendEmail(job.data.userId);
});What BullMQ does well:
- Rock-solid reliability on proven Redis infrastructure
- Fine-grained control over job priorities, delays, and retries
- Massive ecosystem and community knowledge
- Self-hostable with full control
But here's the catch: You're managing infrastructure. Redis needs to be provisioned, monitored, and scaled. Workers need to run somewhere. Connection pooling needs configuration. When your job fails at step 3 of 5, you're writing custom retry logic.
For Eden Stack's goals — helping developers ship production apps fast — asking them to set up Redis infrastructure felt like adding friction, not removing it.
Trigger.dev: The Modern Approach
Trigger.dev has gained serious momentum (13,300+ GitHub stars). Their pitch is compelling: background jobs that feel like writing normal TypeScript functions.
// Trigger.dev: Task-based approach
import { task } from '@trigger.dev/sdk/v3';
export const sendWelcomeEmail = task({
id: 'send-welcome-email',
run: async (payload: { userId: string }) => {
await sendEmail(payload.userId);
return { sent: true };
},
});What Trigger.dev does well:
- Clean developer experience
- Self-hostable with a cloud option
- Good TypeScript support
- Active development and community
Trigger.dev is genuinely good. If Eden Stack were optimized for a different use case, it might have won.
Inngest: Durable Execution for AI-Native Apps
Here's where Inngest diverges — and why it won for Eden Stack.
Inngest isn't just a job queue. It's a durable execution engine. The difference matters when you're building workflows that must survive failures, coordinate across services, or handle long-running AI operations.
// Inngest: Durable execution with steps
import { inngest } from './client';
export const onboardUser = inngest.createFunction(
{ id: 'onboard-user' },
{ event: 'user/created' },
async ({ event, step }) => {
// Each step is automatically retried and checkpointed
const user = await step.run('create-profile', async () => {
return await createUserProfile(event.data.userId);
});
await step.run('send-welcome-email', async () => {
await sendWelcomeEmail(user.email);
});
// Wait for external event (e.g., email verification)
const verified = await step.waitForEvent('user/email-verified', {
timeout: '24h',
match: 'data.userId',
});
if (verified) {
await step.run('activate-trial', async () => {
await activateTrial(user.id);
});
}
}
);Notice what's different: steps are individually checkpointed. If send-welcome-email fails, Inngest retries that step — not the entire function. The state from create-profile is preserved. No Redis. No worker processes. No custom retry logic.
Why Durable Execution Matters
In traditional queues, a 5-step workflow that fails on step 4 either:
- Retries from the beginning (wasteful, potentially dangerous)
- Requires you to manually track progress (complex)
- Leaves partial state that needs cleanup (messy)
Inngest handles this automatically. Each step.run() is atomic, retriable, and checkpointed. This is especially critical for:
- AI workflows — LLM calls are expensive and slow. Re-running an entire AI pipeline because step 3 timed out is wasteful.
- Payment processing — You cannot afford to charge a customer twice because a webhook handler crashed.
- Multi-service orchestration — When coordinating across APIs, partial failures are the norm.
AI-Native by Design
Eden Stack is built for AI-native applications. Inngest's AgentKit takes this seriously:
// Inngest AgentKit: Durable AI agents
import { Agent, agenticWorkflow } from '@inngest/agent-kit';
const researchAgent = new Agent({
name: 'Researcher',
tools: [webSearch, documentFetch],
model: anthropic('claude-sonnet-4-20250514'),
});
export const researchWorkflow = agenticWorkflow({
agents: [researchAgent],
maxIterations: 10,
});AI agent loops are inherently long-running and failure-prone. Network timeouts, rate limits, model errors — all common. Inngest's durability means your agent can run for hours, surviving failures, with full observability into each step.
Self-Hosting + Generous Cloud
One of your concerns might be vendor lock-in. Inngest addresses this:
- Self-hostable: Run the entire Inngest server on your own infrastructure
- Cloud with generous free tier: 25,000 function runs/month free, then predictable pricing
- No infrastructure management: Unlike BullMQ, you don't need to provision Redis
For Eden Stack's use case — a template that should work for startups and scale with them — this flexibility is perfect. Start with the free cloud tier, self-host when you need to.
When to Choose What
Choose BullMQ when:
- You already have Redis infrastructure and expertise
- You need maximum control over queue behavior
- You're building simple job queues without complex orchestration
- Your team prefers managing their own infrastructure
Choose Trigger.dev when:
- You want a modern DX without durability requirements
- You're building relatively simple background tasks
- You prefer their specific approach to task definition
Choose Inngest when:
- You're building AI-powered applications with long-running workflows
- Failure recovery and step-level retries are critical
- You want durable execution without managing infrastructure
- You need event-driven coordination across services
Why Eden Stack Uses Inngest
Eden Stack is designed for developers building production AI-native applications. The choice was clear:
- Durable execution — AI workflows need step-level reliability
- Event-driven architecture — Inngest's event system fits naturally with webhooks and service coordination
- Zero infrastructure — No Redis to manage, no workers to deploy
- Self-hosting option — No vendor lock-in when you need to scale
Combined with the rest of the stack — Elysia for the API, TanStack Start for the frontend — Inngest provides the missing piece for reliable background processing.
The result is a template where background jobs "just work." Send an event, define your function, let Inngest handle the rest. That's the developer experience Eden Stack aims to provide.
This post reflects my opinions after building production applications with multiple background job solutions. Your requirements may differ — BullMQ and Trigger.dev are both excellent tools for their intended use cases.