Skip to main content

Inngest vs Temporal vs Trigger.dev 2026

·APIScout Team
inngesttemporaltrigger.devbackground jobsworkflowapi2026

The Background Jobs Problem

Modern applications need to run code that doesn't fit in a request/response cycle: send emails after signup, process uploaded files, run nightly data syncs, retry failed webhook deliveries, orchestrate multi-step AI pipelines. Serverless architectures made this harder — no long-running processes, functions time out, infrastructure is ephemeral.

Durable execution platforms solve this. They let you write code that looks sequential but runs reliably across failures, restarts, and retries — without managing queues, workers, or retry state yourself.

In 2026, three platforms dominate: Inngest, Temporal, and Trigger.dev. They share the same core promise but take very different approaches in complexity, target audience, and operational model.

TL;DR

  • Inngest: Best for TypeScript/serverless teams wanting minimal infrastructure. Excellent Next.js/Vercel integration. Event-driven architecture makes it natural for product workflows.
  • Temporal: Best for complex, long-running business workflows with strict reliability requirements. More complex to operate, more powerful state machines. The enterprise choice.
  • Trigger.dev: Best for TypeScript developers wanting background jobs that feel like writing normal code. v3 architecture is the most modern developer experience of the three.

Key Takeaways

InngestTemporalTrigger.dev
Primary modelEvent-driven stepsWorkflow/activityBackground tasks
Best fitServerless/Next.jsEnterprise workflowsTypeScript apps
Self-host✅ (primary)
Cloud✅ (Temporal Cloud)
Free tierLimited trial
SDK languagesTS, Python, GoTS, Go, Python, Java, .NETTypeScript (Python via extension)
ComplexityLowHighLow

Inngest

What It Is

Inngest is a durable execution platform built for serverless and edge environments. Your functions are deployed as HTTP endpoints (serverless functions, API routes) — Inngest calls them, tracks state, handles retries, and manages the execution flow. You don't run workers.

This is fundamentally different from Temporal: there's no long-running process to manage. Inngest works with Vercel, Netlify, AWS Lambda, and any HTTP-capable runtime.

Core Concepts

Functions: Your business logic, decorated with Inngest metadata:

import { inngest } from "./client";

export const processUserSignup = inngest.createFunction(
  {
    id: "process-user-signup",
    retries: 3,
  },
  { event: "user/signed.up" },
  async ({ event, step }) => {
    // Step 1: Send welcome email
    await step.run("send-welcome-email", async () => {
      await sendEmail(event.data.email, "Welcome!");
    });

    // Step 2: Wait 3 days, then send onboarding email
    await step.sleep("wait-for-onboarding", "3 days");

    await step.run("send-onboarding-email", async () => {
      await sendEmail(event.data.email, "How's it going?");
    });

    // Step 3: Create trial reminder after 14 days
    await step.sleep("wait-for-trial-reminder", "11 days");

    await step.run("send-trial-ending-email", async () => {
      const user = await db.users.findById(event.data.userId);
      if (!user.isPaid) {
        await sendEmail(event.data.email, "Your trial is ending!");
      }
    });
  }
);

Each step.run() is a checkpoint — if the function fails mid-way, Inngest resumes from the last completed step, not from the beginning. The step.sleep() doesn't block a worker; Inngest schedules the next wake-up and invokes your function again.

Events: Triggers that start or fan-out to functions:

// Trigger the function from anywhere in your app
await inngest.send({
  name: "user/signed.up",
  data: { userId: "usr_123", email: "user@example.com" },
});

Fan-out: One event triggers multiple functions in parallel:

// Multiple functions can listen to the same event
export const syncToHubspot = inngest.createFunction(
  { id: "sync-hubspot" },
  { event: "user/signed.up" },
  async ({ event, step }) => { /* ... */ }
);

export const createStripeCustomer = inngest.createFunction(
  { id: "create-stripe-customer" },
  { event: "user/signed.up" },
  async ({ event, step }) => { /* ... */ }
);

Both fire simultaneously when user/signed.up is sent.

Cron Jobs

export const dailyDigest = inngest.createFunction(
  { id: "daily-digest" },
  { cron: "0 9 * * *" }, // 9am UTC daily
  async ({ step }) => {
    const users = await step.run("fetch-active-users", fetchActiveUsers);
    for (const batch of chunk(users, 50)) {
      await step.run(`send-digest-${batch[0].id}`, () => sendDigestBatch(batch));
    }
  }
);

Next.js Integration

// app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest } from "@/inngest/client";
import { processUserSignup, dailyDigest } from "@/inngest/functions";

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [processUserSignup, dailyDigest],
});

That's the full integration. Inngest discovers your functions via this endpoint, registers them, and starts routing events.

Pricing

  • Free: 50K–100K function runs/month; 50 concurrent runs; event history
  • Pro (~$350/month): 200 concurrent steps; granular metrics; increased throughput; 14-day trace retention; priority support; $50 per 1M additional executions
  • Enterprise: Custom; SAML/RBAC; audit trails; dedicated Slack; 90-day trace retention; SLAs

Inngest charges per function run (each step.run() call counts). The pricing is predictable for typical workloads.

Self-Hosting

Inngest's server is source-available under SSPL (Server Side Public License) with a delayed open-source arrangement — code is relicensed to Apache 2.0 after a 3-year delay. Self-host with Docker:

docker run -p 8288:8288 inngest/inngest:latest dev

The cloud product adds managed infrastructure, team features, and SLAs.


Temporal

What It Is

Temporal is a workflow orchestration engine built for mission-critical, long-running processes. It originated at Uber (as Cadence) and is designed for workflows that span hours, days, or years — financial transactions, order fulfillment, data pipelines, compliance processes.

Temporal's model is fundamentally different from Inngest: you write workflows (long-running code) and activities (individual steps), and Temporal's server ensures they execute exactly-once, with full state persistence and history.

Core Concepts

Workflows: Deterministic functions that orchestrate activities:

import { proxyActivities, sleep, defineWorkflow } from "@temporalio/workflow";
import type * as activities from "./activities";

const { sendEmail, createSubscription, chargeCard } = proxyActivities<typeof activities>({
  startToCloseTimeout: "30 seconds",
  retry: {
    maximumAttempts: 3,
    initialInterval: "1 second",
    backoffCoefficient: 2,
  },
});

export async function userOnboardingWorkflow(userId: string): Promise<void> {
  // Send welcome email
  await sendEmail(userId, "welcome");

  // Create subscription (idempotent)
  await createSubscription(userId);

  // Wait 14 days for trial period
  await sleep("14 days");

  // Charge for first subscription
  const result = await chargeCard(userId, "monthly");

  if (result.failed) {
    await sendEmail(userId, "payment-failed");
  }
}

Activities: Individual units of work (I/O, external API calls):

// activities.ts
export async function sendEmail(userId: string, template: string): Promise<void> {
  const user = await db.users.findById(userId);
  await emailProvider.send(user.email, template);
}

export async function chargeCard(userId: string, plan: string): Promise<{ failed: boolean }> {
  try {
    await stripe.charge(userId, plan);
    return { failed: false };
  } catch {
    return { failed: true };
  }
}

Workers: Long-running processes that execute workflows and activities:

// worker.ts
import { Worker } from "@temporalio/worker";
import * as activities from "./activities";

const worker = await Worker.create({
  workflowsPath: require.resolve("./workflows"),
  activities,
  taskQueue: "user-onboarding",
});

await worker.run(); // Runs indefinitely

This is Temporal's key operational requirement: you must run workers. No workers → no workflow execution. This makes Temporal less compatible with serverless environments (though workarounds exist).

Signals and Queries

Temporal's killer feature for complex workflows: external code can interact with a running workflow:

// In the workflow definition
import { setHandler, defineSignal, defineQuery } from "@temporalio/workflow";

const approveSignal = defineSignal<[string]>("approve");
const statusQuery = defineQuery<string>("status");

export async function approvalWorkflow(): Promise<void> {
  let approved = false;
  let approver = "";

  setHandler(approveSignal, (approvedBy) => {
    approved = true;
    approver = approvedBy;
  });

  setHandler(statusQuery, () => approved ? `Approved by ${approver}` : "Pending");

  // Wait indefinitely for approval (can be years)
  await condition(() => approved, "365 days");

  await processAfterApproval(approver);
}
// External code sends the signal
await client.workflow.getHandle(workflowId).signal("approve", "manager@company.com");

// Query current status
const status = await client.workflow.getHandle(workflowId).query("status");

This pattern — human-in-the-loop, multi-party approval, async notification handling — is extremely difficult in other systems and natural in Temporal.

Pricing

Temporal Cloud:

  • Action rate: $50 per 1M actions (raised from $25/1M in early 2025); volume discounts from 5M+ actions
  • Minimum spend: Greater of $100/month or 5% of monthly consumption
  • Active workflow storage: $1/GB-day; closed workflow history: $0.01/GB-day
  • Default capacity: 500 Actions/second per namespace; add Temporal Resource Units (TRUs) for more
  • Payment: pay-as-you-go (monthly invoice) or pre-purchased Temporal Credits
  • Enterprise: Custom; dedicated support

Self-hosted: Free (Apache 2.0), but you pay infrastructure (Temporal server + database + workers). Non-trivial to operate at scale.

The operational complexity and pricing model make Temporal most suitable for teams with engineering capacity to maintain it — or enterprises paying for Temporal Cloud.


Trigger.dev

What It Is

Trigger.dev is the newest of the three, designed from the ground up for the modern TypeScript developer. v3 (2024) rearchitected the platform around durable tasks — functions that run reliably, resume after failures, and integrate with modern TS tooling.

The pitch: background jobs should feel like writing normal code, not operating distributed systems infrastructure.

Core Concepts

Tasks: The fundamental unit — async functions tagged with Trigger.dev metadata:

import { task, wait, logger } from "@trigger.dev/sdk/v3";

export const processDocument = task({
  id: "process-document",
  retry: {
    maxAttempts: 3,
    minTimeoutInMs: 1000,
    factor: 2,
  },
  run: async (payload: { documentId: string }) => {
    const doc = await fetchDocument(payload.documentId);

    // Extract text
    const text = await extractText(doc);

    // Wait for external processing (doesn't block a worker)
    const result = await wait.for({ seconds: 30 });

    // Run AI classification
    const classification = await classifyDocument(text);

    logger.info("Document classified", { classification });
    return { classification };
  },
});

Triggering tasks:

// From your API or anywhere in your app
import { tasks } from "@trigger.dev/sdk/v3";

// Trigger and get back a handle
const handle = await tasks.trigger("process-document", { documentId: "doc_123" });

// Or trigger and wait for result
const result = await tasks.triggerAndWait("process-document", { documentId: "doc_123" });

Scheduled tasks:

import { schedules } from "@trigger.dev/sdk/v3";

export const weeklyReport = schedules.task({
  id: "weekly-report",
  cron: "0 9 * * MON",
  run: async () => {
    const data = await generateWeeklyReport();
    await sendReport(data);
  },
});

Batch operations:

export const processImages = task({
  id: "process-images",
  run: async (payload: { imageIds: string[] }) => {
    // Process all images in parallel, with automatic batching
    const results = await Promise.all(
      payload.imageIds.map((id) =>
        tasks.triggerAndWait("resize-image", { imageId: id })
      )
    );
    return results;
  },
});

AI Task Integration

Trigger.dev has invested in first-class AI workflow support — particularly for long-running AI tasks that exceed serverless timeouts:

import { task } from "@trigger.dev/sdk/v3";
import Anthropic from "@anthropic-ai/sdk";

export const analyzeDocument = task({
  id: "analyze-document",
  // No timeout worries — Trigger.dev handles long-running tasks
  run: async (payload: { text: string }) => {
    const client = new Anthropic();

    // Claude call that might take 30-60 seconds
    const response = await client.messages.create({
      model: "claude-opus-4-6",
      max_tokens: 4096,
      messages: [{ role: "user", content: `Analyze this document: ${payload.text}` }],
    });

    return response.content[0].text;
  },
});

Pricing

Trigger.dev v3 uses compute-time billing (pay per second of CPU) rather than per-run pricing:

  • Free ($0/month): $5 monthly compute credit; 10 concurrent runs; unlimited tasks; 5 team members; 10 schedules; 1-day log retention
  • Hobby ($10/month): $10 monthly compute; 25 concurrent runs; 100 schedules; 7-day logs
  • Pro ($50/month): $50 monthly compute; 100+ concurrent runs; 25+ team members; 1,000+ schedules; 30-day logs; dedicated Slack support
  • Compute usage: Micro: $0.0000169/sec; Large: $0.00068/sec; $0.000025 per run invocation
  • Enterprise: Custom; self-hosted option available

The compute-time model makes costs predictable for long-running tasks — you pay for actual CPU time, not per execution count.

Self-Hosting

Trigger.dev is open-source (Apache 2.0). Docker Compose deployment:

git clone https://github.com/triggerdotdev/trigger.dev
cd trigger.dev
cp .env.example .env
docker-compose up

Head-to-Head Comparison

Developer Experience

AspectInngestTemporalTrigger.dev
TypeScript DX⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Learning curveLowHighLow
Local dev experience⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Serverless support⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Worker management needed

Features

FeatureInngestTemporalTrigger.dev
Retries + backoff
Step checkpointing
Cron/scheduled jobs
Fan-out / parallel
Sleep / delays
Signals/queries
Human-in-the-loopLimitedLimited
Versioning workflows
Multi-language supportTS, Python, Go7 languagesTS-first (Python extension)

When to Use Each

Choose Inngest if:

  • You're on Vercel, Netlify, or any serverless stack
  • You want event-driven architecture for product workflows
  • You have TypeScript, Python, or Go
  • You want minimal operational overhead
  • Your workflows are product-facing (onboarding, notifications, billing)

Choose Temporal if:

  • You have complex, multi-step business workflows with strict requirements
  • You need signals/queries for external interaction with running workflows
  • You have engineering capacity to operate workers and the Temporal server
  • You're building in a regulated industry (finance, healthcare, logistics)
  • You need workflow versioning for long-lived workflows (months/years)

Choose Trigger.dev if:

  • You're building TypeScript applications and want the cleanest DX
  • You have AI/LLM tasks that exceed serverless timeouts
  • You want aggressive pricing for high-volume workloads
  • You want a modern, actively-developed alternative to Inngest

The Bottom Line

All three solve background job reliability. The differences are about operational model and complexity ceiling:

  • Inngest is the fastest way to add durable execution to a Next.js or serverless app. Zero worker management.
  • Temporal is the most powerful but requires the most engineering investment. Right for genuinely complex workflows.
  • Trigger.dev has the best developer experience in 2026 for pure TypeScript teams, especially for AI workloads.

For a startup that hasn't committed to one: start with Trigger.dev or Inngest. Graduate to Temporal if you outgrow them.


Compare workflow APIs at APIScout.

Related: Stripe vs PayPal vs Adyen vs Square 2026 · OpenAI Agents SDK: Architecture Patterns

Comments