Trigger.dev started as a developer-first Zapier alternative in early 2023. Seven months later, the team shipped V2 and repositioned as a Temporal alternative for TypeScript. That pivot exposes a real architectural gap: webhook-driven automation cannot handle the state persistence, retry boundaries, and multi-day execution windows that agent workflows demand.
The original V1 Show HN pulled 745 points. The V2 announcement got 172 points and explicit positioning against Temporal. User feedback drove the shift. Developers wanted durable execution primitives, not just event triggers.
Why Webhook Automation Breaks for Long-Running Workflows
Zapier-style tools chain HTTP requests. Each step fires a webhook, waits for a response, then triggers the next step. This works for simple automations: form submission to Slack notification, new email to CRM record.
It fails when workflows span hours or days:
- No state persistence between steps. If a webhook times out, you lose context.
- No replay semantics. Retrying a failed step often duplicates side effects.
- No partial progress tracking. A five-step workflow that fails on step four starts over.
- No idempotency guarantees. Webhooks fire multiple times if the network hiccups.
Agent workflows hit all four problems. A research agent might call a search API, wait for human approval, scrape a dozen URLs, then generate a report. That takes hours. Webhook chains cannot hold state across those boundaries.
What Durable Execution Primitives Actually Provide
Temporal introduced durable execution for polyglot workflows. Trigger.dev V2 brings the same model to TypeScript with a simpler deployment surface.
Core primitives:
- Task definitions. Functions wrapped in a
task()call that the runtime can pause, resume, and retry. - State serialization. The runtime snapshots local variables at await points. If a worker crashes, a new worker resumes from the last snapshot.
- Retry boundaries. You define retry policies per task. The runtime handles exponential backoff, jitter, and max attempts.
- Idempotency keys. Each task invocation gets a unique ID. Duplicate calls with the same ID return the cached result.
Here’s a minimal example:
import { task } from "@trigger.dev/sdk/v3";
export const researchTask = task({
id: "research-workflow",
run: async ({ topic }: { topic: string }) => {
// Step 1: Search
const searchResults = await searchAPI(topic);
// Step 2: Wait for human approval (hours or days)
const approved = await waitForApproval(searchResults);
if (!approved) {
return { status: "rejected" };
}
// Step 3: Scrape URLs (may take minutes)
const scrapedData = await scrapeURLs(searchResults.urls);
// Step 4: Generate report
const report = await generateReport(scrapedData);
return { status: "complete", report };
},
retry: {
maxAttempts: 3,
factor: 2,
minTimeout: 1000,
},
});
If scrapeURLs() throws an error, the runtime retries from that exact point. It does not re-run the search or re-request approval. The state snapshot includes searchResults and approved.
Trigger.dev vs. Temporal: Deployment and State Trade-offs
| Dimension | Trigger.dev V2 | Temporal |
|---|---|---|
| Worker model | Managed cloud workers or self-hosted containers | Self-hosted workers in any language |
| State serialization | TypeScript closures, JSON snapshots | Protobuf event history |
| Deployment complexity | Push code, runtime handles workers | Deploy worker pools, configure queues |
| Replay debugging | Time-travel through snapshots in dashboard | Replay event history locally |
| Language support | TypeScript only | Go, Java, Python, PHP, .NET |
| Observability | Built-in dashboard, real-time traces | Requires external APM integration |
Temporal’s event history model is more flexible. You can replay workflows in any language, inspect every state transition, and build custom tooling on top of the event log. The cost is operational overhead: you run worker pools, tune queue depths, and manage cluster upgrades.
Trigger.dev trades flexibility for simplicity. You write TypeScript, push to the platform, and the runtime handles worker scaling. State snapshots are opaque JSON blobs. You cannot replay them locally or inspect intermediate transitions without the dashboard.
Failure Modes and Retry Boundaries
Durable execution does not eliminate failure. It shifts where failures happen and how you recover.
Common failure scenarios:
- Non-deterministic code in task functions. If your task reads
Date.now()or generates a random UUID, replays produce different results. The runtime cannot guarantee idempotency. - External API rate limits. A task that calls an API 100 times will hit rate limits on retry. You need exponential backoff and circuit breakers.
- State snapshot size limits. Trigger.dev snapshots local variables. If you load a 500 MB dataset into memory, the snapshot fails. You need to store large state in external databases and pass references.
- Partial side effects. If a task writes to a database then crashes before completing, the retry writes again. You need idempotency keys or upsert logic.
Trigger.dev provides retry policies per task, but you still write the idempotency logic. The runtime retries the entire task function. It does not know which lines are safe to re-run.
State Persistence and Replay Mechanics
When a task hits an await, the runtime serializes the call stack and local variables. It stores the snapshot in a database (Postgres in the managed cloud, configurable for self-hosted).
On resume:
- The runtime fetches the snapshot.
- It reconstructs the call stack and local variables.
- It re-executes the function from the await point.
This works for pure functions. It breaks for stateful operations:
- File system writes. If a task writes to
/tmp, the file disappears after a worker restart. - In-memory caches. If a task caches API responses in a Map, the cache resets on replay.
- Database transactions. If a task starts a transaction, the transaction rolls back on crash. The replay starts a new transaction.
You handle these by externalizing state. Write files to S3, cache responses in Redis, use idempotent database operations.
When to Use Trigger.dev vs. Temporal
Use Trigger.dev if:
- You write TypeScript and want zero operational overhead.
- Your workflows run for hours or days but do not need millisecond latency.
- You need built-in observability without integrating APM tools.
- You trust a managed platform to handle worker scaling and state persistence.
Use Temporal if:
- You need polyglot workflows (Python for ML, Go for data pipelines).
- You require full control over worker deployment and scaling.
- You want to replay workflows locally for debugging.
- You need to build custom tooling on top of the event history.
Avoid both if:
- Your workflows complete in seconds and do not need retries. Use a simple queue like BullMQ.
- You need real-time streaming responses. Durable execution adds latency at every await point.
- You cannot tolerate the state snapshot overhead. Some workflows serialize gigabytes of state per step.
Technical Verdict
Trigger.dev V2 is a pragmatic choice for TypeScript teams building agent workflows that span hours or days. It handles the plumbing (state persistence, retries, observability) without forcing you to run infrastructure. The trade-off is lock-in to the TypeScript runtime and opaque state snapshots.
Temporal gives you more control and flexibility at the cost of operational complexity. If you need polyglot workflows or custom replay tooling, the overhead is worth it. If you just want to ship durable tasks without managing worker pools, Trigger.dev is faster.
The real insight from the V1 to V2 pivot: webhook automation and durable execution solve different problems. Webhooks work for event-driven chains with no shared state. Durable execution works for long-running workflows with retries, partial progress, and failure recovery. Most agent workflows need the latter.