Configure when and how your workflows execute. From cron schedules to conditional triggers and delayed execution.
Execute when specific events occur (booking.created, payment.completed)
Execute on a schedule (daily reports, weekly cleanups)
Execute after a time delay (reminders, follow-ups)
Execute only when conditions are met (if amount > $1000)
// Single event trigger
export const onBookingCreated = inngest.createFunction(
{ id: "on-booking-created" },
{ event: "booking.created" },
async ({ event, step }) => {
// Runs every time booking.created is sent
}
)
// Multiple event triggers
export const syncCalendar = inngest.createFunction(
{ id: "sync-calendar" },
[
{ event: "booking.created" },
{ event: "booking.cancelled" },
{ event: "booking.modified" }
],
async ({ event, step }) => {
// Runs for any of these events
console.log(`Handling ${event.name}`)
}
)
// Event with match expression (filter)
export const highValueBooking = inngest.createFunction(
{ id: "high-value-booking" },
{
event: "booking.created",
if: "event.data.totalAmount > 100000" // > $1000 (cents)
},
async ({ event, step }) => {
// Only runs for bookings over $1000
await notifyVIPTeam(event.data)
}
)
// Event with batch processing
export const batchProcessor = inngest.createFunction(
{
id: "batch-processor",
batchEvents: {
maxSize: 100, // Process up to 100 events at once
timeout: "5s" // Or after 5 seconds, whichever comes first
}
},
{ event: "analytics.track" },
async ({ events, step }) => {
// events is an array of up to 100 events
await step.run("batch-insert", async () => {
await insertAnalyticsBatch(events.map(e => e.data))
})
}
)| Pattern | Description | Example Use Case |
|---|---|---|
| 0 9 * * * | Daily at 9:00 AM | Morning reports |
| 0 */4 * * * | Every 4 hours | Data sync |
| 0 10 * * 1 | Monday at 10:00 AM | Weekly digest |
| 0 0 1 * * | 1st of month at midnight | Monthly billing |
| */15 * * * * | Every 15 minutes | Health checks |
| 0 0 * * 0 | Sunday at midnight | Weekly cleanup |
// Daily morning report
export const morningReport = inngest.createFunction(
{ id: "morning-report" },
{ cron: "0 9 * * *" }, // 9 AM every day
async ({ step }) => {
const stats = await step.run("gather-stats", async () => {
return {
bookings: await getYesterdaysBookings(),
revenue: await getYesterdaysRevenue(),
occupancy: await getCurrentOccupancy()
}
})
await step.run("send-report", async () => {
await sendEmail({
to: "team@company.com",
template: "daily_report",
data: stats
})
})
}
)
// Hourly availability sync
export const syncAvailability = inngest.createFunction(
{ id: "sync-availability" },
{ cron: "0 * * * *" }, // Every hour
async ({ step }) => {
const properties = await step.run("get-properties", async () => {
return await getActiveProperties()
})
// Process in parallel batches
const batchSize = 50
for (let i = 0; i < properties.length; i += batchSize) {
const batch = properties.slice(i, i + batchSize)
await Promise.all(
batch.map((property, idx) =>
step.run(`sync-${i + idx}`, async () => {
await syncPropertyCalendar(property.id)
})
)
)
}
}
)
// Monthly cleanup with reporting
export const monthlyMaintenance = inngest.createFunction(
{ id: "monthly-maintenance" },
{ cron: "0 2 1 * *" }, // 2 AM on the 1st of each month
async ({ step }) => {
const results = {
logsDeleted: 0,
sessionsCleared: 0,
archivesMoved: 0
}
results.logsDeleted = await step.run("cleanup-logs", async () => {
return await deleteOldLogs(90) // Older than 90 days
})
results.sessionsCleared = await step.run("clear-sessions", async () => {
return await clearExpiredSessions()
})
results.archivesMoved = await step.run("archive-old-data", async () => {
return await archiveOldBookings(365) // Older than 1 year
})
await step.run("send-maintenance-report", async () => {
await sendEmail({
to: "ops@company.com",
template: "maintenance_report",
data: results
})
})
return results
}
)// Send reminder before check-in
export const checkInReminder = inngest.createFunction(
{ id: "check-in-reminder" },
{ event: "booking.confirmed" },
async ({ event, step }) => {
const checkInDate = new Date(event.data.checkIn)
const reminderDate = new Date(checkInDate)
reminderDate.setDate(reminderDate.getDate() - 1) // 1 day before
// Sleep until reminder time (no compute used)
await step.sleepUntil("wait-until-reminder", reminderDate)
// Check if booking is still active
const booking = await step.run("check-booking", async () => {
return await getBooking(event.data.bookingId)
})
if (booking.status === "cancelled") {
return { skipped: true, reason: "booking_cancelled" }
}
// Send the reminder
await step.run("send-reminder", async () => {
await sendEmail({
to: booking.guestEmail,
template: "check_in_reminder",
data: {
propertyName: booking.propertyName,
checkInTime: booking.checkInTime,
address: booking.propertyAddress
}
})
})
}
)
// Follow-up after stay
export const requestReview = inngest.createFunction(
{ id: "request-review" },
{ event: "booking.completed" },
async ({ event, step }) => {
// Wait 2 days after checkout
await step.sleep("wait-for-review-window", "2d")
// Check if guest already left a review
const hasReview = await step.run("check-review", async () => {
return await checkExistingReview(event.data.bookingId)
})
if (hasReview) {
return { skipped: true, reason: "already_reviewed" }
}
// Send review request
await step.run("send-review-request", async () => {
await sendEmail({
to: event.data.guestEmail,
template: "review_request",
data: {
bookingId: event.data.bookingId,
propertyName: event.data.propertyName,
reviewLink: `/review/${event.data.bookingId}`
}
})
})
// Wait 3 more days for follow-up
await step.sleep("wait-for-followup", "3d")
// Check again
const hasReviewNow = await step.run("recheck-review", async () => {
return await checkExistingReview(event.data.bookingId)
})
if (!hasReviewNow) {
await step.run("send-followup", async () => {
await sendEmail({
to: event.data.guestEmail,
template: "review_reminder",
data: { bookingId: event.data.bookingId }
})
})
}
}
)
// Expiration handling
export const handleExpiration = inngest.createFunction(
{ id: "handle-pending-expiration" },
{ event: "booking.pending" },
async ({ event, step }) => {
// Wait for payment (with timeout)
const paymentEvent = await step.waitForEvent("wait-for-payment", {
event: "payment.completed",
match: "data.bookingId",
timeout: "24h"
})
if (!paymentEvent) {
// Payment not received, expire the booking
await step.run("expire-booking", async () => {
await updateBookingStatus(event.data.bookingId, "expired")
await releaseHeldDates(event.data.propertyId, event.data.checkIn, event.data.checkOut)
await sendEmail({
to: event.data.guestEmail,
template: "booking_expired",
data: { bookingId: event.data.bookingId }
})
})
return { status: "expired" }
}
return { status: "paid", paymentId: paymentEvent.data.paymentId }
}
)// Trigger only for high-value bookings
export const vipBookingHandler = inngest.createFunction(
{ id: "vip-booking-handler" },
{
event: "booking.created",
if: "event.data.totalAmount > 50000" // > $500
},
async ({ event, step }) => {
await step.run("assign-concierge", async () => {
await assignConcierge(event.data.bookingId)
})
await step.run("send-vip-welcome", async () => {
await sendVIPWelcomePackage(event.data.guestId)
})
}
)
// Trigger based on user tier
export const tierBasedNotification = inngest.createFunction(
{ id: "tier-notification" },
{
event: "user.action",
if: "event.data.userTier == 'premium' || event.data.userTier == 'enterprise'"
},
async ({ event, step }) => {
// Only for premium and enterprise users
}
)
// Complex conditions
export const complexCondition = inngest.createFunction(
{ id: "complex-condition" },
{
event: "order.placed",
if: "(event.data.amount > 10000 && event.data.isFirstOrder) || event.data.priority == 'urgent'"
},
async ({ event, step }) => {
// Runs for:
// - First orders over $100
// - Any urgent orders
}
)
// Dynamic routing based on event data
export const dynamicRouter = inngest.createFunction(
{ id: "dynamic-router" },
{ event: "task.created" },
async ({ event, step }) => {
const { priority, category, assignee } = event.data
// Route based on priority
if (priority === "critical") {
await step.run("handle-critical", async () => {
await escalateToOnCall(event.data)
await sendPagerAlert(event.data)
})
} else if (priority === "high") {
await step.run("handle-high", async () => {
await notifyTeamLead(event.data)
})
}
// Category-specific handling
switch (category) {
case "maintenance":
await step.run("route-maintenance", async () => {
await assignToMaintenanceTeam(event.data)
})
break
case "billing":
await step.run("route-billing", async () => {
await assignToFinanceTeam(event.data)
})
break
default:
await step.run("route-general", async () => {
await assignToGeneralQueue(event.data)
})
}
}
)booking.payment.completed is better than update
Use if conditions to avoid processing unnecessary events
Always set timeouts on waitForEvent to prevent hanging functions
After sleeping/waiting, verify the entity still exists and is in the expected state
Use event triggers for immediate processing; cron for batch/periodic tasks only