
trigger-jobs
Trigger.dev background job patterns. Use when creating scheduled tasks, async jobs, background worke
Trigger.dev Background Jobs
Complete guide to background jobs, queues, and rate limiting using Trigger.dev.
Overview
This skill covers:
- Task Creation - Defining background jobs with Trigger.dev SDK
- Queue Management - Concurrency limits and rate limiting
- Job Triggering - From tRPC, Better Auth, or anywhere in code
- Email Tasks - Using JSX for React Email templates
- Advanced Patterns - Delays, subtasks, batch processing
When to Apply
Reference these guidelines when:
- Creating background jobs or async tasks
- Sending emails asynchronously
- Implementing rate-limited operations
- Building scheduled or delayed tasks
- Processing batch operations
- Integrating jobs with tRPC or Better Auth
Quick Reference
Basic Task
import { task } from '@trigger.dev/sdk/v3';
export const myTask = task({
id: 'my-task',
queue: {
name: 'default',
concurrencyLimit: 5,
},
run: async (payload: { data: string }, { ctx }) => {
console.log('Executing:', payload.data);
// Your logic here
return { success: true };
},
});
Email Task (with JSX)
// Use .tsx extension
import { emailClient } from '@retrip-ai/email';
import { MyEmail } from '@retrip-ai/email/emails/my-email';
import { task } from '@trigger.dev/sdk/v3';
export const sendEmailTask = task({
id: 'send-email',
queue: {
name: 'emails',
concurrencyLimit: 2, // Respect Resend rate limit
},
run: async (payload: { to: string; subject: string }) => {
await emailClient.emails.send({
from: 'Retrip <platform@no-reply.retrip.io>',
to: payload.to,
subject: payload.subject,
react: <MyEmail />,
});
return { success: true };
},
});
Triggering Tasks
import { myTask } from '@retrip-ai/jobs';
// Trigger and don't wait
await myTask.trigger({ data: 'value' });
// Trigger and wait for result
const handle = await myTask.trigger({ data: 'value' });
const result = await handle.result();
Project Structure
packages/jobs/
├── .env # Environment variables
├── trigger.config.ts # Trigger.dev configuration
└── src/
├── index.ts # Package exports
└── tasks/
├── index.ts
└── my-task.tsx
References
Complete documentation with examples:
references/jobs.md- Task creation, triggers, queues, rate limiting, advanced patterns
To find specific patterns:
grep -l "concurrencyLimit" references/*.md
grep -l "subtasks" references/*.md
grep -l "email" references/*.md
Core Patterns
1. Basic Task Creation
File: packages/jobs/src/tasks/my-task.ts
import { task } from '@trigger.dev/sdk/v3';
import { z } from 'zod';
const payloadSchema = z.object({
userId: z.string(),
message: z.string(),
});
export const myTask = task({
id: 'my-task',
queue: {
name: 'default',
concurrencyLimit: 5, // Max 5 simultaneous executions
},
retry: {
maxAttempts: 3,
minTimeoutInMs: 1000,
maxTimeoutInMs: 10000,
factor: 2,
},
run: async (payload, { ctx }) => {
const validated = payloadSchema.parse(payload);
console.log('Executing task:', validated);
console.log('Run ID:', ctx.run.id);
// Your business logic here
return { success: true, userId: validated.userId };
},
});
Export the task:
// packages/jobs/src/tasks/index.ts
export { myTask } from './my-task';
// packages/jobs/src/index.ts
export * from './tasks';
2. Email Task (with React Email)
IMPORTANT: Use .tsx extension for JSX support.
File: packages/jobs/src/tasks/send-invitation-email.tsx
import { emailClient } from '@retrip-ai/email';
import { InvitationEmail } from '@retrip-ai/email/emails/invitation';
import { task } from '@trigger.dev/sdk/v3';
import { z } from 'zod';
const payloadSchema = z.object({
to: z.string().email(),
invitedByName: z.string(),
teamName: z.string(),
invitationLink: z.string().url(),
});
export const sendInvitationEmailTask = task({
id: 'send-invitation-email',
queue: {
name: 'emails',
concurrencyLimit: 2, // Respect Resend rate limit (2/sec)
},
retry: {
maxAttempts: 3,
},
run: async (payload, { ctx }) => {
const validated = payloadSchema.parse(payload);
console.log(`Sending invitation to ${validated.to}`);
await emailClient.emails.send({
from: 'Retrip <platform@no-reply.retrip.io>',
to: validated.to,
subject: `${validated.invitedByName} invited you to ${validated.teamName}`,
react: (
<InvitationEmail
invitedByName={validated.invitedByName}
teamName={validated.teamName}
invitationLink={validated.invitationLink}
/>
),
});
console.log(`Invitation sent (Run ID: ${ctx.run.id})`);
return { success: true };
},
});
3. Triggering from Better Auth
// packages/auth-db/src/auth.ts
import { sendInvitationEmailTask } from '@retrip-ai/jobs';
organizationPlugin({
sendInvitationEmail: async (data) => {
await sendInvitationEmailTask.trigger({
to: data.email,
invitedByName: 'John Doe',
teamName: 'My Team',
invitationLink: data.link,
});
},
});
4. Triggering from tRPC
// apps/api-trpc/src/routers/notifications.ts
import { sendNotificationTask } from '@retrip-ai/jobs';
export const notificationsRouter = createTRPCRouter({
send: protectedProcedure
.input(z.object({ message: z.string() }))
.mutation(async ({ input }) => {
// Trigger and don't wait
await sendNotificationTask.trigger({
message: input.message,
});
return { queued: true };
}),
});
Queue Management and Rate Limiting
Concurrency Limits
Control how many instances of a task can run simultaneously:
export const myTask = task({
id: 'my-task',
queue: {
name: 'my-queue',
concurrencyLimit: 3, // Max 3 simultaneous executions
},
run: async (payload) => {
// When 3 tasks are running, the 4th waits in queue
},
});
Use cases:
- Rate limiting external APIs (e.g., Resend: 2/sec)
- Preventing database overload
- Respecting third-party service limits
Queue Behavior
All tasks with the same queue name share the same queue:
// Both tasks use the 'emails' queue
export const sendInvitationTask = task({
id: 'send-invitation',
queue: { name: 'emails', concurrencyLimit: 2 },
// ...
});
export const sendWelcomeTask = task({
id: 'send-welcome',
queue: { name: 'emails', concurrencyLimit: 2 },
// ...
});
When both tasks are triggered:
- Maximum 2 emails send at a time (shared limit)
- FIFO queue for waiting tasks
Retry Configuration
export const myTask = task({
id: 'my-task',
retry: {
maxAttempts: 3, // Total attempts (1 original + 2 retries)
minTimeoutInMs: 1000, // Start with 1s delay
maxTimeoutInMs: 10000, // Max 10s delay
factor: 2, // Exponential backoff (1s, 2s, 4s...)
randomize: true, // Add jitter
},
run: async (payload) => {
// If this throws, task will retry automatically
},
});
Advanced Patterns
Task with Delays
export const scheduledTask = task({
id: 'scheduled-task',
run: async (payload: { message: string }, { ctx }) => {
console.log('Starting task...');
// Wait 5 seconds
await ctx.wait.for({ seconds: 5 });
console.log('Continuing after delay...');
// Wait until specific time
await ctx.wait.until({ date: new Date('2025-01-15') });
return { success: true };
},
});
Batch Processing with Subtasks
export const batchEmailTask = task({
id: 'batch-email',
run: async (payload: { emails: string[] }) => {
console.log(`Sending to ${payload.emails.length} recipients`);
// Trigger multiple subtasks in parallel
const handles = await Promise.all(
payload.emails.map((email) =>
sendEmailTask.trigger({ to: email, subject: 'Hello' })
)
);
// Wait for all to complete
const results = await Promise.all(
handles.map((handle) => handle.result())
);
return { sent: results.length };
},
});
Conditional Execution
export const conditionalTask = task({
id: 'conditional-task',
run: async (payload: { shouldSendEmail: boolean }) => {
if (payload.shouldSendEmail) {
await sendEmailTask.trigger({ to: 'user@example.com', subject: 'Hello' });
}
return { executed: payload.shouldSendEmail };
},
});
Configuration
trigger.config.ts
import { defineConfig } from '@trigger.dev/sdk/v3';
export default defineConfig({
project: process.env.TRIGGER_PROJECT_ID || '',
dirs: ['./src/tasks'],
runtime: 'node',
logLevel: 'log',
maxDuration: 60, // Maximum execution time (seconds)
retries: {
enabledInDev: false,
default: {
maxAttempts: 3,
minTimeoutInMs: 1000,
maxTimeoutInMs: 10000,
factor: 2,
randomize: true,
},
},
});
Environment Variables
File: packages/jobs/.env (DO NOT commit)
TRIGGER_SECRET_KEY="tr_dev_xxx"
TRIGGER_PROJECT_ID="proj_xxx"
RESEND_API_KEY="re_xxx"
Get credentials from cloud.trigger.dev.
Local Development
Start Development Server
cd packages/jobs
bun run dev
This starts the local worker that executes tasks.
View Dashboard
Access cloud.trigger.dev to:
- View executed tasks
- Logs and errors
- Retry failed tasks
- Performance metrics
Deployment
cd packages/jobs
bun run deploy
Best Practices
✅ Do:
Task Structure:
- One file per task for clarity
- Use
.tsxextension for JSX (emails) - Validate payloads with Zod schemas
- Return meaningful structured data
Queues:
- Configure
concurrencyLimitto respect rate limits - Use descriptive queue names
- Group related tasks in same queue
Logging:
- Use
console.logwith useful context - Include Run ID in logs:
ctx.run.id - Log start and completion
Error Handling:
- Let errors throw for automatic retry
- Configure retry strategy appropriately
- Monitor dashboard for failures
Configuration:
- Don't commit
.envfiles - Use environment variables for secrets
- Configure
maxDurationappropriately
❌ Don't:
Task Structure:
- ❌ Create tasks without validation
- ❌ Use
.tsfor files with JSX - ❌ Return unstructured data
- ❌ Mix multiple responsibilities in one task
Queues:
- ❌ Skip
concurrencyLimitfor rate-limited APIs - ❌ Use same queue name for unrelated tasks
- ❌ Set limits too high (respect API limits)
Error Handling:
- ❌ Catch and ignore errors (prevents retry)
- ❌ Skip error monitoring
- ❌ Set
maxAttemptstoo high
Dependencies:
- ❌ Forget to add native packages to
build.external - ❌ Use packages not compatible with Node.js runtime
Common Patterns
Email with Validation
const payloadSchema = z.object({
to: z.string().email(),
subject: z.string().min(1).max(100),
});
export const sendEmailTask = task({
id: 'send-email',
queue: { name: 'emails', concurrencyLimit: 2 },
run: async (payload) => {
const validated = payloadSchema.parse(payload);
await emailClient.emails.send({
from: 'Retrip <platform@no-reply.retrip.io>',
to: validated.to,
subject: validated.subject,
react: <EmailTemplate />,
});
},
});
Trigger and Wait
const handle = await myTask.trigger({ data: 'value' });
const result = await handle.result(); // Blocks until complete
console.log(result); // { success: true }
Fire and Forget
await myTask.trigger({ data: 'value' });
// Continue without waiting
Troubleshooting
Error: Missing API key
Ensure .env has all required variables and the dev script loads them:
"dev": "export $(cat .env | xargs) && npx trigger.dev@latest dev"
Error: No trigger files found
Verify dirs in trigger.config.ts points to correct directory.
Error: JSX syntax error
Rename file from .ts to .tsx.
Rate limit exceeded
Reduce concurrencyLimit or add delays between executions.
Native Package Issues
Add to build.external in trigger.config.ts:
build: {
external: ['sharp', 'canvas', 'pino', 'my-native-package'],
},
Related Skills
tanstack-comprehensive- Triggering jobs from tRPC procedures
Version: 1.0.0 Last updated: 2026-01-14