Majoritatea aplicatiilor de productie au nevoie de munca care nu se incadreaza in ciclul cerere/raspuns: trimiterea de email-uri, procesarea uploadurilor, rularea pipeline-urilor AI, sincronizarea datelor de la terti, generarea de rapoarte. Raspunsul traditional este o coada (Redis, SQS, RabbitMQ), o flota de workeri, un scheduler si o gramada fragila de cod glue care se sparge la fiecare deploy.
Trigger.dev reduce acel stack la un singur SDK TypeScript. Scrii functii, le apelezi de oriunde, iar platforma se ocupa de queueing, retries, observability, scheduling si executie durabila. Tasks ruleaza atat de mult cat este nevoie - fara timeout serverless de 10 secunde, fara munca pierduta la redeploy-uri.
De ce Trigger.dev
Schimbarea din 2026 este executia durabila. Workflow-urile trebuie sa supravietuiasca restartarilor, crash-urilor, deploy-urilor si rate limit-urilor. Ele trebuie de asemenea sa transmita progresul catre UI in timp real si sa se opreasca pentru input uman. Trigger.dev a fost reconstruit in jurul acestor cerinte cu versiunea 3 si continua sa-si extinda suprafata de infrastructura AI.
Modelul este simplu: definesti tasks ca exports, SDK-ul le ridica, platforma le programeaza si le ruleaza in containere izolate, iar starea run-ului este pastrata astfel incat sa poti relua, reincerca si observa.
Inceperea
Initializeaza un proiect
npx trigger.dev@latest login npx trigger.dev@latest init
Aceasta creeaza un fisier trigger.config.ts si un director trigger/ cu tasks de exemplu. Fisierul config este sursa adevarului pentru proiectul tau: ce directoare contin tasks, setarile de build, lifecycle hooks si optiunile 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"], });
Ruleaza tasks local
npx trigger.dev@latest dev
Serverul dev se conecteaza la cloud, inregistreaza task-urile tale si transmite runs prin codul tau local. Setezi breakpoints in editorul tau si le atingi pe trigger-uri reale - acelasi loop pe care l-ai folosi in orice proiect normal Node.js.
Definirea unui Task
Un task este un obiect exportat cu un id unic si o functie run. SDK-ul inspecteaza exporturile prin dirs si le inregistreaza automat.
// 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 }; }, });
Trei lucruri de retinut:
- Niciun timeout in body-ul run. Platforma gestioneaza timpul de executie prin
maxDurationin config, nu in runtime. - Throws sunt retries. SDK-ul prinde exceptiile si reruleaza cu backoff exponential conform politicii
retry. - Valoarea returnata este pastrata. Alte tasks si frontend-ul tau pot citi
run.outputde oriunde.
Triggerarea Tasks-urilor
Apelezi un task din backend-ul tau, rutele API sau un alt 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 - foloseste pentru a urmari sau afisa progresul
Optiunile deblocheaza mult comportament intr-un singur apel:
idempotencyKey- daca un run cu aceeasi cheie exista deja, SDK-ul returneaza handle-ul existent in loc sa duplice munca.concurrencyKey- serializeaza runs care impart cheia astfel incat sa nu depasesti un rate limit per-tenant.queue.concurrencyLimit- cap global pentru coada in toate cheile.delay- programeaza run-ul pentru un timp viitor.ttl- daca run-ul nu a inceput pana atunci, expira automat.
Batch trigger
Pentru workload-uri fan-out, batchTrigger accepta pana la 500 de items pe apel si creeaza un run per item.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Tasks Programate
Joburile cron devin declaratii de prima clasa. Schedule-ul in sine este un obiect separat pe care il poti atasa la un task de mai multe ori.
// 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); }, });
Pentru schedules per-tenant - sa zicem, un cron per client - le creezi dinamic prin 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}`, });
deduplicationKey face apelul idempotent: rerularea aceluiasi cod la momentul deploy-ului nu acumuleaza schedules duplicate.
Cozi, concurrency si idempotency
Trei primitive acopera majoritatea nevoilor de rate-limiting si ordonare.
Un pattern comun: o coada per tenant cu o concurrency mica per-cheie pentru a respecta rate limit-ul unui vendor, plus o cheie de idempotency pentru a face retries sigure.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Asteptari si munca de lunga durata
Tasks pot face pauza fara a tine o conexiune sau a arde compute. Platforma pastreaza starea si reia functia cand asteptarea se termina.
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 este caracteristica killer: declanseaza un task copil si suspenda parintele pana cand copilul se termina. Compui tasks ca functii async, dar orchestrarea ruleaza durabil pe parcursul zilelor sau saptamanilor.
Human-in-the-loop cu wait.forToken
Pentru fluxuri de aprobare si gates AI, wait.forToken face pauza pana cand aplicatia ta apeleaza inapoi cu un rezultat.
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); }, });
Editorul deschide un UI, revizuieste schita, da clic pe Approve, iar backend-ul tau finalizeaza tokenul. Task-ul preia de unde a ramas - chiar daca au trecut ore sau zile.
Lifecycle Hooks
Poti atasa init, onStart, onSuccess si onFailure la un task sau global in trigger.config.ts. Foloseste-le pentru tracing, error reporting si setup partajat.
// 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 ruleaza o data per worker container la boot, nu per run, deci este locul potrivit pentru a configura clienti si pool-uri.
Realtime in Frontend
Trigger.dev publica modificarile starii run - status, metadata, output - peste un API streaming. Hook-urile React se aboneaza la acel stream si re-renderizeaza automat.
// 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> ); }
Generezi public access token-ul pe partea de server, scoped la un run specific, si il trimiti la client. Hook-ul gestioneaza auth, reconectarea si actualizarile incrementale.
Pentru trigger-and-subscribe intr-un singur pas:
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>;
Agenti AI si Streaming
Trigger.dev a devenit un runtime popular pentru agentii AI deoarece aceleasi primitive - executie durabila, retries, asteptari, metadata in timp real, human-in-the-loop - sunt exact ceea ce au nevoie agentii. Stream-ezi tokens de la un model provider in metadata in timp ce run se intampla, frontend-ul le renderizeaza live, iar run supravietuieste tool calls de lunga durata fara a arde un 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 }; }, });
Frontend-ul foloseste useRealtimeRun si citeste run.metadata.partial pentru a renderiza raspunsul streaming, in acelasi mod in care ai renderiza un chat completion - cu exceptia faptului ca acesta supravietuieste unei reincarcari complete a paginii.
Deployment
Deploy-urile compileaza task-urile tale intr-un bundle versionat, construiesc un container si schimba traficul atomic. Runs vechi in zbor continua sa foloseasca versiunea anterioara.
npx trigger.dev@latest deploy --env prod
In CI conectezi de obicei aceasta la acelasi workflow care livreaza aplicatia ta:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
Pentru medii preview, treci --env preview --branch ${{ github.head_ref }} si Trigger.dev creeaza un mediu izolat per branch, oglindind cum Vercel gestioneaza preview deployment-urile.
Self-Hosting vs Cloud
Trigger.dev este open source sub licenta Apache 2.0. Poti self-host pe orice platforma container (Docker Compose, Kubernetes, Fly.io) sau folosi cloud-ul gestionat la trigger.dev.
| Aspect | Cloud | Self-hosted |
|---|---|---|
| Setup | Inregistrare, executie init | Executie docker-compose sau Helm chart |
| Scaling | Automat | Responsabilitatea ta |
| Pricing | Per run + per compute | Doar costul infrastructurii |
| Compliance | SOC 2 | Ce ofera mediul tau |
| Ideal pentru | Majoritatea echipelor | Rezidenta stricta a datelor, infra custom |
SDK-ul si CLI-ul sunt identice intre moduri - schimbi un flag profile si pointezi la propria ta instanta.
Best Practices
1. Mentine payloads-urile mici si serializabile
Trimite ID-uri si referinte, nu obiecte complete. Trage datele in interiorul task-ului. Aceasta mentine coada mica, payloads ieftine de logat si iti permite sa schimbi sursa de date fara a re-trigger-a.
2. Idempotency keys pe fiecare apel extern
Combina idempotencyKey pe trigger-ul task-ului cu chei de idempotency pe API-urile vendor-ilor (Stripe, OpenAI, etc.). Retries vor fi sigure end-to-end.
3. Foloseste triggerAndWait pentru orchestrare, nu Promise.all de trigger-e
Un parinte care apeleaza triggerAndWait compune durabil tasks copii. Un parinte care declanseaza si rezolva imediat pierde observabilitatea lantului.
4. Tag runs
Adauga tags la trigger-e (tags: ["user:123", "feature:onboarding"]) astfel incat sa poti filtra dashboard-ul si management API dupa dimensiuni de business.
5. Mentine init idempotent
Ruleaza la fiecare cold start. Evita migrations sau efecte secundare one-shot acolo.
Concluzie
Trigger.dev elimina categoriile de munca care odata necesitau sa construiesti un sistem de joburi de la zero. Scrii async TypeScript, il apelezi de oriunde, iar platforma iti ofera executie durabila, scheduling, cozi, retries, actualizari in timp real si pattern-uri human-in-the-loop out of the box.
Aceeasi suprafata care alimenteaza un cron de noapte este suprafata care alimenteaza un agent AI multi-step care transmite la frontend si face pauza pentru revizuire. Aceasta convergenta face framework-ul demn de o privire serioasa in 2026, fie ca rulezi un SaaS care are nevoie de munca de fundal fiabila, fie ca livrezi caracteristici AI care supravietuiesc unui timeout serverless.
Checklist Inceput:
- Inregistreaza-te pe trigger.dev sau ruleaza stack-ul Docker self-hosted
npx trigger.dev@latest initin proiectul tau- Defineste primul task cu
task({ id, run })- Trigger-eaza-l din API-ul tau si vezi run-ul in dashboard
- Adauga
idempotencyKeysiconcurrencyKeypentru siguranta in productie- Conecteaza
useRealtimeRunla o componenta de status- Deploy cu
trigger.dev deploy --env proddin CI