Background Jobs

intermediate*

Serverless background job processing with Inngest. Event-driven workflows with automatic retries.

jobsqueuesinngestserverlessevents
Tested on201619TS5.9
$ bunx sinew add infrastructure/background-jobs
Interactive demo coming soon

1The Problem

Serverless functions have execution time limits and need:

  • Reliable background processing
  • Automatic retries on failure
  • Event-driven workflows
  • Idempotency guarantees

2The Solution

Use Inngest for event-driven background jobs with built-in retries, step functions, and local development tools.

3Files

lib/inngest/client.ts

lib/inngest/client.tsTypeScript
import { Inngest } from "inngest";

export const inngest = new Inngest({
  id: "my-app",
  eventKey: process.env.INNGEST_EVENT_KEY,
});

// Define event types for type safety
export type Events = {
  "user/created": {
    data: { userId: string; email: string; name: string };
  };
  "order/placed": {
    data: {
      orderId: string;
      userId: string;
      items: Array<{ productId: string; quantity: number }>;
      total: number;
    };
  };
};

lib/inngest/functions.ts

lib/inngest/functions.tsTypeScript
import { inngest } from "./client";

// Welcome email after user signup
export const sendWelcomeEmail = inngest.createFunction(
  { id: "send-welcome-email", retries: 3, triggers: [{ event: "user/created" }] },
  async ({ event, step }) => {
    const { userId, email, name } = event.data;

    // Send welcome email
    await step.run("send-email", async () => {
      // await sendEmail({ to: email, template: 'welcome', data: { name } });
    });

    // Wait 24 hours then send tips email
    await step.sleep("wait-for-tips", "24h");

    await step.run("send-tips-email", async () => {
      // await sendEmail({ to: email, template: 'tips' });
    });

    return { success: true, userId };
  }
);

// Process order with multiple steps
export const processOrder = inngest.createFunction(
  {
    id: "process-order",
    retries: 5,
    idempotency: "event.data.orderId", // Prevent duplicates
    triggers: [{ event: "order/placed" }],
  },
  async ({ event, step }) => {
    const { orderId, items, total } = event.data;

    // Reserve inventory
    await step.run("reserve-inventory", async () => {
      // await reserveInventory(items);
    });

    // Charge payment
    const payment = await step.run("charge-payment", async () => {
      // return await stripe.charges.create({ amount: total });
      return { transactionId: "txn_123" };
    });

    // Send confirmation
    await step.run("send-confirmation", async () => {
      // await sendOrderConfirmation(orderId);
    });

    return { orderId, transactionId: payment.transactionId };
  }
);

export const functions = [sendWelcomeEmail, processOrder];

app/api/inngest/route.ts

app/api/inngest/route.tsTypeScript
import { serve } from "inngest/next";
import { inngest } from "@/lib/inngest/client";
import { functions } from "@/lib/inngest/functions";

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions,
});

lib/inngest/trigger.ts

lib/inngest/trigger.tsTypeScript
import { inngest } from "./client";
import type { Events } from "./client";

export async function triggerEvent<K extends keyof Events>(eventName: K, data: Events[K]["data"]) {
  await inngest.send({ name: eventName, data });
}

// Helper functions
export async function onUserCreated(user: { id: string; email: string; name: string }) {
  await triggerEvent("user/created", {
    userId: user.id,
    email: user.email,
    name: user.name,
  });
}

export async function onOrderPlaced(order: {
  id: string;
  userId: string;
  items: Array<{ productId: string; quantity: number }>;
  total: number;
}) {
  await triggerEvent("order/placed", {
    orderId: order.id,
    userId: order.userId,
    items: order.items,
    total: order.total,
  });
}

4Dependencies

$ bun add inngest

5Configuration

Environment Variables

| Variable | Description | Required | | --------------------- | ----------------------------- | ---------------- | | INNGEST_SIGNING_KEY | Inngest signing key | Yes (production) | | INNGEST_EVENT_KEY | Event key for external events | Optional |

Local Development

npx inngest-cli@latest dev
Bash

6Usage

Trigger from API Route

import { onUserCreated } from "@/lib/inngest/trigger";

export async function POST(req: Request) {
  const user = await createUser(data);
  await onUserCreated(user);
  return Response.json(user);
}
TypeScript

Trigger from Server Action

"use server";

import { onOrderPlaced } from "@/lib/inngest/trigger";

export async function placeOrder(formData: FormData) {
  const order = await db.order.create({ data });
  await onOrderPlaced(order);
  return order;
}
TypeScript

7Alternatives

  • Inngest - Event-driven with zero infrastructure
  • Trigger.dev - Open-source, self-hostable
  • QStash - Simple HTTP-based message queue

Related patterns