Background jobs are inevitable the moment your AI app needs to do anything that takes longer than a few seconds — sending emails, processing uploads, calling slow APIs, or running LLM chains. Trigger.dev is an open-source background job platform built specifically for TypeScript developers. It lets you define jobs as plain TypeScript functions, trigger them from your app, and monitor them in a real-time dashboard — all without managing queues, workers, or Redis yourself.
This guide walks through integrating Trigger.dev into a Next.js application from scratch, including local development with the CLI, writing your first job, and triggering it from an API route.
How Trigger.dev Works
Trigger.dev runs a long-lived background worker that connects to Trigger's cloud (or your self-hosted server) over a persistent HTTP connection. When you fire a job from your app, Trigger routes it to your worker, which executes it in your own infrastructure. This means your code runs in your environment — no cold starts shipping data to a third-party cloud.
| Concept | Description |
|---|---|
| Job | A TypeScript function decorated with `client.defineJob()` |
| Event | The payload that triggers a job — a plain JSON object |
| Run | A single execution of a job with a given event |
| Task | A named step inside a job that shows up in the dashboard |
Installation
# Install the SDK npm install @trigger.dev/sdk @trigger.dev/nextjs # Install the CLI globally npm install -g @trigger.dev/cli # Authenticate and link your project npx trigger.dev@latest login npx trigger.dev@latest init
The `init` command creates a `trigger/` directory, a `trigger.config.ts` file, and a catch-all API route at `app/api/trigger/route.ts` that Trigger uses to register and receive job results.
Writing Your First Job
// trigger/jobs/send-welcome-email.ts import { client } from "@/trigger"; import { eventTrigger } from "@trigger.dev/sdk"; import { z } from "zod"; client.defineJob({ id: "send-welcome-email", name: "Send Welcome Email", version: "1.0.0", trigger: eventTrigger({ name: "user.created", schema: z.object({ userId: z.string(), email: z.string().email(), name: z.string(), }), }), run: async (payload, io) => { // io.logger sends logs visible in the Trigger dashboard await io.logger.info("Sending welcome email", { email: payload.email }); // Use io.runTask() to create named checkpoints const result = await io.runTask( "send-email", async () => { // Replace with your email provider (Resend, SendGrid, etc.) const response = await fetch("https://api.resend.com/emails", { method: "POST", headers: { Authorization: `Bearer ${process.env.RESEND_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "hello@yourapp.com", to: payload.email, subject: `Welcome, ${payload.name}!`, html: `<p>Thanks for signing up. We're glad you're here.</p>`, }), }); return response.json(); }, { name: "Send via Resend" } ); await io.logger.info("Email sent", { id: result.id }); return { success: true, emailId: result.id }; }, });
Registering Jobs
Trigger needs to know about all your jobs. Export them from a central index file:
// trigger/index.ts import { TriggerClient } from "@trigger.dev/sdk"; export const client = new TriggerClient({ id: "my-app", apiKey: process.env.TRIGGER_API_KEY!, apiUrl: process.env.TRIGGER_API_URL, // optional: for self-hosted }); // Import all job files so they self-register import "./jobs/send-welcome-email";
// app/api/trigger/route.ts (auto-generated by init) import { createNextRouteHandler } from "@trigger.dev/nextjs"; import { client } from "@/trigger"; export const { POST, dynamic } = createNextRouteHandler({ client });
Triggering a Job from an API Route
// app/api/auth/signup/route.ts import { client } from "@/trigger"; import { NextResponse } from "next/server"; export async function POST(req: Request) { const { email, name } = await req.json(); // Create user in your database... const user = await createUser({ email, name }); // Fire-and-forget: trigger the background job await client.sendEvent({ name: "user.created", payload: { userId: user.id, email: user.email, name: user.name, }, }); return NextResponse.json({ userId: user.id }, { status: 201 }); }
Local Development
# In one terminal — run your Next.js app npm run dev # In another terminal — connect the Trigger dev server npx trigger.dev@latest dev # The CLI tunnels your local worker to Trigger's cloud # You'll see job runs appear in the dashboard in real time
Use `io.wait()` to pause a job for a duration without holding a worker thread. This is perfect for retry delays or scheduled follow-ups — e.g., send a reminder email 3 days after signup.Environment Variables
# .env.local TRIGGER_API_KEY=tr_dev_xxxxxxxxxxxxxxxxxxxx # Optional for self-hosted: # TRIGGER_API_URL=https://your-trigger-server.com
Viewing Runs in the Dashboard
Every job run appears in the Trigger.dev dashboard with full logs, task timings, input/output payloads, and retry history. You can replay failed runs with the original payload — no need to re-trigger from your app. This makes debugging background jobs dramatically faster than tailing application logs.
Next Steps
- Add scheduled jobs with `intervalTrigger()` or `cronTrigger()` for periodic tasks
- Use `io.sendEvent()` inside a job to chain jobs together
- Add integrations from `@trigger.dev/github`, `@trigger.dev/slack`, and more
- Deploy your worker to Railway, Fly.io, or Render for production