mech.app
Automation

Trigger.dev V2: TypeScript Background Jobs vs. Temporal's Workflow Engine

How Trigger.dev's managed TypeScript job runner compares to Temporal's durable execution model for state persistence, retries, and deployment boundaries.

Source: trigger.dev
Trigger.dev V2: TypeScript Background Jobs vs. Temporal's Workflow Engine

Trigger.dev launched V2 in October 2023 as a TypeScript-first alternative to Temporal, pivoting from a Zapier clone after early user feedback. The shift exposed a real gap: developers want durable background job orchestration without Temporal’s operational overhead or polyglot SDK complexity. The question is not whether you need orchestration. The question is where simple job queues end and durable workflow engines begin.

The Decision Boundary

Most applications start with Redis-backed job queues like Bull or BullMQ. You push tasks, workers pull them, retries happen at the queue level. This works until you need:

  • Multi-step workflows where step N depends on step N-1’s output
  • Long-running tasks that survive worker restarts (hours or days)
  • Durable state that persists across retries and failures
  • Observability into task execution history and intermediate states

When you cross that line, you need either a workflow engine (Temporal, Trigger.dev) or you build one yourself with Postgres, Redis Streams, and a lot of error-prone state management code.

Trigger.dev’s Architecture

Trigger.dev runs tasks in isolated execution environments with automatic state persistence. The core primitives:

  • Tasks are TypeScript functions decorated with task() that return serializable results
  • Runs are individual task executions tracked with unique IDs
  • State snapshots persist after each step, enabling resume-from-checkpoint on failure
  • Queues and concurrency limits control parallelism at the task or account level

The platform handles deployment, scaling, and infrastructure. You write TypeScript, push to Git, and Trigger.dev builds containers and routes traffic.

import { task } from "@trigger.dev/sdk/v2";

export const processVideo = task({
  id: "process-video",
  run: async (payload: { videoUrl: string }) => {
    // Step 1: Download (state persisted)
    const file = await downloadVideo(payload.videoUrl);
    
    // Step 2: Transcode (state persisted)
    const transcoded = await transcodeVideo(file);
    
    // Step 3: Upload (state persisted)
    const cdnUrl = await uploadToCdn(transcoded);
    
    return { cdnUrl };
  }
});

If the worker crashes after transcoding, Trigger.dev resumes from step 3. You do not write checkpoint logic. The SDK intercepts async calls and snapshots state automatically.

Temporal’s Durable Execution Model

Temporal uses event sourcing. Every workflow decision and activity result gets appended to an immutable event log. When a worker restarts, it replays the log to reconstruct state. This gives you:

  • Deterministic replay: workflows must be deterministic so replay produces identical state
  • Versioning complexity: changing workflow code requires version management to avoid replay mismatches
  • Polyglot SDKs: Go, Java, TypeScript, Python, but TypeScript support lags behind Go/Java

Temporal’s strength is handling workflows that span weeks or months with thousands of steps. The trade-off is operational complexity: you run Temporal Server (often on Kubernetes), manage worker fleets, and debug replay issues when workflows evolve.

State Persistence Trade-offs

AspectTrigger.devTemporal
State modelCheckpoint snapshots after each async callEvent sourcing with full replay
Determinism requirementNo, snapshots capture actual stateYes, replay must be deterministic
Code evolutionChange code freely, snapshots are opaqueRequires versioning and compatibility checks
TypeScript-first supportNative TypeScript SDK and runtimeTypeScript SDK available but Go/Java are primary
ObservabilityTask runs with step-by-step logsWorkflow history with full event log
Storage overheadModerate (snapshots per step)High (every decision and result logged)
Resume granularityLast successful stepAny point in workflow history

Trigger.dev’s snapshot model is simpler but less flexible. You cannot replay from arbitrary points in history. Temporal’s event sourcing gives you time-travel debugging and precise replay, but you pay for it with determinism constraints and versioning overhead.

Retry Semantics

Trigger.dev retries failed tasks with exponential backoff. You configure max attempts and backoff multipliers:

export const flakeyTask = task({
  id: "flakey-task",
  retry: {
    maxAttempts: 5,
    factor: 2,
    minTimeout: 1000,
    maxTimeout: 60000,
  },
  run: async (payload) => {
    // Retries automatically on throw
    const result = await unreliableApi(payload);
    return result;
  }
});

Temporal separates workflow retries from activity retries. Workflows coordinate activities, and activities do the actual work. You configure retry policies per activity, and workflows can implement custom retry logic with timers and conditionals. This gives you fine-grained control but requires understanding the workflow/activity boundary.

Deployment and Hosting

Trigger.dev is managed infrastructure. You:

  1. Install the SDK: npm install @trigger.dev/sdk
  2. Write tasks in your codebase
  3. Push to Git or run trigger.dev deploy
  4. Trigger.dev builds containers, deploys workers, and routes requests

You do not manage servers, queues, or databases. The platform handles autoscaling, observability, and retries. The trade-off is vendor lock-in and less control over execution environments.

Temporal requires self-hosting or using Temporal Cloud. Self-hosting means:

  • Running Temporal Server (Go binary or Kubernetes Helm chart)
  • Managing Cassandra or PostgreSQL for persistence
  • Deploying worker fleets in your infrastructure
  • Configuring Elasticsearch for visibility queries (optional but recommended)

Temporal Cloud removes operational burden but costs more than Trigger.dev for equivalent workloads. You still deploy and manage workers yourself.

Observability and Debugging

Trigger.dev provides a web UI with:

  • Real-time task execution logs
  • Step-by-step progress for multi-step tasks
  • Retry history and failure reasons
  • Metrics on task duration, success rate, and queue depth

Temporal’s Web UI shows:

  • Workflow execution history (full event log)
  • Activity results and retry attempts
  • Stack traces and error messages
  • Time-travel debugging by replaying workflows locally

Temporal’s observability is deeper but harder to navigate. You see every decision and timer, which helps debug complex workflows but overwhelms simple use cases.

When Simpler Orchestration Wins

Use Trigger.dev when:

  • Your team writes TypeScript and wants minimal operational overhead
  • Tasks run for minutes to hours, not weeks
  • You need durable retries and state persistence without event sourcing complexity
  • Managed infrastructure is acceptable (or preferred)

Use Temporal when:

  • Workflows span days or weeks with thousands of steps
  • You need deterministic replay for compliance or debugging
  • Polyglot support matters (Go, Java, Python workers)
  • You already run Kubernetes and want full control over execution environments

Use a simple job queue (Bull, BullMQ, Sidekiq) when:

  • Tasks are independent and idempotent
  • Retries at the queue level are sufficient
  • You do not need multi-step workflows or durable state

Failure Modes

Trigger.dev fails when:

  • Snapshot size explodes: large intermediate states bloat storage and slow resume
  • Non-serializable state: closures, functions, or class instances break snapshots
  • Vendor outage: managed infrastructure means you depend on Trigger.dev’s uptime
  • Compliance replay requirements: workflows requiring deterministic replay for audit trails cannot use snapshot-based state

Temporal fails when:

  • Non-deterministic code: random numbers, timestamps, or external calls in workflow logic break replay
  • Workflow versioning errors: changing workflow code without versioning causes replay mismatches
  • Operational complexity: self-hosting Temporal Server and managing Cassandra/Postgres adds failure surfaces

Both systems fail when you exceed rate limits on external APIs or run out of concurrency slots. Trigger.dev’s managed queues make this easier to hit. Temporal’s self-hosted workers give you more control but require capacity planning.

Technical Verdict

Use Trigger.dev if:

  • You write TypeScript and want zero infrastructure management
  • Your workflows complete in minutes to hours, not days or weeks
  • You prefer managed services over self-hosted control
  • Code simplicity matters more than deterministic replay
  • You need background jobs with durable state but not event sourcing

Avoid Trigger.dev if:

  • You require deterministic replay for compliance audits or time-travel debugging
  • Workflows span multiple days with thousands of steps
  • Vendor lock-in is unacceptable (no self-hosted option in V2)
  • You need polyglot worker support beyond TypeScript

Use Temporal if:

  • Workflows are long-running (days to weeks) with complex state machines
  • Deterministic replay and versioning are compliance requirements
  • You need Go, Java, or Python workers alongside TypeScript
  • You already operate Kubernetes and want infrastructure control

Avoid Temporal if:

  • Your team lacks operational expertise for Cassandra/Postgres and Kubernetes
  • Workflows are simple and short-lived (under an hour)
  • TypeScript is your only language and you want minimal setup

Use a simple job queue (Bull, BullMQ) if:

  • Tasks are independent, idempotent, and do not require multi-step coordination
  • Queue-level retries are sufficient
  • You do not need durable state persistence across worker restarts

The gap Trigger.dev fills is real: TypeScript developers who need more than Bull but less than Temporal. The managed infrastructure bet works if you trust the vendor and do not need self-hosted control. The snapshot model works if your workflows are short enough that event sourcing overhead does not pay off.