Inngest provides durable execution for your workflows. Functions automatically retry on failure, maintain state across steps, and can run for days or weeks.
Inngest is an event-driven workflow engine that runs on your existing infrastructure. It handles the complexity of distributed systems: retries, state management, concurrency control, and observability.
Failed steps automatically retry with exponential backoff. No data loss even during outages.
State persists between steps. Resume exactly where you left off, even after server restarts.
Break workflows into steps. Each step runs independently and can be monitored separately.
Visual dashboard shows every function run, step timing, errors, and retry attempts.
// lib/inngest/client.ts
import { Inngest } from "inngest"
export const inngest = new Inngest({
id: "realaroha",
// Optional: Custom event schemas for type safety
schemas: new EventSchemas().fromRecord<Events>()
})
// Type-safe event definitions
type Events = {
"booking.created": {
data: {
bookingId: string
propertyId: string
guestId: string
checkIn: string
checkOut: string
totalAmount: number
}
}
"booking.cancelled": {
data: {
bookingId: string
reason: string
refundAmount: number
}
}
// ... more events
}// app/api/inngest/route.ts
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: functions, // Array of all your functions
})Steps are the building blocks of Inngest functions. Each step runs independently, has its own retry policy, and its result is cached.
export const processBooking = inngest.createFunction(
{
id: "process-booking",
retries: 3,
onFailure: async ({ error, event }) => {
// Handle permanent failure
await notifyOps("Booking processing failed", { error, event })
}
},
{ event: "booking.created" },
async ({ event, step }) => {
// Step 1: Validate booking
const validation = await step.run("validate-booking", async () => {
const booking = await getBooking(event.data.bookingId)
if (!booking) throw new Error("Booking not found")
if (booking.status !== "pending") throw new Error("Invalid status")
return { valid: true, booking }
})
// Step 2: Check availability (separate retry scope)
const availability = await step.run("check-availability", async () => {
const available = await checkPropertyAvailability(
event.data.propertyId,
event.data.checkIn,
event.data.checkOut
)
if (!available) {
throw new NonRetriableError("Property not available")
}
return { available: true }
})
// Step 3: Process payment
const payment = await step.run("process-payment", async () => {
const result = await processPayment({
bookingId: event.data.bookingId,
amount: event.data.totalAmount,
guestId: event.data.guestId
})
return result
})
// Step 4: Confirm booking (only runs if payment succeeds)
await step.run("confirm-booking", async () => {
await updateBookingStatus(event.data.bookingId, "confirmed")
await blockCalendarDates(event.data.propertyId, {
start: event.data.checkIn,
end: event.data.checkOut
})
})
// Step 5: Send notifications (parallel)
await Promise.all([
step.run("notify-guest", async () => {
await sendEmail({
to: event.data.guestId,
template: "booking_confirmed"
})
}),
step.run("notify-host", async () => {
await notifyPropertyOwner(event.data.propertyId, {
type: "new_booking",
bookingId: event.data.bookingId
})
})
])
return {
success: true,
bookingId: event.data.bookingId,
paymentId: payment.id
}
}
)Inngest can wait for time periods or events without consuming resources.
// Wait for a specific duration
export const sendReminder = inngest.createFunction(
{ id: "send-check-in-reminder" },
{ event: "booking.confirmed" },
async ({ event, step }) => {
// Calculate time until check-in
const checkInDate = new Date(event.data.checkIn)
const reminderDate = new Date(checkInDate)
reminderDate.setDate(reminderDate.getDate() - 1) // 1 day before
// Wait until reminder time (function sleeps, no compute used)
await step.sleepUntil("wait-for-reminder-time", reminderDate)
// Send the reminder
await step.run("send-reminder", async () => {
await sendEmail({
to: event.data.guestId,
template: "check_in_reminder",
data: {
propertyAddress: event.data.propertyAddress,
checkInTime: event.data.checkInTime,
accessInstructions: event.data.accessInstructions
}
})
})
}
)
// Wait for another event
export const awaitPayment = inngest.createFunction(
{ id: "await-payment" },
{ event: "booking.approved" },
async ({ event, step }) => {
// Wait up to 24 hours for payment
const paymentEvent = await step.waitForEvent("wait-for-payment", {
event: "payment.completed",
match: "data.bookingId", // Match on bookingId
timeout: "24h"
})
if (!paymentEvent) {
// Payment timeout - cancel booking
await step.run("cancel-booking", async () => {
await cancelBooking(event.data.bookingId, "payment_timeout")
await sendEmail({
to: event.data.guestId,
template: "booking_expired"
})
})
return { status: "cancelled", reason: "payment_timeout" }
}
// Payment received - confirm booking
await step.run("confirm-booking", async () => {
await confirmBooking(event.data.bookingId)
})
return { status: "confirmed", paymentId: paymentEvent.data.paymentId }
}
)Process multiple items in parallel, then aggregate results.
export const processPropertyBatch = inngest.createFunction(
{ id: "process-property-batch" },
{ event: "properties.batch.process" },
async ({ event, step }) => {
const { propertyIds } = event.data
// Fan-out: Process each property in parallel
const results = await Promise.all(
propertyIds.map((propertyId, index) =>
step.run(`process-property-${index}`, async () => {
const property = await getProperty(propertyId)
const analytics = await calculatePropertyAnalytics(property)
const score = await updatePropertyScore(propertyId, analytics)
return { propertyId, score, analytics }
})
)
)
// Fan-in: Aggregate results
const summary = await step.run("aggregate-results", async () => {
const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length
const topPerformers = results
.filter(r => r.score > 90)
.map(r => r.propertyId)
await saveBatchReport({
processedAt: new Date(),
totalProperties: results.length,
averageScore: avgScore,
topPerformers
})
return { avgScore, topPerformers }
})
return summary
}
)
// With concurrency control
export const processWithLimit = inngest.createFunction(
{
id: "process-with-concurrency-limit",
concurrency: {
limit: 10, // Max 10 concurrent executions
key: "event.data.organizationId" // Per organization
}
},
{ event: "batch.process" },
async ({ event, step }) => {
// Process items with controlled concurrency
}
)import { NonRetriableError } from "inngest"
export const robustFunction = inngest.createFunction(
{
id: "robust-function",
retries: 5, // Number of retry attempts
// Custom backoff configuration
backoff: {
type: "exponential",
delay: "1s", // Initial delay
multiplier: 2, // Multiply delay each retry
maxDelay: "1h" // Cap at 1 hour
}
},
{ event: "task.execute" },
async ({ event, step }) => {
await step.run("call-external-api", async () => {
try {
const response = await fetch("https://api.example.com/data")
if (response.status === 429) {
// Rate limited - retry with backoff
throw new Error("Rate limited")
}
if (response.status === 400) {
// Bad request - don't retry, it will always fail
throw new NonRetriableError("Invalid request data")
}
return await response.json()
} catch (error) {
if (error instanceof NonRetriableError) throw error
// Network errors, timeouts, etc. will be retried
throw error
}
})
}
)
// Per-step retry configuration
export const mixedRetries = inngest.createFunction(
{ id: "mixed-retries" },
{ event: "task.run" },
async ({ event, step }) => {
// This step retries 3 times
await step.run("step-with-retries", async () => {
// ... code that might fail
}, { retries: 3 })
// This step never retries
await step.run("step-no-retry", async () => {
// Critical step - fail fast if it doesn't work
}, { retries: 0 })
}
)// Daily report at 9 AM
export const dailyReport = inngest.createFunction(
{ id: "daily-report" },
{ cron: "0 9 * * *" }, // Every day at 9:00 AM
async ({ step }) => {
const report = await step.run("generate-report", async () => {
const bookings = await getTodaysBookings()
const revenue = await calculateDailyRevenue()
const occupancy = await getOccupancyRate()
return { bookings, revenue, occupancy }
})
await step.run("send-report", async () => {
await sendEmail({
to: "team@example.com",
template: "daily_report",
data: report
})
})
}
)
// Check for expiring subscriptions weekly
export const checkSubscriptions = inngest.createFunction(
{ id: "check-subscriptions" },
{ cron: "0 10 * * 1" }, // Every Monday at 10 AM
async ({ step }) => {
const expiring = await step.run("find-expiring", async () => {
return await findExpiringSubscriptions(7) // Next 7 days
})
// Fan out to send reminders
await Promise.all(
expiring.map((sub, i) =>
step.run(`send-reminder-${i}`, async () => {
await sendRenewalReminder(sub)
})
)
)
}
)
// Cleanup old data monthly
export const monthlyCleanup = inngest.createFunction(
{ id: "monthly-cleanup" },
{ cron: "0 3 1 * *" }, // 1st of each month at 3 AM
async ({ step }) => {
await step.run("cleanup-logs", async () => {
await deleteOldLogs(90) // Older than 90 days
})
await step.run("cleanup-sessions", async () => {
await deleteExpiredSessions()
})
await step.run("archive-bookings", async () => {
await archiveOldBookings(365) // Older than 1 year
})
}
)Inngest provides a visual dashboard for monitoring all your functions.
┌─────────────────────────────────────────────────────────────────┐ │ INNGEST DASHBOARD │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Functions Recent Runs │ │ ─────────────────── ───────────────────────────── │ │ ✓ process-booking 32/hr │ ✓ evt_abc123 2.3s success │ │ │ ✓ send-reminder 8/hr │ ✓ evt_def456 1.1s success │ │ │ ⚠ sync-calendar 5/hr │ ⚠ evt_ghi789 4.2s retry │ │ │ ✓ daily-report 1/day │ ✗ evt_jkl012 0.5s failed │ │ │ │ │ Function: process-booking │ │ ───────────────────────────────────────────────────────────── │ │ Status: Running │ │ Event: booking.created │ │ Started: 2024-03-10 14:30:00 │ │ │ │ Steps: │ │ ├─ ✓ validate-booking 142ms │ │ ├─ ✓ check-availability 89ms │ │ ├─ ✓ process-payment 2.1s │ │ ├─ ● confirm-booking running... │ │ ├─ ○ notify-guest pending │ │ └─ ○ notify-host pending │ │ │ └─────────────────────────────────────────────────────────────────┘
Run npx inngest-cli dev to start the local Inngest Dev Server. Access the dashboard at http://localhost:8288.