Vercel functions time out after 60 seconds. Trigger.dev runs your background jobs for hours — with retries, scheduling, and real-time logs.
What is Trigger.dev?
Trigger.dev is an open-source background job framework for TypeScript. Define tasks, trigger them from your app, and they run reliably on Trigger.dev's infrastructure.
Free Tier
- 5,000 runs/month
- 5 concurrent runs
- 30 min max run duration
- Real-time logs
Quick Start
npx trigger.dev@latest init
Define a Task
// trigger/tasks.ts
import { task } from '@trigger.dev/sdk/v3';
export const processOrder = task({
id: 'process-order',
maxDuration: 300, // 5 minutes
retry: { maxAttempts: 3 },
run: async (payload: { orderId: string; userId: string }) => {
// Step 1: Validate order
const order = await db.orders.findUnique({ where: { id: payload.orderId } });
if (!order) throw new Error('Order not found');
// Step 2: Charge payment (this can take time)
const charge = await stripe.charges.create({
amount: order.total,
currency: 'usd',
customer: order.stripeCustomerId,
});
// Step 3: Update order status
await db.orders.update({
where: { id: payload.orderId },
data: { status: 'paid', chargeId: charge.id },
});
// Step 4: Send confirmation email
await resend.emails.send({
from: 'orders@myapp.com',
to: order.email,
subject: 'Order Confirmed!',
html: `<p>Order ${order.id} has been confirmed.</p>`,
});
return { success: true, chargeId: charge.id };
},
});
Trigger from Your App
import { tasks } from '@trigger.dev/sdk/v3';
import type { processOrder } from './trigger/tasks';
// In your API route
app.post('/api/orders', async (req, res) => {
const order = await db.orders.create({ data: req.body });
// Trigger background job
const handle = await tasks.trigger<typeof processOrder>('process-order', {
orderId: order.id,
userId: req.user.id,
});
res.json({ orderId: order.id, jobId: handle.id });
});
Scheduled Tasks (Cron)
import { schedules } from '@trigger.dev/sdk/v3';
export const dailyReport = schedules.task({
id: 'daily-report',
cron: '0 9 * * *', // Every day at 9 AM
run: async () => {
const metrics = await calculateDailyMetrics();
await sendReportEmail(metrics);
return { sent: true, metrics };
},
});
Wait for External Events
import { task, wait } from '@trigger.dev/sdk/v3';
export const onboardUser = task({
id: 'onboard-user',
run: async (payload: { userId: string }) => {
// Send welcome email
await sendWelcomeEmail(payload.userId);
// Wait 1 day
await wait.for({ days: 1 });
// Check if user completed setup
const user = await db.users.findUnique({ where: { id: payload.userId } });
if (!user.completedSetup) {
await sendReminderEmail(payload.userId);
}
// Wait 3 more days
await wait.for({ days: 3 });
// Final check
const updatedUser = await db.users.findUnique({ where: { id: payload.userId } });
if (!updatedUser.completedSetup) {
await sendFinalReminderEmail(payload.userId);
}
},
});
Trigger.dev vs Alternatives
| Feature | Trigger.dev | Inngest | BullMQ | AWS SQS + Lambda |
|---|---|---|---|---|
| Language | TypeScript | TypeScript | TypeScript | Any |
| Self-host | Yes | Yes | Yes (needs Redis) | No |
| Max Duration | 30 min (free) | Varies | Unlimited | 15 min |
| Cron | Yes | Yes | Yes | EventBridge |
| Real-time Logs | Yes | Yes | Bull Board | CloudWatch |
| Retries | Built-in | Built-in | Built-in | Built-in |
Need background scraping jobs? Check out my Apify actors — scheduled scraping with automatic retries. For custom solutions, email spinov001@gmail.com.
Top comments (0)