D
Dev SOPKnowledge Base
Search
← All topics

Trigger.dev: Background Jobs, Scheduled Tasks, and Long-Running Workflows

Trigger.dev background job patterns — task definitions, scheduled cron jobs, batch processing, retry configuration, wait/sleep, concurrency limits, and integration with Next.js API routes for job triggering.

trigger-devbackground-jobsschedulingtypescriptqueuesworkflows
Agent trigger phrases: Trigger.dev · background jobs · Trigger task · scheduled job · Trigger.dev cron · long-running task · job queue · Trigger.dev workflow

Overview

Trigger.dev runs background jobs, scheduled tasks, and long-running workflows outside of serverless function limits. Tasks can run for hours, not just seconds.

Installation

npm install @trigger.dev/sdk
npx trigger.dev@latest init

This creates trigger.config.ts and src/trigger/ directory.

Basic Task

// src/trigger/send-email.ts
import { task } from "@trigger.dev/sdk/v3";

export const sendEmailTask = task({
  id: "send-email",
  retry: {
    maxAttempts: 3,
    factor: 2,
    minTimeoutInMs: 1_000,
    maxTimeoutInMs: 30_000,
  },
  run: async (payload: { to: string; subject: string; body: string }) => {
    const result = await emailService.send(payload);
    return { messageId: result.id, sentAt: new Date().toISOString() };
  },
});

Trigger from API Route

// src/app/api/send-email/route.ts
import { NextRequest, NextResponse } from "next/server";
import { sendEmailTask } from "@/trigger/send-email";

export async function POST(request: NextRequest) {
  const payload = await request.json();

  const handle = await sendEmailTask.trigger(payload);

  return NextResponse.json({
    jobId: handle.id,
    status: "queued",
  });
}

Scheduled Tasks (Cron)

// src/trigger/daily-digest.ts
import { schedules } from "@trigger.dev/sdk/v3";

export const dailyDigestTask = schedules.task({
  id: "daily-digest",
  cron: "0 9 * * 1-5",  // 9am UTC weekdays
  run: async () => {
    const users = await db.from("users").select().eq("digest_enabled", true);

    for (const user of users) {
      await sendDigestEmail(user);
    }

    return { processed: users.length };
  },
});

Long-Running Task with Wait

import { task, wait } from "@trigger.dev/sdk/v3";

export const importDataTask = task({
  id: "import-data",
  machine: { preset: "large-1x" },  // more memory for heavy tasks
  run: async (payload: { fileUrl: string }) => {
    // Download file
    const data = await downloadFile(payload.fileUrl);

    // Process in batches with pauses (avoid rate limits)
    const batches = chunk(data, 100);
    const results = [];

    for (let i = 0; i < batches.length; i++) {
      const batchResult = await processBatch(batches[i]);
      results.push(...batchResult);

      // Pause between batches
      if (i < batches.length - 1) {
        await wait.for({ seconds: 2 });
      }
    }

    return { imported: results.length };
  },
});

Batch Trigger

// Trigger multiple jobs at once
const handles = await sendEmailTask.batchTrigger(
  users.map(user => ({
    payload: {
      to: user.email,
      subject: "Weekly Report",
      body: generateReport(user),
    },
  }))
);

console.log(`Triggered ${handles.runs.length} jobs`);

Wait for Another Task

import { task, runs } from "@trigger.dev/sdk/v3";
import { importDataTask } from "./import-data";

export const processAfterImport = task({
  id: "process-after-import",
  run: async (payload: { fileUrl: string }) => {
    // Trigger dependent task and wait for it
    const importHandle = await importDataTask.trigger({ fileUrl: payload.fileUrl });

    // Wait for completion
    const importResult = await runs.poll(importHandle.id, { pollIntervalMs: 5_000 });

    if (importResult.status !== "COMPLETED") {
      throw new Error(`Import failed: ${importResult.status}`);
    }

    // Continue with import results
    return await processImportedData(importResult.output);
  },
});

Concurrency Limits

export const apiSyncTask = task({
  id: "api-sync",
  concurrencyLimit: {
    limit: 5,  // max 5 concurrent runs
    key: (payload) => payload.accountId,  // per-account limit
  },
  run: async (payload: { accountId: string }) => {
    // Only 5 syncs per account run simultaneously
  },
});

Retry Configuration

export const webhookTask = task({
  id: "process-webhook",
  retry: {
    maxAttempts: 5,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 60_000,
    randomize: true,  // jitter
  },
  run: async (payload) => {
    // Automatically retried with exponential backoff on throw
    const result = await externalApi.call(payload);
    return result;
  },
});

Context and Metadata

export const myTask = task({
  id: "my-task",
  run: async (payload, { ctx }) => {
    // Access task metadata
    console.log(`Run ID: ${ctx.run.id}`);
    console.log(`Attempt: ${ctx.attempt.number}`);
    console.log(`Task ID: ${ctx.task.id}`);

    // Mark progress
    await ctx.exportTelemetry.span("processing", async () => {
      await doWork();
    });
  },
});

Trigger.dev Config

// trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";

export default defineConfig({
  project: "proj_xxx",
  runtime: "node",
  logLevel: "log",
  retries: {
    enabledInDev: false,  // don't retry in development
    default: {
      maxAttempts: 3,
      minTimeoutInMs: 1_000,
      maxTimeoutInMs: 10_000,
      factor: 2,
    },
  },
  dirs: ["./src/trigger"],
});

Checking Job Status

import { runs } from "@trigger.dev/sdk/v3";

const run = await runs.retrieve(runId);
console.log(run.status);  // QUEUED | EXECUTING | COMPLETED | FAILED | CANCELED
console.log(run.output);  // task return value (when COMPLETED)

When to Use Trigger.dev vs API Routes

| Scenario | Use | |----------|-----| | < 10 second response needed | API route | | Email sending, webhook processing | Trigger.dev task | | File processing, data import | Trigger.dev task | | Scheduled data sync | Trigger.dev cron | | Multi-step workflows | Trigger.dev task with waits | | Real-time streaming response | API route with streaming |