Build reactive, scalable systems that respond to changes in real-time. Events are the foundation of modern property management, marketplace, and automation platforms.
Event-driven architecture enables your system to react instantly to changes, scale independently, and maintain loose coupling between services. This is essential for applications like Uber, Airbnb, and property management systems.
Respond instantly to booking requests, maintenance alerts, price changes, and user actions.
Services communicate through events, not direct calls. Change one service without breaking others.
Handle traffic spikes by scaling event consumers independently. Process millions of events reliably.
Complete audit trail of everything that happened. Replay events to rebuild state or debug issues.
┌─────────────────────────────────────────────────────────────────────┐
│ EVENT PRODUCERS │
├──────────────┬──────────────┬──────────────┬───────────────────────┤
│ User │ External │ Scheduled │ System │
│ Actions │ Webhooks │ Jobs │ Events │
└──────┬───────┴──────┬───────┴──────┬───────┴───────────┬───────────┘
│ │ │ │
└──────────────┴──────────────┴───────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ EVENT BUS (Inngest) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Queue │ │ Routing │ │ Replay │ │
│ │ Storage │ │ Rules │ │ History │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└──────────────────────┬───────────────────────────┘
│
┌──────────────────────┴───────────────────────────┐
│ EVENT CONSUMERS │
├──────────────┬──────────────┬───────────────────┤
│ Workflow │ Agent │ Notification │
│ Engine │ Executor │ Service │
└──────────────┴──────────────┴───────────────────┘Business-level events that represent significant occurrences in your domain.
booking.created, property.listed, payment.completed, review.submittedEvents from external services and third-party integrations.
stripe.payment_intent.succeeded, calendar.sync.completed, sms.deliveredTime-based events triggered by cron jobs or delayed execution.
daily.report.generate, booking.reminder.send, subscription.renewal.checkInternal system events for monitoring, alerts, and infrastructure.
agent.error.occurred, workflow.timeout.exceeded, rate.limit.reachedAll events follow a consistent structure for reliable processing and tracing:
interface Event<T = unknown> {
// Unique event identifier
id: string
// Event type (e.g., "booking.created")
name: string
// Event payload with business data
data: T
// Metadata for tracing and routing
metadata: {
// Correlation ID for distributed tracing
correlationId: string
// User who triggered the event (if applicable)
userId?: string
// Organization context
organizationId?: string
// Source service/component
source: string
// Event version for schema evolution
version: string
// ISO timestamp
timestamp: string
// Optional: parent event for chains
parentEventId?: string
}
}
// Example: Booking Created Event
const bookingCreatedEvent: Event<BookingData> = {
id: "evt_abc123",
name: "booking.created",
data: {
bookingId: "bk_xyz789",
propertyId: "prop_456",
guestId: "user_123",
checkIn: "2024-03-15",
checkOut: "2024-03-20",
totalAmount: 75000, // cents
status: "pending"
},
metadata: {
correlationId: "corr_def456",
userId: "user_123",
organizationId: "org_789",
source: "booking-service",
version: "1.0",
timestamp: "2024-03-10T14:30:00Z"
}
}import { inngest } from "@/lib/inngest/client"
// Publish a single event
await inngest.send({
name: "booking.created",
data: {
bookingId: booking.id,
propertyId: booking.propertyId,
guestId: booking.guestId,
checkIn: booking.checkIn,
checkOut: booking.checkOut,
totalAmount: booking.totalAmount
}
})
// Publish multiple events (batch)
await inngest.send([
{
name: "booking.created",
data: { bookingId: "bk_1", ... }
},
{
name: "notification.send",
data: { type: "booking_confirmation", ... }
},
{
name: "calendar.sync",
data: { propertyId: "prop_1", ... }
}
])
// Publish with user context
await inngest.send({
name: "property.updated",
data: { propertyId: "prop_123", changes: {...} },
user: { id: currentUser.id }
})import { inngest } from "@/lib/inngest/client"
// Simple event handler
export const handleBookingCreated = inngest.createFunction(
{ id: "handle-booking-created" },
{ event: "booking.created" },
async ({ event, step }) => {
const { bookingId, propertyId, guestId } = event.data
// Step 1: Send confirmation email
await step.run("send-confirmation", async () => {
await sendEmail({
to: guestId,
template: "booking_confirmation",
data: { bookingId }
})
})
// Step 2: Block calendar dates
await step.run("block-calendar", async () => {
await blockCalendarDates(propertyId, event.data)
})
// Step 3: Notify property owner
await step.run("notify-owner", async () => {
await notifyOwner(propertyId, bookingId)
})
return { success: true, bookingId }
}
)
// Event handler with multiple triggers
export const syncCalendar = inngest.createFunction(
{ id: "sync-calendar" },
[
{ event: "booking.created" },
{ event: "booking.cancelled" },
{ event: "booking.modified" }
],
async ({ event, step }) => {
// Handle any booking change
await step.run("sync", async () => {
await syncPropertyCalendar(event.data.propertyId)
})
}
)Here's how events power a complete booking flow, similar to Airbnb:
Guest submits booking request
│
▼
┌───────────────────────────────────────┐
│ Event: booking.requested │
│ Triggers: │
│ ├── Availability check │
│ ├── Price calculation │
│ └── Fraud detection │
└───────────────────┬───────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Available │ │ Unavailable │
│ │ │ │
│ Event: │ │ Event: │
│ booking. │ │ booking. │
│ available │ │ declined │
└───────┬───────┘ └───────────────┘
│
▼
┌───────────────────────────────────────┐
│ Host Approval (HITL Gate) │
│ - Auto-approve if instant book │
│ - Wait for host if manual │
└───────────────────┬───────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Approved │ │ Rejected │
│ │ │ │
│ Event: │ │ Event: │
│ booking. │ │ booking. │
│ approved │ │ rejected │
└───────┬───────┘ └───────────────┘
│
▼
┌───────────────────────────────────────┐
│ Event: payment.requested │
│ Triggers: │
│ ├── Stripe payment intent │
│ └── Payment timeout watchdog │
└───────────────────┬───────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Payment OK │ │ Payment Fail │
│ │ │ │
│ Event: │ │ Event: │
│ payment. │ │ payment. │
│ completed │ │ failed │
└───────┬───────┘ └───────────────┘
│
▼
┌───────────────────────────────────────┐
│ Event: booking.confirmed │
│ Triggers (parallel): │
│ ├── Guest confirmation email │
│ ├── Host notification │
│ ├── Calendar sync │
│ ├── Revenue recording │
│ ├── Review reminder scheduled │
│ └── Check-in instructions scheduled │
└───────────────────────────────────────┘Follow the pattern domain.action.status(e.g., booking.payment.completed)
Always pass correlation IDs through event chains for distributed tracing
Events may be delivered more than once. Design handlers to be idempotent
Include version in metadata to handle schema evolution gracefully