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.