Inngest turns events into reliable, durable functions — no queues, no workers, no infrastructure.
Define a Function
import { inngest } from './client'
export const sendWelcomeEmail = inngest.createFunction(
{ id: 'send-welcome-email' },
{ event: 'user/signed.up' },
async ({ event, step }) => {
// Step 1: Wait 1 hour
await step.sleep('wait-before-email', '1h')
// Step 2: Send email (auto-retried on failure)
await step.run('send-email', async () => {
await resend.emails.send({
to: event.data.email,
subject: 'Welcome!',
html: '<h1>Welcome aboard!</h1>'
})
})
// Step 3: Update user record
await step.run('update-user', async () => {
await db.user.update({
where: { id: event.data.userId },
data: { welcomeEmailSent: true }
})
})
}
)
Send Events
import { inngest } from './client'
// From your API route
export async function POST(request) {
const user = await createUser(request)
// Send event — triggers all matching functions
await inngest.send({
name: 'user/signed.up',
data: { userId: user.id, email: user.email }
})
return Response.json(user)
}
Step Functions — Durable Execution
export const processOrder = inngest.createFunction(
{ id: 'process-order', retries: 5 },
{ event: 'order/created' },
async ({ event, step }) => {
// Each step is independently retried
const payment = await step.run('charge-payment', async () => {
return stripe.charges.create({ amount: event.data.total })
})
// Wait for external event (webhook from shipping provider)
const shipment = await step.waitForEvent('wait-for-shipment', {
event: 'shipment/created',
match: 'data.orderId',
timeout: '7d'
})
// Continue after shipment confirmed
await step.run('send-tracking', async () => {
await sendTrackingEmail(event.data.email, shipment.data.trackingNumber)
})
}
)
Fan-out Pattern
export const batchProcess = inngest.createFunction(
{ id: 'batch-process' },
{ event: 'batch/uploaded' },
async ({ event, step }) => {
const items = await step.run('parse-file', () => parseCSV(event.data.fileUrl))
// Fan-out: send an event for each item
await step.sendEvent('fan-out',
items.map(item => ({
name: 'item/process',
data: item
}))
)
}
)
Real-World Use Case
An e-commerce platform had a brittle order pipeline: if email sending failed, the whole order processing crashed. With Inngest, each step retries independently. If Stripe is down, it retries charging. If the email service fails, the order is still processed. They went from 5% failed orders to 0.01%.
Inngest makes complex workflows feel like writing a simple function.
Build Smarter Data Pipelines
Need to scrape websites, extract APIs, or automate data collection? Check out my ready-to-use scrapers on Apify — no coding required.
Custom scraping solution? Email me at spinov001@gmail.com — fast turnaround, fair prices.
Top comments (0)