DEV Community

Alex Spinov
Alex Spinov

Posted on

Trigger.dev Has Free Background Jobs — Here's How to Run Long Tasks Without Timeouts

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
Enter fullscreen mode Exit fullscreen mode

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 };
  },
});
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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 };
  },
});
Enter fullscreen mode Exit fullscreen mode

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);
    }
  },
});
Enter fullscreen mode Exit fullscreen mode

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)