Skip to main content
Back to Blog
Tutorials
3 min read
November 26, 2024

How to Implement Background Jobs in Next.js

Implement background job processing in Next.js with Inngest for email sending, data processing, and scheduled tasks.

Ryel Banfield

Founder & Lead Developer

Background jobs handle time-consuming tasks without blocking your API responses. Here is how to implement them with Inngest.

Why Inngest?

  • No infrastructure to manage (no Redis, no workers)
  • Works with serverless (Vercel, Netlify)
  • Built-in retries, scheduling, and event-driven workflows
  • Type-safe with TypeScript

Step 1: Install Inngest

pnpm add inngest

Step 2: Create Inngest Client

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

export const inngest = new Inngest({
  id: "my-app",
  schemas: new EventSchemas().fromRecord<{
    "user/created": { data: { userId: string; email: string; name: string } };
    "order/placed": { data: { orderId: string; userId: string; amount: number } };
    "report/generate": { data: { reportType: string; userId: string } };
    "email/send": { data: { to: string; subject: string; template: string; data: Record<string, unknown> } };
  }>(),
});

Step 3: Define Functions (Jobs)

// lib/inngest/functions/welcome-email.ts
import { inngest } from "../client";
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

export const sendWelcomeEmail = inngest.createFunction(
  {
    id: "send-welcome-email",
    retries: 3,
  },
  { event: "user/created" },
  async ({ event }) => {
    const { email, name } = event.data;

    await resend.emails.send({
      from: "welcome@yourdomain.com",
      to: email,
      subject: `Welcome to our platform, ${name}!`,
      html: `<h1>Welcome, ${name}!</h1><p>We are glad you joined us.</p>`,
    });

    return { sent: true, email };
  }
);
// lib/inngest/functions/order-processing.ts
import { inngest } from "../client";
import { db } from "@/db";
import { orders } from "@/db/schema";
import { eq } from "drizzle-orm";

export const processOrder = inngest.createFunction(
  {
    id: "process-order",
    retries: 5,
    concurrency: {
      limit: 10, // Max 10 concurrent order processing jobs
    },
  },
  { event: "order/placed" },
  async ({ event, step }) => {
    // Step 1: Verify payment
    const payment = await step.run("verify-payment", async () => {
      // Call payment provider
      return { verified: true };
    });

    if (!payment.verified) {
      throw new Error("Payment verification failed");
    }

    // Step 2: Update order status
    await step.run("update-order", async () => {
      await db
        .update(orders)
        .set({ status: "processing" })
        .where(eq(orders.id, event.data.orderId));
    });

    // Step 3: Send confirmation email
    await step.run("send-confirmation", async () => {
      await inngest.send({
        name: "email/send",
        data: {
          to: event.data.userId,
          subject: "Order Confirmed",
          template: "order-confirmation",
          data: { orderId: event.data.orderId },
        },
      });
    });

    // Step 4: Wait 1 hour, then send follow-up
    await step.sleep("wait-for-followup", "1h");

    await step.run("send-followup", async () => {
      // Send "how's your order?" email
    });

    return { processed: true };
  }
);

Step 4: Scheduled Jobs

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

// Run every day at 9 AM
export const dailyDigest = inngest.createFunction(
  { id: "daily-digest" },
  { cron: "0 9 * * *" },
  async ({ step }) => {
    const users = await step.run("get-users", async () => {
      // Get users who opted into daily digest
      return [];
    });

    for (const user of users) {
      await step.run(`send-digest-${user.id}`, async () => {
        // Generate and send digest
      });
    }
  }
);

// Run every Monday at 8 AM
export const weeklyReport = inngest.createFunction(
  { id: "weekly-report" },
  { cron: "0 8 * * 1" },
  async ({ step }) => {
    const report = await step.run("generate-report", async () => {
      // Generate weekly metrics
      return { revenue: 0, users: 0, orders: 0 };
    });

    await step.run("send-report", async () => {
      // Email report to admins
    });
  }
);

Step 5: API Route to Serve Inngest

// app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest } from "@/lib/inngest/client";
import { sendWelcomeEmail } from "@/lib/inngest/functions/welcome-email";
import { processOrder } from "@/lib/inngest/functions/order-processing";
import { dailyDigest, weeklyReport } from "@/lib/inngest/functions/scheduled";

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [sendWelcomeEmail, processOrder, dailyDigest, weeklyReport],
});

Step 6: Triggering Jobs

// In your API route or server action
import { inngest } from "@/lib/inngest/client";

// After user signup
await inngest.send({
  name: "user/created",
  data: { userId: user.id, email: user.email, name: user.name },
});

// After order placement
await inngest.send({
  name: "order/placed",
  data: { orderId: order.id, userId: user.id, amount: order.amount },
});

// Send multiple events at once
await inngest.send([
  { name: "user/created", data: { ... } },
  { name: "email/send", data: { ... } },
]);

Step 7: Fan-Out Pattern

// Process many items in parallel
export const bulkProcess = inngest.createFunction(
  { id: "bulk-process" },
  { event: "bulk/process" },
  async ({ event, step }) => {
    const items = event.data.items;

    // Fan out: send an event for each item
    await step.sendEvent(
      "fan-out",
      items.map((item) => ({
        name: "item/process",
        data: { itemId: item.id },
      }))
    );
  }
);

Need Async Processing?

We build web applications with background job processing, event-driven workflows, and scalable async architectures. Contact us to discuss your project.

background jobsqueuesasync processingNext.jstutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles