← InngestCONTENT HISTORY
WHAT CHANGED
Update to Inngest
Snapshot Oct 24, 2026 · 13:41 UTC · version 0.4.1
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Inngest and check future changes in Following.
Up to 10 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use when designing event-driven workflows, decoupling services, implementing fan-out patterns (one trigger, many downstream handlers), implementing idempotent event handling with IDs (24-hour dedupe window), or handling at-least-once delivery from external sources like Stripe webhooks. Covers Inngest event schema, payload format, naming conventions, IDs for idempotency, the ts param, fan-out patterns, and system events like inngest/function.failed.",
"included_files": [],
"name": "inngest-events",
"skill_md_contents": "---\nname: inngest-events\ndescription: Use when designing event-driven workflows, decoupling services, implementing fan-out patterns (one trigger, many downstream handlers), implementing idempotent event handling with IDs (24-hour dedupe window), or handling at-least-once delivery from external sources like Stripe webhooks. Covers Inngest event schema, payload format, naming conventions, IDs for idempotency, the ts param, fan-out patterns, and system events like inngest/function.failed.\n---\n\n# Inngest Events\n\nMaster Inngest event design and delivery patterns. Events are the foundation of Inngest - learn to design robust event schemas, implement idempotency, leverage fan-out patterns, and handle system events effectively.\n\n> **These skills are focused on TypeScript.** For Python or Go, refer to the [Inngest documentation](https://www.inngest.com/llms.txt) for language-specific guidance. Core concepts apply across all languages.\n\n## Event Payload Format\n\nEvery Inngest event is a JSON object with required and optional properties:\n\n### Required Properties\n\n```typescript\ntype Event = {\n name: string; // Event type (triggers functions)\n data: object; // Payload data (any nested JSON)\n};\n```\n\n### Complete Schema\n\n```typescript\ntype EventPayload = {\n name: string; // Required: event type\n data: Record<string, any>; // Required: event data\n id?: string; // Optional: deduplication ID\n ts?: number; // Optional: timestamp (Unix ms)\n v?: string; // Optional: schema version\n};\n```\n\n### Basic Event Example\n\n```typescript\nawait inngest.send({\n name: \"billing/invoice.paid\",\n data: {\n customerId: \"cus_NffrFeUfNV2Hib\",\n invoiceId: \"in_1J5g2n2eZvKYlo2C0Z1Z2Z3Z\",\n userId: \"user_03028hf09j2d02\",\n amount: 1000,\n metadata: {\n accountId: \"acct_1J5g2n2eZvKYlo2C0Z1Z2Z3Z\",\n accountName: \"Acme.ai\"\n }\n }\n});\n```\n\n## Event Naming Conventions\n\n**Use the Object-Action pattern:** `domain/noun.verb`\n\n### Recommended Patterns\n\n```typescript\n// ✅ Good: Clear object-action pattern\n\"billing/invoice.paid\";\n\"user/profile.updated\";\n\"order/item.shipped\";\n\"ai/summary.completed\";\n\n// ✅ Good: Domain prefixes for organization\n\"stripe/customer.created\";\n\"intercom/conversation.assigned\";\n\"slack/message.posted\";\n\n// ❌ Avoid: Unclear or inconsistent\n\"payment\"; // What happened?\n\"user_update\"; // Use dots, not underscores\n\"invoiceWasPaid\"; // Too verbose\n```\n\n### Naming Guidelines\n\n- **Past tense:** Events describe what happened (`created`, `updated`, `failed`)\n- **Dot notation:** Use dots for hierarchy (`billing/invoice.paid`)\n- **Prefixes:** Group related events (`api/user.created`, `webhook/stripe.received`)\n- **Consistency:** Establish patterns and stick to them\n\n## Event IDs and Idempotency\n\n**When to use IDs:** Prevent duplicate processing when events might be sent multiple times.\n\n### Basic Deduplication\n\n```typescript\nawait inngest.send({\n id: \"cart-checkout-completed-ed12c8bde\", // Unique per event type\n name: \"storefront/cart.checkout.completed\",\n data: {\n cartId: \"ed12c8bde\",\n items: [\"item1\", \"item2\"]\n }\n});\n```\n\n### ID Best Practices\n\n```typescript\n// ✅ Good: Specific to event type and instance\nid: `invoice-paid-${invoiceId}`;\nid: `user-signup-${userId}-${timestamp}`;\nid: `order-shipped-${orderId}-${trackingNumber}`;\n\n// ❌ Bad: Generic IDs shared across event types\nid: invoiceId; // Could conflict with other events\nid: \"user-action\"; // Too generic\nid: customerId; // Same customer, different events\n```\n\n**Deduplication window:** 24 hours from first event reception\n\nSee **inngest-durable-functions** for idempotency configuration.\n\n## The `ts` Parameter for Delayed Delivery\n\n**When to use:** Schedule events for future processing or maintain event ordering.\n\n### Future Scheduling\n\n```typescript\nconst oneHourFromNow = Date.now() + 60 * 60 * 1000;\n\nawait inngest.send({\n name: \"trial/reminder.send\",\n ts: oneHourFromNow, // Deliver in 1 hour\n data: {\n userId: \"user_123\",\n trialExpiresAt: \"2024-02-15T12:00:00Z\"\n }\n});\n```\n\n### Maintaining Event Order\n\n```typescript\n// Events with timestamps are processed in chronological order\nconst events = [\n {\n name: \"user/action.performed\",\n ts: 1640995200000, // Earlier\n data: { action: \"login\" }\n },\n {\n name: \"user/action.performed\",\n ts: 1640995260000, // Later\n data: { action: \"purchase\" }\n }\n];\n\nawait inngest.send(events);\n```\n\n## Fan-Out Patterns\n\n**Use case:** One event triggers multiple independent functions for reliability and parallel processing.\n\n### Basic Fan-Out Implementation\n\n```typescript\n// Send single event\nawait inngest.send({\n name: \"user/signup.completed\",\n data: {\n userId: \"user_123\",\n email: \"user@example.com\",\n plan: \"pro\"\n }\n});\n\n// Multiple functions respond to same event\nconst sendWelcomeEmail = inngest.createFunction(\n { id: \"send-welcome-email\", triggers: [{ event: \"user/signup.completed\" }] },\n async ({ event, step }) => {\n await step.run(\"send-email\", async () => {\n return sendEmail({\n to: event.data.email,\n template: \"welcome\"\n });\n });\n }\n);\n\nconst createTrialSubscription = inngest.createFunction(\n { id: \"create-trial\", triggers: [{ event: \"user/signup.completed\" }] },\n async ({ event, step }) => {\n await step.run(\"create-subscription\", async () => {\n return stripe.subscriptions.create({\n customer: event.data.stripeCustomerId,\n trial_period_days: 14\n });\n });\n }\n);\n\nconst addToCrm = inngest.createFunction(\n { id: \"add-to-crm\", triggers: [{ event: \"user/signup.completed\" }] },\n async ({ event, step }) => {\n await step.run(\"crm-sync\", async () => {\n return crm.contacts.create({\n email: event.data.email,\n plan: event.data.plan\n });\n });\n }\n);\n```\n\n### Fan-Out Benefits\n\n- **Independence:** Functions run separately; one failure doesn't affect others\n- **Parallel execution:** All functions run simultaneously\n- **Selective replay:** Re-run only failed functions\n- **Cross-service:** Trigger functions in different codebases/languages\n\n### Advanced Fan-Out with `waitForEvent`\n\nIn expressions, `event` = the **original** triggering event, `async` = the **new** event being matched. See [Expression Syntax Reference](../references/expressions.md) for full details.\n\n```typescript\nconst orchestrateOnboarding = inngest.createFunction(\n { id: \"orchestrate-onboarding\", triggers: [{ event: \"user/signup.completed\" }] },\n async ({ event, step }) => {\n // Fan out to multiple services\n await step.sendEvent(\"fan-out\", [\n { name: \"email/welcome.send\", data: event.data },\n { name: \"subscription/trial.create\", data: event.data },\n { name: \"crm/contact.add\", data: event.data }\n ]);\n\n // Wait for all to complete\n const [emailResult, subResult, crmResult] = await Promise.all([\n step.waitForEvent(\"email-sent\", {\n event: \"email/welcome.sent\",\n timeout: \"5m\",\n if: `event.data.userId == async.data.userId`\n }),\n step.waitForEvent(\"subscription-created\", {\n event: \"subscription/trial.created\",\n timeout: \"5m\",\n if: `event.data.userId == async.data.userId`\n }),\n step.waitForEvent(\"crm-synced\", {\n event: \"crm/contact.added\",\n timeout: \"5m\",\n if: `event.data.userId == async.data.userId`\n })\n ]);\n\n // Complete onboarding\n await step.run(\"complete-onboarding\", async () => {\n return completeUserOnboarding(event.data.userId);\n });\n }\n);\n```\n\nSee **inngest-steps** for additional patterns including `step.invoke`.\n\n## System Events\n\nInngest emits system events for function lifecycle monitoring:\n\n### Available System Events\n\n```typescript\n// Function execution events\n\"inngest/function.failed\"; // Function failed after retries\n\"inngest/function.finished\"; // Function finished - completed or failed\n\"inngest/function.cancelled\"; // Function cancelled before completion\n```\n\n### Handling Failed Functions\n\n```typescript\nconst handleFailures = inngest.createFunction(\n { id: \"handle-failed-functions\", triggers: [{ event: \"inngest/function.failed\" }] },\n async ({ event, step }) => {\n const { function_id, run_id, error } = event.data;\n\n await step.run(\"log-failure\", async () => {\n logger.error(\"Function failed\", {\n functionId: function_id,\n runId: run_id,\n error: error.message,\n stack: error.stack\n });\n });\n\n // Alert on critical function failures\n if (function_id.includes(\"critical\")) {\n await step.run(\"send-alert\", async () => {\n return alerting.sendAlert({\n title: `Critical function failed: ${function_id}`,\n severity: \"high\",\n runId: run_id\n });\n });\n }\n\n // Auto-retry certain failures\n if (error.code === \"RATE_LIMIT_EXCEEDED\") {\n await step.run(\"schedule-retry\", async () => {\n return inngest.send({\n name: \"retry/function.requested\",\n ts: Date.now() + 5 * 60 * 1000, // Retry in 5 minutes\n data: { originalRunId: run_id }\n });\n });\n }\n }\n);\n```\n\n## Sending Events\n\n### Client Setup\n\n```typescript\n// inngest/client.ts\nimport { Inngest } from \"inngest\";\n\nexport const inngest = new Inngest({\n id: \"my-app\"\n});\n// You must set INNGEST_EVENT_KEY environment variable in production\n```\n\n### Single Event\n\n```typescript\nconst result = await inngest.send({\n name: \"order/placed\",\n data: {\n orderId: \"ord_123\",\n customerId: \"cus_456\",\n amount: 2500,\n items: [\n { id: \"item_1\", quantity: 2 },\n { id: \"item_2\", quantity: 1 }\n ]\n }\n});\n\n// Returns event IDs for tracking\nconsole.log(result.ids); // [\"01HQ8PTAESBZPBDS8JTRZZYY3S\"]\n```\n\n### Batch Events\n\n```typescript\nconst orderItems = await getOrderItems(orderId);\n\n// Convert to events\nconst events = orderItems.map((item) => ({\n name: \"inventory/item.reserved\",\n data: {\n itemId: item.id,\n orderId: orderId,\n quantity: item.quantity,\n warehouseId: item.warehouseId\n }\n}));\n\n// Send all at once (up to 512kb)\nawait inngest.send(events);\n```\n\n### Sending from Functions\n\n```typescript\ninngest.createFunction(\n { id: \"process-order\", triggers: [{ event: \"order/placed\" }] },\n async ({ event, step }) => {\n // Use step.sendEvent() instead of inngest.send() in functions\n // for reliability and deduplication\n await step.sendEvent(\"trigger-fulfillment\", {\n name: \"fulfillment/order.received\",\n data: {\n orderId: event.data.orderId,\n priority: event.data.customerTier === \"premium\" ? \"high\" : \"normal\"\n }\n });\n }\n);\n```\n\n## Event Design Best Practices\n\n### Schema Versioning\n\n```typescript\n// Use version field to track schema changes\nawait inngest.send({\n name: \"user/profile.updated\",\n v: \"2024-01-15.1\", // Schema version\n data: {\n userId: \"user_123\",\n changes: {\n email: \"new@example.com\",\n preferences: { theme: \"dark\" }\n },\n // New field in v2 schema\n auditInfo: {\n changedBy: \"user_456\",\n reason: \"user_requested\"\n }\n }\n});\n```\n\n### Rich Context Data\n\n```typescript\n// Include enough context for all consumers\nawait inngest.send({\n name: \"payment/charge.succeeded\",\n data: {\n // Primary identifiers\n chargeId: \"ch_123\",\n customerId: \"cus_456\",\n\n // Amount details\n amount: 2500,\n currency: \"usd\",\n\n // Context for different consumers\n subscription: {\n id: \"sub_789\",\n plan: \"pro_monthly\"\n },\n invoice: {\n id: \"inv_012\",\n number: \"INV-2024-001\"\n },\n\n // Metadata for debugging\n paymentMethod: {\n type: \"card\",\n last4: \"4242\",\n brand: \"visa\"\n },\n metadata: {\n source: \"stripe_webhook\",\n environment: \"production\"\n }\n }\n});\n```\n\n**Event design principles:**\n\n1. **Self-contained:** Include all data consumers need\n2. **Immutable:** Never modify event schemas after sending\n3. **Traceable:** Include correlation IDs and audit trails\n4. **Actionable:** Provide enough context for business logic\n5. **Debuggable:** Include metadata for troubleshooting\n"
}SHA-256 of public snapshot: 9f1279a2bcbc62db656350d195c31aa4e7c8638c79835eb72edc866b8392f120