A maioria das aplicacoes em producao precisa de trabalho que nao se encaixa no ciclo requisicao/resposta: enviar emails, processar uploads, executar pipelines de IA, sincronizar dados de terceiros, gerar relatorios. A resposta tradicional e uma fila (Redis, SQS, RabbitMQ), uma frota de workers, um scheduler e uma pilha fragil de codigo de cola que quebra a cada deploy.
Trigger.dev colapsa essa stack em um unico SDK TypeScript. Voce escreve funcoes, as chama de qualquer lugar e a plataforma gerencia enfileiramento, retries, observabilidade, scheduling e execucao duravel. As tasks rodam pelo tempo necessario - sem timeout serverless de 10 segundos, sem trabalho perdido em redeploys.
Por que Trigger.dev
A mudanca em 2026 e a execucao duravel. Os workflows precisam sobreviver a reinicios, crashes, deploys e rate limits. Tambem precisam transmitir o progresso para a UI em tempo real e pausar para input humano. O Trigger.dev foi reconstruido em torno desses requisitos com a versao 3 e continua expandindo sua superficie de infraestrutura para IA.
O modelo e simples: voce define tasks como exports, o SDK as recolhe, a plataforma as agenda e executa em containers isolados e o estado do run e persistido para que voce possa retomar, retentar e observar.
Comecando
Inicializar um projeto
npx trigger.dev@latest login npx trigger.dev@latest init
Isso cria um arquivo trigger.config.ts e um diretorio trigger/ com tasks de exemplo. O arquivo de config e a fonte de verdade do seu projeto: quais diretorios contem tasks, configuracoes de build, lifecycle hooks e opcoes de runtime.
// trigger.config.ts import { defineConfig } from "@trigger.dev/sdk"; export default defineConfig({ project: "proj_abc123", runtime: "node", logLevel: "log", maxDuration: 3600, retries: { enabledInDev: true, default: { maxAttempts: 3, factor: 2, minTimeoutInMs: 1000, maxTimeoutInMs: 30_000, }, }, dirs: ["./trigger"], });
Executar tasks localmente
npx trigger.dev@latest dev
O servidor de dev se conecta a cloud, registra suas tasks e transmite os runs atraves do seu codigo local. Voce coloca breakpoints no seu editor e os atinge em triggers reais - o mesmo loop que voce usaria em qualquer projeto Node.js normal.
Definindo uma Task
Uma task e um objeto exportado com um id unico e uma funcao run. O SDK inspeciona exports atraves de dirs e os registra automaticamente.
// trigger/send-welcome-email.ts import { task } from "@trigger.dev/sdk"; import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY); export const sendWelcomeEmail = task({ id: "send-welcome-email", retry: { maxAttempts: 5, factor: 1.8, minTimeoutInMs: 500, maxTimeoutInMs: 30_000, }, run: async (payload: { email: string; name: string }) => { const { data, error } = await resend.emails.send({ from: "hello@spinny.dev", to: payload.email, subject: `Welcome, ${payload.name}`, html: `<p>Glad you are here, ${payload.name}.</p>`, }); if (error) throw error; return { messageId: data?.id }; }, });
Tres coisas a notar:
- Sem timeout no corpo do run. A plataforma gerencia o tempo de execucao via
maxDurationna config, nao no runtime. - Throws sao retries. O SDK captura excecoes e re-executa com backoff exponencial conforme a politica
retry. - O valor de retorno e persistido. Outras tasks e seu frontend podem ler
run.outputde qualquer lugar.
Triggando Tasks
Voce chama uma task do seu backend, suas rotas API ou outra task.
import { sendWelcomeEmail } from "@/trigger/send-welcome-email"; const handle = await sendWelcomeEmail.trigger( { email: "user@example.com", name: "Alex" }, { idempotencyKey: `welcome-${userId}`, concurrencyKey: `tenant-${tenantId}`, queue: { name: "emails", concurrencyLimit: 50 }, delay: "30s", ttl: "10m", } ); console.log(handle.id); // run_xyz - use para rastrear ou exibir o progresso
As opcoes desbloqueiam muito comportamento em uma unica chamada:
idempotencyKey- se um run com a mesma key ja existir, o SDK retorna o handle existente em vez de duplicar trabalho.concurrencyKey- serializa runs que compartilham a key para que voce nao ultrapasse um rate limit por tenant.queue.concurrencyLimit- cap global para a fila atraves de todas as keys.delay- agenda o run para um tempo futuro.ttl- se o run nao tiver iniciado ate la, expira-o automaticamente.
Batch trigger
Para workloads de fan-out, batchTrigger aceita ate 500 itens por chamada e cria um run por item.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Tasks Agendadas
Cron jobs se tornam declaracoes de primeira classe. O proprio schedule e um objeto separado que voce pode anexar a uma task multiplas vezes.
// trigger/daily-digest.ts import { schedules } from "@trigger.dev/sdk"; export const dailyDigest = schedules.task({ id: "daily-digest", cron: "0 9 * * *", run: async (payload) => { console.log("Scheduled at:", payload.timestamp); console.log("Last run:", payload.lastTimestamp); console.log("Timezone:", payload.timezone); console.log("Next 5 runs:", payload.upcoming); await sendDigestForDate(payload.timestamp); }, });
Para schedules por tenant - digamos, um cron por cliente - voce os cria dinamicamente atraves da management API.
import { schedules } from "@trigger.dev/sdk"; await schedules.create({ task: "daily-digest", cron: "0 9 * * *", timezone: "America/New_York", externalId: `customer_${customerId}`, deduplicationKey: `digest-${customerId}`, });
A deduplicationKey torna a chamada idempotente: re-executar o mesmo codigo no momento do deploy nao empilha schedules duplicados.
Filas, Concorrencia e Idempotencia
Tres primitivas cobrem a maioria das necessidades de rate-limiting e ordenacao.
Um padrao comum: uma fila por tenant com uma pequena concorrencia por key para respeitar o rate limit de um vendor, mais uma idempotency key para tornar os retries seguros.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Esperas e Trabalho de Longa Duracao
Tasks podem pausar sem manter uma conexao ou queimar compute. A plataforma persiste o estado e retoma a funcao quando a espera se completa.
import { wait } from "@trigger.dev/sdk"; export const onboarding = task({ id: "onboarding", run: async (payload: { userId: string }) => { await sendWelcomeEmail.triggerAndWait({ userId: payload.userId }); await wait.for({ days: 1 }); await sendTipsEmail.trigger({ userId: payload.userId }); await wait.until({ date: oneWeekFromSignup(payload.userId) }); await sendUpgradeOffer.trigger({ userId: payload.userId }); }, });
triggerAndWait e o recurso decisivo: triggera uma task filha e suspende o pai ate que a filha se complete. Voce compoe tasks como funcoes async, mas a orquestracao roda duravelmente atraves de dias ou semanas.
Human-in-the-loop com wait.forToken
Para fluxos de aprovacao e gates de IA, wait.forToken pausa ate que sua aplicacao responda com um resultado.
import { task, wait } from "@trigger.dev/sdk"; export const publishPost = task({ id: "publish-post", run: async (payload: { draftId: string }) => { const draft = await generateAIContent(payload.draftId); const token = await wait.createToken({ timeout: "7d" }); await notifyEditor({ draftId: draft.id, token: token.id }); const decision = await wait.forToken<{ approved: boolean; notes?: string }>( token.id ); if (decision.approved) { return await publish(draft); } return await applyFeedback(draft, decision.notes); }, });
O editor abre uma UI, revisa o rascunho, clica em Aprovar e seu backend completa o token. A task continua de onde parou - mesmo que horas ou dias tenham passado.
Lifecycle Hooks
Voce pode anexar init, onStart, onSuccess e onFailure a uma task ou globalmente em trigger.config.ts. Use-os para tracing, error reporting e setup compartilhado.
// trigger.config.ts export default defineConfig({ // ... init: async () => { Sentry.init({ dsn: process.env.SENTRY_DSN }); }, onFailure: async ({ error, ctx }) => { Sentry.captureException(error, { tags: { taskId: ctx.task.id, runId: ctx.run.id }, }); }, });
init roda uma vez por container worker no boot, nao por run, entao e o lugar certo para configurar clientes e pools.
Realtime no Frontend
O Trigger.dev publica mudancas de estado do run - status, metadata, output - sobre uma API em streaming. Os hooks React se inscrevem nesse stream e re-renderizam automaticamente.
// trigger/process-video.ts import { task, metadata } from "@trigger.dev/sdk"; export const processVideo = task({ id: "process-video", run: async (payload: { videoId: string }) => { metadata.set("stage", "transcoding"); await transcode(payload.videoId); metadata.set("stage", "thumbnails"); await generateThumbnails(payload.videoId); metadata.set("stage", "uploading"); const url = await uploadToCDN(payload.videoId); return { url }; }, });
// components/VideoStatus.tsx "use client"; import { useRealtimeRun } from "@trigger.dev/react-hooks"; import type { processVideo } from "@/trigger/process-video"; export function VideoStatus({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun<typeof processVideo>(runId, { accessToken: publicAccessToken, }); if (error) return <p>Error: {error.message}</p>; if (!run) return <p>Loading...</p>; return ( <div> <p>Status: {run.status}</p> <p>Stage: {String(run.metadata?.stage ?? "queued")}</p> {run.output?.url && <video src={run.output.url} controls />} </div> ); }
Voce gera o public access token no servidor, com escopo para um run especifico, e o envia ao cliente. O hook gerencia auth, reconexao e atualizacoes incrementais.
Para trigger-and-subscribe em uma so vez:
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks"; const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof processVideo>( "process-video", { accessToken: publicAccessToken } ); <button onClick={() => submit({ videoId })} disabled={isLoading}> Process video </button>;
Agentes de IA e Streaming
O Trigger.dev se tornou um runtime popular para agentes de IA porque as mesmas primitivas - execucao duravel, retries, esperas, metadata em tempo real, human-in-the-loop - sao exatamente o que os agentes precisam. Voce transmite tokens de um provedor de modelo para metadata enquanto o run esta acontecendo, o frontend os renderiza ao vivo e o run sobrevive a tool calls de longa duracao sem queimar um timeout serverless.
import { task, metadata } from "@trigger.dev/sdk"; import { streamText } from "ai"; import { anthropic } from "@ai-sdk/anthropic"; export const researchAgent = task({ id: "research-agent", maxDuration: 1800, run: async (payload: { question: string }) => { const result = streamText({ model: anthropic("claude-opus-4-7"), system: "You are a research assistant. Use the web.", prompt: payload.question, tools: { webSearch }, }); let fullText = ""; for await (const chunk of result.textStream) { fullText += chunk; metadata.set("partial", fullText); } return { answer: fullText, usage: await result.usage }; }, });
O frontend usa useRealtimeRun e le run.metadata.partial para renderizar a resposta em streaming, da mesma forma que voce renderizaria uma chat completion - exceto que esta sobrevive a um reload completo da pagina.
Deployando
Os deploys compilam suas tasks em um bundle versionado, constroem um container e fazem swap atomico do trafego. Os runs antigos em voo continuam usando a versao anterior.
npx trigger.dev@latest deploy --env prod
Em CI voce tipicamente conecta isso ao mesmo workflow que envia sua app:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
Para ambientes de preview, passe --env preview --branch ${{ github.head_ref }} e o Trigger.dev cria um ambiente isolado por branch, espelhando como o Vercel lida com preview deployments.
Self-Hosting vs Cloud
O Trigger.dev e open source sob a licenca Apache 2.0. Voce pode self-hostear em qualquer plataforma de containers (Docker Compose, Kubernetes, Fly.io) ou usar a cloud gerenciada em trigger.dev.
| Aspecto | Cloud | Self-hosted |
|---|---|---|
| Setup | Cadastrar, executar init | Executar docker-compose ou Helm chart |
| Scaling | Automatico | Sua responsabilidade |
| Pricing | Por run + por compute | Apenas custo de infra |
| Compliance | SOC 2 | O que seu ambiente fornece |
| Ideal para | A maioria das equipes | Residencia de dados estrita, infra customizada |
O SDK e a CLI sao identicos entre os modos - voce muda uma flag de profile e aponta para sua propria instancia.
Best Practices
1. Mantenha os payloads pequenos e serializaveis
Passe IDs e referencias, nao objetos completos. Recupere os dados dentro da task. Isso mantem a fila pequena, payloads baratos para logar e permite mudar a fonte de dados sem re-triggerar.
2. Idempotency keys em cada chamada externa
Combine idempotencyKey no trigger da task com idempotency keys nas APIs dos seus vendors (Stripe, OpenAI, etc.). Os retries serao seguros end-to-end.
3. Use triggerAndWait para orquestracao, nao Promise.all de triggers
Um pai que chama triggerAndWait compoe duravelmente tasks filhas. Um pai que triggera e resolve imediatamente perde a observabilidade da cadeia.
4. Tagge os runs
Adicione tags aos triggers (tags: ["user:123", "feature:onboarding"]) para poder filtrar o dashboard e a management API por dimensoes de negocio.
5. Mantenha init idempotente
Roda em cada cold start. Evite migrations ou efeitos colaterais one-shot la dentro.
Conclusao
O Trigger.dev remove as categorias de trabalho que costumavam exigir construir um sistema de jobs do zero. Voce escreve TypeScript async, o chama de qualquer lugar e a plataforma te da execucao duravel, scheduling, filas, retries, atualizacoes em tempo real e padroes human-in-the-loop out of the box.
A mesma superficie que alimenta um cron noturno e a superficie que alimenta um agente de IA multi-passo que transmite ao frontend e pausa para revisao. Essa convergencia e o que torna o framework digno de uma olhada seria em 2026, seja voce gerenciando um SaaS que precisa de trabalho em segundo plano confiavel ou enviando recursos de IA que sobrevivem a um timeout serverless.
Checklist para Comecar:
- Cadastre-se em trigger.dev ou execute o stack Docker self-hosted
npx trigger.dev@latest initno seu projeto- Defina sua primeira task com
task({ id, run })- Triggere-a da sua API e veja o run no dashboard
- Adicione
idempotencyKeyeconcurrencyKeypara seguranca em producao- Conecte
useRealtimeRunem um componente de status- Deploye com
trigger.dev deploy --env proddo CI