mech.app
Automation

Trigger.dev V2: What a Temporal Alternative for TypeScript Reveals About Durable Execution Plumbing

How Trigger.dev pivoted from event workflows to durable execution, exposing retry semantics, state persistence, and TypeScript-first ergonomics.

Source: trigger.dev
Trigger.dev V2: What a Temporal Alternative for TypeScript Reveals About Durable Execution Plumbing

Trigger.dev launched in February 2023 as a “developer-first Zapier alternative” and earned 745 points on Hacker News. Eight months later, the team shipped V2 with a different pitch: a Temporal alternative for TypeScript developers. That pivot exposes a critical gap in the durable execution landscape and reveals what happens when you build workflow infrastructure for the language most developers actually use.

The shift was not cosmetic. V2 replaced event-driven webhook chaining with durable execution primitives: automatic retries, state checkpointing, and long-running task orchestration. The team discovered that developers did not want another IFTTT clone. They wanted a way to run multi-hour jobs, recover from transient failures, and debug workflows without learning Go or deploying a Temporal cluster.

Why Temporal Is Hard and Why That Matters

Temporal is the gold standard for durable execution. It guarantees that workflows complete even if workers crash, networks partition, or third-party APIs timeout. The trade-off is operational complexity:

  • Go-first architecture: The SDK is built for Go. TypeScript and Python SDKs exist but feel like afterthoughts.
  • Cluster management: You run Temporal Server (Cassandra or PostgreSQL backend), workers, and a web UI. Self-hosting means managing stateful services.
  • Workflow determinism: You cannot call Math.random() or Date.now() inside a workflow. Side effects must be wrapped in activities. Violating this breaks replay.

For teams with SRE capacity and Go expertise, Temporal is worth it. For a solo developer building an AI agent that scrapes websites and waits for human approval, the learning curve is a blocker.

What Trigger.dev V2 Changed

V2 kept the durable execution guarantees but optimized for TypeScript ergonomics and managed infrastructure. In practice, this looks like:

State Persistence Without Manual Checkpoints

Temporal requires you to structure workflows as deterministic functions. Trigger.dev uses a task-based model where each step is automatically checkpointed. If a task fails, the runtime replays from the last successful step.

export const processOrder = task({
  id: "process-order",
  run: async ({ orderId }: { orderId: string }) => {
    // Step 1: Fetch order (checkpointed)
    const order = await db.orders.findUnique({ where: { id: orderId } });
    
    // Step 2: Charge payment (checkpointed)
    const charge = await stripe.charges.create({
      amount: order.total,
      currency: "usd",
      source: order.paymentToken,
    });
    
    // Step 3: Send confirmation (checkpointed)
    await sendEmail({
      to: order.email,
      template: "order-confirmation",
      data: { orderId, chargeId: charge.id },
    });
    
    return { status: "completed", chargeId: charge.id };
  },
});

If the Stripe API times out, the task retries from step 2. The database fetch does not re-run. The runtime tracks execution state in a managed Postgres instance, so you do not provision or manage storage infrastructure directly (though storage costs are billed separately).

Retry Semantics and Failure Modes

Trigger.dev uses exponential backoff with jitter by default. You can override per task:

export const flakyScraper = task({
  id: "scrape-site",
  retry: {
    maxAttempts: 5,
    factor: 2,
    minTimeout: 1000,
    maxTimeout: 60000,
    randomize: true,
  },
  run: async ({ url }: { url: string }) => {
    const response = await fetch(url);
    const html = await response.text();
    return parseHTML(html);
  },
});

Failed tasks land in a dead-letter queue visible in the dashboard. You can manually retry, inspect logs, or trigger a webhook. There is no built-in circuit breaker, so if an upstream API is down for hours, tasks will exhaust retries and fail.

Long-Running Tasks and Timeouts

Temporal workflows can run for years. Trigger.dev tasks have a 24-hour wall-clock limit on the managed tier. For longer jobs, you chain tasks:

import { addWeeks } from "date-fns";

export const weeklyReport = task({
  id: "weekly-report",
  run: async ({ startDate }: { startDate: Date }) => {
    const data = await aggregateData(startDate);
    
    // Trigger next week's report
    await weeklyReport.trigger({
      startDate: addWeeks(startDate, 1),
    });
    
    return data;
  },
});

This works for scheduled jobs but breaks the “single workflow” mental model. If you need true multi-day orchestration, you still need Temporal.

Deployment Model and Observability

Trigger.dev runs tasks in managed workers (Node.js or Bun runtimes). You push code via CLI, and the platform handles scaling. The architecture looks like this:

ComponentResponsibilityFailure Mode
API ServerAccepts task triggers, enqueues jobsRate limits apply; overflow queued to durable storage
Worker PoolExecutes tasks, reports checkpointsAuto-scales to zero; cold starts add 2-5s latency
State StorePostgres for execution logs, retriesManaged RDS; no user access to raw tables
DashboardReal-time logs, traces, manual retriesRead-only; no SSH or direct DB queries

Observability is built in. Every task gets:

  • Structured logs with trace IDs
  • Step-by-step execution timeline
  • Retry history and failure reasons
  • OpenTelemetry spans (exportable to Datadog, Honeycomb)

Direct SSH access and debugger attachment are not supported. For complex failures, you add logging and redeploy.

TypeScript vs. Go: Developer Experience Trade-offs

Temporal’s Go SDK enforces determinism at compile time. Trigger.dev’s TypeScript SDK relies on runtime checks and developer discipline. Here is what that means:

Temporal (Go):

  • Workflows are pure functions. Side effects live in activities.
  • Replay is guaranteed safe because the compiler prevents non-deterministic code.
  • Debugging requires understanding workflow history and event sourcing.

Trigger.dev (TypeScript):

  • Tasks can call APIs directly. The runtime checkpoints after each await.
  • Non-deterministic code (like Math.random()) works but breaks replay if you are not careful.
  • Debugging uses standard console.log and stack traces.

For teams building AI agents that call LLMs, scrape websites, and wait for webhooks, the TypeScript model is faster to ship. For financial systems that require audit trails and formal verification, Temporal’s guarantees matter more.

When Self-Hosting Makes Sense

Trigger.dev offers a self-hosted option (Docker Compose or Kubernetes). You run:

  • API server (Node.js)
  • Worker coordinator (manages task distribution)
  • Postgres (state and logs)
  • Redis (job queue)

This makes sense if:

  • You process sensitive data that cannot leave your VPC
  • You need sub-second task latency (managed workers have cold start overhead)
  • You want to customize retry logic or add circuit breakers

The trade-off is operational burden. You manage database backups, worker scaling, and security patches. The managed tier costs $20/month for 500,000 task runs. Self-hosting costs engineer time.

Comparison: Trigger.dev vs. Temporal vs. Inngest

FeatureTrigger.dev V2TemporalInngest
Primary LanguageTypeScriptGo (TS/Python SDKs exist)TypeScript
State PersistenceManaged PostgresSelf-hosted (Cassandra/Postgres)Managed (proprietary)
Max Task Duration24 hoursUnlimited24 hours
Retry StrategyExponential backoff, DLQConfigurable, infinite retriesExponential backoff, manual replay
DeploymentManaged workers or self-hostedSelf-hosted cluster requiredManaged only
ObservabilityBuilt-in dashboard, OTEL exportTemporal UI, requires setupBuilt-in dashboard
Pricing$20/mo for 500k runsFree (self-hosted), enterprise for cloudFree tier, paid plans vary

Likely Failure Modes

Cold Start Latency: Managed workers spin down after inactivity. First task in a batch adds 2-5 seconds. If you trigger 1000 tasks simultaneously, some will queue.

24-Hour Limit: Tasks that need to wait days (like “send reminder in 3 days”) require chaining or external schedulers. Temporal handles this natively.

No Circuit Breaker: If an upstream API is down, tasks retry until max attempts. You cannot pause all tasks for a specific integration without custom logic.

Vendor Lock-In: The managed tier uses proprietary state storage. Migrating to Temporal or self-hosted Trigger.dev requires rewriting task definitions.

Technical Verdict

Use Trigger.dev V2 when:

  • You build in TypeScript and want durable execution without learning Go
  • Tasks complete in hours, not days
  • You prefer managed infrastructure over running a Temporal cluster
  • You need fast iteration on AI agents, webhooks, or background jobs

Avoid it when:

  • You need multi-day workflows with human-in-the-loop steps
  • You require formal guarantees about determinism (financial systems, compliance)
  • You already run Temporal and have Go expertise
  • You need sub-second task latency at scale

Trigger.dev is not a Temporal replacement. It is a TypeScript-first durable execution runtime that trades some of Temporal’s guarantees for developer velocity. For teams building AI agents, the trade-off is worth it. For teams building payment systems, it is not.