Trigger.dev positions itself as a developer-first alternative to Zapier, but the architecture underneath reveals a different ambition: durable execution for long-running tasks, retry primitives, and observability hooks that agent systems need. The platform started as a GUI workflow builder, then pivoted to code-first orchestration. In the V2 announcement (172 HN points), the team repositioned Trigger.dev as a workflow engine for TypeScript developers, signaling a shift toward durable execution patterns that agent systems require.
The core difference is deployment shape. Zapier workflows live in a proprietary runtime. Trigger.dev tasks live in your codebase, versioned alongside application logic, tested in CI, and deployed as part of your release cycle. This changes how you handle failures, debug state transitions, and integrate with existing infrastructure.
Architecture: Tasks as Code
Trigger.dev tasks are TypeScript functions decorated with metadata. You define them in your repository, import them into your application, and trigger them via SDK or webhook. The platform handles execution, retries, and state persistence.
import { task } from "@trigger.dev/sdk/v3";
export const processOrder = task({
id: "process-order",
retry: {
maxAttempts: 3,
factor: 2,
minTimeout: 1000,
maxTimeout: 10000,
},
run: async (payload: { orderId: string }) => {
const order = await db.orders.findUnique({
where: { id: payload.orderId }
});
await stripe.charges.create({
amount: order.total,
currency: "usd",
customer: order.customerId,
});
await shippo.transactions.create({
shipment: order.shipmentId,
rate: order.rateId,
});
return { status: "completed", orderId: order.id };
},
});
The task definition includes retry configuration, idempotency keys, and timeout boundaries. The platform serializes execution state after each step, so if the Stripe call succeeds but Shippo fails, the retry starts from the Shippo call. This is durable execution without explicit checkpointing.
Execution Model and State Persistence
Trigger.dev runs tasks in isolated containers with automatic state snapshots. Each task invocation gets a unique run ID. The platform persists:
- Input payload
- Intermediate results after each await
- Logs and traces
- Retry attempts and backoff timers
When a task fails mid-execution, the next retry reconstructs state from the last successful checkpoint. This works because the SDK intercepts async boundaries and serializes context. The trade-off is that all intermediate values must be JSON-serializable. You cannot pass class instances or open database connections across checkpoints.
For long-running workflows, Trigger.dev supports wait periods and scheduled continuations. A task can pause for hours or days, then resume when an external event arrives. This is useful for human-in-the-loop approval flows or rate-limited API polling.
import { task, wait } from "@trigger.dev/sdk/v3";
export const approvalWorkflow = task({
id: "approval-workflow",
run: async (payload: { documentId: string }) => {
const doc = await fetchDocument(payload.documentId);
await sendApprovalEmail(doc.reviewerId);
// Wait for approval event (up to 7 days)
const approval = await wait.forEvent("approval", {
timeout: "7d",
filter: { documentId: payload.documentId },
});
if (approval.approved) {
await publishDocument(doc.id);
}
return { approved: approval.approved };
},
});
The wait.forEvent primitive suspends execution and releases compute resources. When the approval event arrives (via webhook or SDK call), the platform resumes the task from the exact line where it paused.
Retry Logic and Idempotency
Retry configuration lives in the task definition. You specify max attempts, backoff strategy, and timeout boundaries. The platform automatically retries transient failures (network errors, 5xx responses) but surfaces permanent failures (4xx errors, validation exceptions) immediately.
Idempotency is opt-in. You provide an idempotency key when triggering a task. If the same key appears twice within the deduplication window, the platform returns the cached result instead of re-executing. This prevents duplicate charges, duplicate emails, or duplicate database writes when clients retry failed requests. The idempotency key applies to the initial trigger call, not to internal retries within a single task execution.
| Failure Type | Retry Behavior | Idempotency Impact |
|---|---|---|
| Network timeout | Automatic retry with exponential backoff | Same run ID, retries are part of single execution |
| 5xx API error | Automatic retry up to maxAttempts | Same run ID, retries are part of single execution |
| 4xx API error | Immediate failure, no retry | Cached as failed result if idempotency key provided |
| Validation error | Immediate failure, no retry | Cached as failed result if idempotency key provided |
| Timeout exceeded | Immediate failure, no retry | Cached as failed result if idempotency key provided |
For workflows that span multiple external APIs, you need to design for partial failures. In the processOrder example above, if the Shippo call fails after Stripe succeeds, the retry starts from the Shippo call. But if Shippo is non-idempotent (like sending a shipment notification), you need application-level deduplication. Trigger.dev does not automatically deduplicate side effects within a task.
Observability and Debugging
Every task execution generates structured logs, traces, and metrics. The dashboard shows:
- Run timeline with step durations
- Input/output payloads for each step
- Retry attempts and backoff delays
- Error stack traces with source maps
The platform provides built-in observability through its dashboard and API. For teams that need to export traces to external systems, you can instrument tasks with your own telemetry libraries and forward data to Datadog, Honeycomb, or other observability platforms. Each task run gets a unique run ID that you can propagate through downstream service calls.
For debugging, you can replay a failed run locally. The SDK includes a CLI that fetches the exact input payload and environment variables from a production run, then executes the task in your local environment. This is faster than trying to reproduce failures by manually constructing test payloads.
Deployment and Scaling
Trigger.dev tasks deploy as part of your application. You push code to your repository, the platform detects changes, and deploys updated tasks to isolated containers. Each task version gets a unique identifier, so you can roll back to previous versions without redeploying your entire application.
Scaling is automatic. The platform monitors queue depth and spins up additional containers when tasks are waiting. You set concurrency limits per task to avoid overwhelming downstream APIs. For example, if your task calls a rate-limited API, you might set concurrency to 5 to stay under the rate limit.
The platform supports both push and pull triggers. Push triggers are webhooks or SDK calls that immediately enqueue a task. Pull triggers are scheduled jobs (cron) or event subscriptions (database change streams, message queues). For agent systems, pull triggers are useful for periodic health checks or background data synchronization.
Comparison to Temporal
Trigger.dev markets itself as an alternative to Temporal for TypeScript developers. The core difference is operational complexity. Temporal requires running your own cluster (or paying for Temporal Cloud). Trigger.dev is fully managed. You write tasks, push code, and the platform handles execution.
The trade-off is flexibility. Temporal supports polyglot workflows (Go, Java, Python, TypeScript). Trigger.dev is TypeScript-only. Temporal exposes lower-level primitives like signals, queries, and child workflows. Trigger.dev abstracts these into higher-level patterns like wait.forEvent and task composition.
For agent orchestration, Trigger.dev’s event-driven model fits well. An agent can trigger tasks in response to user input, wait for external API responses, and resume execution when data arrives. The observability layer makes it easier to debug multi-step agent workflows compared to ad-hoc orchestration with queues and workers.
Security Boundaries
Tasks run in isolated containers with no shared state. Each task execution gets its own environment variables, secrets, and network namespace. The platform encrypts secrets at rest and injects them at runtime.
For multi-tenant applications, you need to enforce tenant isolation at the application layer. Trigger.dev does not automatically scope tasks by tenant. If your task processes user data, you must validate that the user has permission to access the data before proceeding.
The platform supports webhook signature verification for inbound triggers. You provide a signing secret, and the platform validates the signature before enqueueing the task. This prevents unauthorized task execution from spoofed webhooks.
Likely Failure Modes
Non-serializable state: If your task captures a database connection, file handle, or class instance, the checkpoint will fail. The platform only serializes JSON-compatible values.
Long-running external calls: If a single API call takes longer than the task timeout, the task fails. You need to break long-running operations into smaller steps with explicit checkpoints.
Rate limit exhaustion: If your task triggers faster than downstream APIs can handle, you will hit rate limits. Set concurrency limits and implement exponential backoff in your task logic.
Idempotency gaps: The double-charge risk in the processOrder example applies only if you do not provide an idempotency key when triggering the task. Internal retries within a single execution are safe because they replay from the last checkpoint. But if a client retries the initial trigger call without an idempotency key, you will execute the entire workflow twice, charging the customer twice. The same risk applies to approvalWorkflow: if sendApprovalEmail is called without an idempotency key and the client retries, the reviewer gets duplicate emails.
Version skew: If you deploy a new task version while old runs are in progress, the old runs continue with the old code. If the new version changes the task signature, old runs may fail when they try to resume. Trigger.dev supports gradual task rollouts: deploy new versions to a subset of tasks first to validate compatibility before full rollout.
Technical Verdict
Use Trigger.dev if:
- You are building payment processing workflows with Stripe, Shippo, and email notification steps that need automatic retry across all three services
- You need order fulfillment tasks that must complete within 24 hours with automatic retry, not sub-second synchronous API responses
- Your team standardizes on TypeScript and you want type safety across task definitions, payloads, and return values
- You need built-in observability without setting up APM tooling or instrumenting custom telemetry
- You want tasks versioned and tested alongside application code in CI/CD pipelines
- You are orchestrating agent workflows with external API calls, human-in-the-loop approvals, or scheduled continuations
Avoid Trigger.dev if:
- You need polyglot support (Python, Go, Java) for workflow logic because your stack is heterogeneous
- Your workflows require sub-second latency or sub-100ms execution times (Trigger.dev is designed for background tasks, not synchronous request handling)
- You need fine-grained control over execution primitives (signals, queries, child workflows) that Temporal exposes
- Your workflows are single-step API calls that do not require durable execution or retry logic
- You already operate a Temporal cluster and have invested in that ecosystem with custom workers and activity implementations
| Dimension | Trigger.dev | Temporal (self-hosted) | AWS Step Functions |
|---|---|---|---|
| Operational overhead | Fully managed, zero-ops | Requires cluster management (Cassandra, Elasticsearch) | Fully managed, AWS-native |
| Language support | TypeScript only | Go, Java, Python, TypeScript, .NET | JSON state machines + Lambda (any language) |
| Cost (10M tasks/month)* | ~$500-1000 (managed tier) | ~$2000-3000 (Temporal Cloud) or infrastructure cost | ~$2500 (state transitions) |
| Durable execution | Built-in checkpointing | Built-in checkpointing | Built-in checkpointing |
| Observability | Integrated dashboard + API | Requires external APM setup | CloudWatch Logs + X-Ray |
*Cost estimate for Trigger.dev assumes managed tier pricing at approximately $0.05-0.10 per 1M tasks. Verify current pricing at trigger.dev/pricing.
For a team with 5 engineers running 10M tasks per month, Trigger.dev costs approximately $500-1000 on the managed tier versus $2000-3000 for Temporal Cloud. Self-hosting Temporal adds infrastructure cost (Cassandra cluster, Elasticsearch, worker nodes) and operational burden. AWS Step Functions costs around $2500 for 10M state transitions but requires designing workflows as JSON state machines rather than imperative code.
The TypeScript-only constraint is a feature for teams that already standardize on Node.js. You get type safety across task definitions, payloads, and return values. But if your stack is polyglot, the lack of language support is a blocker.
For teams building agent systems, Trigger.dev solves the orchestration problem without forcing you to learn a new workflow DSL or manage a distributed execution engine. The code-first model means tasks live in your repository, tested in CI, and versioned alongside your application logic.