mech.app
Automation

Trigger.dev V2: TypeScript-Native Workflow Engine vs Temporal's Orchestration Model

How Trigger.dev's developer experience compares to Temporal for durable execution: state persistence, retries, deployment shape, and failure recovery.

Source: trigger.dev
Trigger.dev V2: TypeScript-Native Workflow Engine vs Temporal's Orchestration Model

Trigger.dev launched V2 after pivoting from a Zapier-style automation builder (V1) to a workflow orchestration platform. The HN community framed it as a “Temporal alternative for TypeScript devs,” exposing a real gap: developers want durable execution without Temporal’s operational overhead, but they need more than simple task queues. The question is whether Trigger.dev’s inability to run workflows across multiple languages or data centers matters for your use case.

This article compares the plumbing. How does each system persist workflow state, schedule retries, handle long-running tasks, and expose failure modes? What does deployment look like, and where do the trade-offs land?

State Persistence and Checkpointing

Temporal persists every workflow decision to an event history store. When a worker crashes, the new worker replays the entire event log to reconstruct in-memory state. Developers write deterministic workflow code, and the framework guarantees that replaying the same events produces the same state. This requires:

  • A persistent database (Cassandra, PostgreSQL, or MySQL) for event storage
  • Deterministic workflow functions (no random UUIDs, no direct I/O)
  • Explicit activity boundaries for non-deterministic operations

Trigger.dev uses a different model. It checkpoints state automatically after each task step. Developers write normal TypeScript functions with async/await, and the SDK serializes the execution context at each await boundary. When a retry happens, the system resumes from the last checkpoint instead of replaying from the beginning.

Key difference: Temporal replays history to rebuild state. Trigger.dev snapshots state directly. This means Trigger.dev can handle non-deterministic code (random values, timestamps) without breaking retries, but it also means the state snapshot must be serializable. Complex objects with circular references or native handles won’t survive a checkpoint.

Retry and Failure Recovery

Both systems retry failed steps, but the mechanics differ.

Temporal retries:

  • Activity retries are configurable per activity (max attempts, backoff strategy)
  • Workflow retries happen at the workflow level, not per step
  • Failed activities can return errors that the workflow handles explicitly
  • Retry state lives in the event history, so changing retry policy mid-flight requires workflow versioning

Trigger.dev retries:

  • Retries happen per task, with exponential backoff by default
  • Developers can configure retry options in the task definition
  • Failed tasks preserve partial progress via checkpoints
  • Changing retry logic requires redeploying the task, but in-flight executions use the old logic

Trigger.dev’s checkpoint model means partial progress is always preserved. If a task completes 8 of 10 steps and crashes, the retry starts at step 9. Temporal requires you to structure workflows so that activities are idempotent, because a replay might re-execute the same activity code (though the framework skips the actual execution and returns the cached result from the event history).

Long-Running Task Handling

Temporal uses durable timers. A workflow can sleep for days or weeks, and the timer lives in the event history. When the timer fires, the workflow resumes. This works because the workflow itself is fundamentally a state machine, and the actual execution happens in workers that can start and stop freely.

Trigger.dev uses a similar approach but exposes it differently. Developers call wait.for() to pause execution, and the platform schedules a resume event. The task worker can shut down, and the platform will spin up a new worker when the wait completes. This is simpler to reason about than Temporal’s replay model, but it requires trusting the platform’s scheduler.

For webhooks or external events, Temporal uses signals (external messages that append to the event history). Trigger.dev uses wait.forEvent(), which registers a listener and suspends the task until the event arrives. Both approaches work, but Temporal’s signal model is more flexible because signals can arrive at any point in the workflow, not only at explicit wait points.

Deployment and Operational Shape

Temporal deployment requires:

  • Temporal Server (Go binary or Kubernetes deployment)
  • Persistent database for event history
  • Worker processes (your code) that poll task queues
  • Optional: Temporal UI, metrics exporters, and archival storage

Self-hosting means managing database backups, server upgrades, and worker autoscaling. Temporal Cloud eliminates server management but still requires deploying and scaling your own worker processes.

Trigger.dev deployment is simpler:

  • Hosted control plane (managed by Trigger.dev)
  • Your code runs in Trigger.dev-managed workers (or self-hosted workers in enterprise mode)
  • No database to manage
  • Built-in observability UI

The trade-off: you’re locked into Trigger.dev’s runtime. Temporal workers are simple polling processes that you can run anywhere. Trigger.dev workers run in the platform’s infrastructure (or your own Kubernetes cluster if you pay for self-hosting).

Observability and Debugging

Temporal exposes:

  • Full event history for every workflow execution
  • Workflow state at any point in time via replay
  • Metrics via Prometheus exporters
  • Stack traces for failed activities
  • Built-in distributed tracing through event history correlation

Debugging a Temporal workflow means reading the event history and replaying it locally. This is powerful but requires understanding the replay model. The event history provides complete tracing across workflow boundaries because every decision and activity result is logged.

Trigger.dev exposes:

  • Real-time task execution logs in the web UI
  • Checkpoint snapshots showing state at each step
  • Retry history and failure reasons
  • Built-in tracing without extra instrumentation
  • Task-level observability with automatic span creation

Trigger.dev’s UI is more accessible for developers who don’t want to learn a new debugging model. However, tracing across task boundaries requires correlating task IDs manually, since each task execution is treated as an independent unit. Temporal’s event history automatically links all activities within a workflow, making it easier to trace complex multi-step processes.

Architectural Comparison: Temporal vs Trigger.dev

DimensionTemporalTrigger.dev
State modelEvent history replayCheckpoint snapshots
DeploymentSelf-hosted or Temporal Cloud (workers still self-managed)Hosted control plane with managed workers
Retry granularityPer activity, configurablePer task, configurable
Long-running tasksDurable timers in event historyPlatform-scheduled resume events
ObservabilityEvent history, Prometheus metrics, automatic workflow tracingBuilt-in UI, real-time logs, task-level spans
Language supportPolyglot (Go, Java, Python, TypeScript, PHP)TypeScript only
Operational overheadHigh (database, server, workers)Low (hosted platform)
Vendor lock-inSelf-hostable open-source (requires operational expertise)High (proprietary runtime)

Code Example: Retry and State Preservation

Here’s how a multi-step task with retries looks in Trigger.dev V3:

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

export const processOrder = task({
  id: "process-order",
  retry: {
    maxAttempts: 3,
    factor: 2,
    minTimeoutInMs: 1000,
  },
  run: async (payload: { orderId: string }) => {
    // Each await boundary creates an automatic checkpoint
    
    const order = await db.orders.findUnique({
      where: { id: payload.orderId },
    });

    const charge = await stripe.charges.create({
      amount: order.total,
      currency: "usd",
      source: order.paymentToken,
    });

    // Task suspends here until webhook arrives or timeout
    const fulfillment = await wait.forEvent("order.fulfilled", {
      filter: { orderId: payload.orderId },
      timeoutInSeconds: 604800, // 7 days
    });

    await sendEmail({
      to: order.customerEmail,
      subject: "Order shipped",
      body: `Tracking: ${fulfillment.trackingNumber}`,
    });

    return { success: true, trackingNumber: fulfillment.trackingNumber };
  },
});

If the Stripe charge fails, the retry starts at step 2. The order fetch doesn’t re-run. If the task crashes while waiting for the fulfillment webhook, the platform reschedules the wait and resumes when the webhook arrives.

In Temporal, you’d structure this as a workflow with three activities (fetch, charge, email) and a signal handler for the fulfillment event. The workflow code would be deterministic, and the activities would handle the actual I/O. Retries would happen at the activity level, and the workflow would replay the entire decision history on each worker restart.

Failure Modes and Limits

Trigger.dev failure modes:

  • Checkpoint serialization fails if state contains non-serializable objects. When this happens, the task enters an error state and retries with exponential backoff. If serialization continues to fail, the task reaches max retry attempts and stops.
  • Platform outage means all tasks pause indefinitely until the platform recovers. There is no local fallback in the standard tier; tasks resume from their last checkpoint once service is restored.
  • Task timeout limits depend on pricing tier (5 minutes to unlimited)
  • No cross-task transactions (each task is independent)

Temporal failure modes:

  • Event history grows unbounded for long-running workflows (requires archival strategy)
  • Workflow versioning is required when changing workflow logic mid-flight
  • Database outage stops all workflow progress
  • Replay can be slow for workflows with thousands of events

Trigger.dev’s checkpoint model avoids unbounded history growth but requires careful state management. Temporal’s replay model avoids serialization issues but requires deterministic code and versioning discipline.

Technical Verdict

Use Trigger.dev when:

  • Your team is TypeScript-native and wants minimal operational overhead
  • You need durable execution for background jobs, webhooks, or scheduled tasks
  • You’re okay with vendor lock-in for simpler developer experience
  • Your workflows fit within the platform’s timeout and concurrency limits

Use Temporal when:

  • You need polyglot support (multiple languages in the same workflow)
  • You require full control over deployment and data residency
  • Your workflows are complex enough to justify the operational investment
  • You need cross-workflow transactions or advanced versioning

Avoid Trigger.dev when:

  • You need to run workflows in your own infrastructure (without enterprise tier)
  • Your state includes complex objects that don’t serialize cleanly
  • You need sub-second task latency (platform overhead adds approximately 100ms)

Avoid Temporal when:

  • Your team lacks the expertise to manage distributed systems
  • You’re building simple background jobs that don’t need durable execution
  • You want to ship fast without learning a new programming model

The real trade-off is operational complexity versus developer experience. Trigger.dev bets that most teams would rather write normal TypeScript and let the platform handle durability. Temporal’s design assumes teams building complex workflows need full control over the execution model. Both are right for different audiences.