Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
inngest avatar

Inngest Events

  • 3.1k installs
  • 27 repo stars
  • Updated July 2, 2026
  • inngest/inngest-skills

inngest-events is an agent skill that designs Inngest event schemas, idempotent IDs, fan-out patterns, and delayed delivery for TypeScript event-driven workflows.

About

inngest-events is an agent skill for designing robust Inngest event payloads, naming conventions, idempotency, fan-out patterns, and system events in TypeScript workflows. It defines the required name and data fields plus optional id, ts, and v schema version properties for every event. Naming guidance enforces domain slash noun dot verb patterns such as billing slash invoice dot paid with past-tense actions and consistent prefixes. Event IDs prevent duplicate processing within a 24-hour deduplication window, with examples tying IDs to invoice, user signup, or order shipment instances. The ts parameter schedules future delivery or preserves chronological ordering across batched sends. Fan-out patterns show one signup event triggering independent welcome email, trial subscription, and CRM sync functions for parallel reliability. System events such as inngest slash function dot failed are documented for failure handling. Developers reach for inngest-events when decoupling services, implementing Stripe-style webhook idempotency, or planning one-trigger many-handler architectures instead of monolithic synchronous handlers.

  • Documents required name and data fields plus optional id, ts, and v on every Inngest event.
  • Naming uses object-action patterns like billing/invoice.paid with past-tense domain prefixes.
  • Event IDs enable 24-hour deduplication for at-least-once delivery from webhooks or retries.
  • ts schedules future delivery or maintains chronological order across batched event sends.
  • Fan-out examples trigger separate welcome, subscription, and CRM functions from one signup event.

Inngest Events by the numbers

  • 3,124 all-time installs (skills.sh)
  • +59 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #184 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Security screen: MEDIUM risk (skills.sh audit)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

inngest-events capabilities & compatibility

Capabilities
event schema design · idempotent event id patterns · fan out multi function triggers · delayed and ordered event delivery
Works with
stripe
Use cases
api development · orchestration
From the docs

What inngest-events says it does

One event triggers multiple independent functions for reliability and parallel processing
SKILL.md
npx skills add https://github.com/inngest/inngest-skills --skill inngest-events

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs3.1k
repo stars27
Security audit2 / 3 scanners passed
Last updatedJuly 2, 2026
Repositoryinngest/inngest-skills

How do I design Inngest events with correct naming, idempotency, and fan-out so duplicate webhooks do not double-process work?

Design Inngest event schemas with object-action naming, idempotent IDs, fan-out triggers, and delayed delivery using ts scheduling.

Who is it for?

TypeScript developers building event-driven backends with Inngest who need schema, idempotency, and fan-out guidance.

Skip if: Skip when the stack is not Inngest or the task is generic REST CRUD without event delivery semantics.

When should I use this skill?

User designs Inngest events, implements fan-out from one trigger, adds idempotency IDs, or handles function.failed system events.

What you get

Well-formed Inngest events with domain-action names, deduplication IDs, and independent downstream function triggers.

  • Event payload definitions
  • Idempotency ID conventions
  • Fan-out function trigger map

By the numbers

  • 24-hour event ID deduplication window

Files

SKILL.mdMarkdownGitHub ↗

Inngest Events

Master 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.

These skills are focused on TypeScript. For Python or Go, refer to the Inngest documentation for language-specific guidance. Core concepts apply across all languages.

Event Payload Format

Every Inngest event is a JSON object with required and optional properties:

Required Properties

type Event = {
  name: string; // Event type (triggers functions)
  data: object; // Payload data (any nested JSON)
};

Complete Schema

type EventPayload = {
  name: string; // Required: event type
  data: Record<string, any>; // Required: event data
  id?: string; // Optional: deduplication ID
  ts?: number; // Optional: timestamp (Unix ms)
  v?: string; // Optional: schema version
};

Basic Event Example

await inngest.send({
  name: "billing/invoice.paid",
  data: {
    customerId: "cus_NffrFeUfNV2Hib",
    invoiceId: "in_1J5g2n2eZvKYlo2C0Z1Z2Z3Z",
    userId: "user_03028hf09j2d02",
    amount: 1000,
    metadata: {
      accountId: "acct_1J5g2n2eZvKYlo2C0Z1Z2Z3Z",
      accountName: "Acme.ai"
    }
  }
});

Event Naming Conventions

Use the Object-Action pattern: domain/noun.verb

Recommended Patterns

// ✅ Good: Clear object-action pattern
"billing/invoice.paid";
"user/profile.updated";
"order/item.shipped";
"ai/summary.completed";

// ✅ Good: Domain prefixes for organization
"stripe/customer.created";
"intercom/conversation.assigned";
"slack/message.posted";

// ❌ Avoid: Unclear or inconsistent
"payment"; // What happened?
"user_update"; // Use dots, not underscores
"invoiceWasPaid"; // Too verbose

Naming Guidelines

  • Past tense: Events describe what happened (created, updated, failed)
  • Dot notation: Use dots for hierarchy (billing/invoice.paid)
  • Prefixes: Group related events (api/user.created, webhook/stripe.received)
  • Consistency: Establish patterns and stick to them

Event IDs and Idempotency

When to use IDs: Prevent duplicate processing when events might be sent multiple times.

Basic Deduplication

await inngest.send({
  id: "cart-checkout-completed-ed12c8bde", // Unique per event type
  name: "storefront/cart.checkout.completed",
  data: {
    cartId: "ed12c8bde",
    items: ["item1", "item2"]
  }
});

ID Best Practices

// ✅ Good: Specific to event type and instance
id: `invoice-paid-${invoiceId}`;
id: `user-signup-${userId}-${timestamp}`;
id: `order-shipped-${orderId}-${trackingNumber}`;

// ❌ Bad: Generic IDs shared across event types
id: invoiceId; // Could conflict with other events
id: "user-action"; // Too generic
id: customerId; // Same customer, different events

Deduplication window: 24 hours from first event reception

See inngest-durable-functions for idempotency configuration.

The ts Parameter for Delayed Delivery

When to use: Schedule events for future processing or maintain event ordering.

Future Scheduling

const oneHourFromNow = Date.now() + 60 * 60 * 1000;

await inngest.send({
  name: "trial/reminder.send",
  ts: oneHourFromNow, // Deliver in 1 hour
  data: {
    userId: "user_123",
    trialExpiresAt: "2024-02-15T12:00:00Z"
  }
});

Maintaining Event Order

// Events with timestamps are processed in chronological order
const events = [
  {
    name: "user/action.performed",
    ts: 1640995200000, // Earlier
    data: { action: "login" }
  },
  {
    name: "user/action.performed",
    ts: 1640995260000, // Later
    data: { action: "purchase" }
  }
];

await inngest.send(events);

Fan-Out Patterns

Use case: One event triggers multiple independent functions for reliability and parallel processing.

Basic Fan-Out Implementation

// Send single event
await inngest.send({
  name: "user/signup.completed",
  data: {
    userId: "user_123",
    email: "user@example.com",
    plan: "pro"
  }
});

// Multiple functions respond to same event
const sendWelcomeEmail = inngest.createFunction(
  { id: "send-welcome-email", triggers: [{ event: "user/signup.completed" }] },
  async ({ event, step }) => {
    await step.run("send-email", async () => {
      return sendEmail({
        to: event.data.email,
        template: "welcome"
      });
    });
  }
);

const createTrialSubscription = inngest.createFunction(
  { id: "create-trial", triggers: [{ event: "user/signup.completed" }] },
  async ({ event, step }) => {
    await step.run("create-subscription", async () => {
      return stripe.subscriptions.create({
        customer: event.data.stripeCustomerId,
        trial_period_days: 14
      });
    });
  }
);

const addToCrm = inngest.createFunction(
  { id: "add-to-crm", triggers: [{ event: "user/signup.completed" }] },
  async ({ event, step }) => {
    await step.run("crm-sync", async () => {
      return crm.contacts.create({
        email: event.data.email,
        plan: event.data.plan
      });
    });
  }
);

Fan-Out Benefits

  • Independence: Functions run separately; one failure doesn't affect others
  • Parallel execution: All functions run simultaneously
  • Selective replay: Re-run only failed functions
  • Cross-service: Trigger functions in different codebases/languages

Advanced Fan-Out with waitForEvent

In expressions, event = the original triggering event, async = the new event being matched. See Expression Syntax Reference for full details.

const orchestrateOnboarding = inngest.createFunction(
  { id: "orchestrate-onboarding", triggers: [{ event: "user/signup.completed" }] },
  async ({ event, step }) => {
    // Fan out to multiple services
    await step.sendEvent("fan-out", [
      { name: "email/welcome.send", data: event.data },
      { name: "subscription/trial.create", data: event.data },
      { name: "crm/contact.add", data: event.data }
    ]);

    // Wait for all to complete
    const [emailResult, subResult, crmResult] = await Promise.all([
      step.waitForEvent("email-sent", {
        event: "email/welcome.sent",
        timeout: "5m",
        if: `event.data.userId == async.data.userId`
      }),
      step.waitForEvent("subscription-created", {
        event: "subscription/trial.created",
        timeout: "5m",
        if: `event.data.userId == async.data.userId`
      }),
      step.waitForEvent("crm-synced", {
        event: "crm/contact.added",
        timeout: "5m",
        if: `event.data.userId == async.data.userId`
      })
    ]);

    // Complete onboarding
    await step.run("complete-onboarding", async () => {
      return completeUserOnboarding(event.data.userId);
    });
  }
);

See inngest-steps for additional patterns including step.invoke.

System Events

Inngest emits system events for function lifecycle monitoring:

Available System Events

// Function execution events
"inngest/function.failed"; // Function failed after retries
"inngest/function.finished"; // Function finished - completed or failed
"inngest/function.cancelled"; // Function cancelled before completion

Handling Failed Functions

const handleFailures = inngest.createFunction(
  { id: "handle-failed-functions", triggers: [{ event: "inngest/function.failed" }] },
  async ({ event, step }) => {
    const { function_id, run_id, error } = event.data;

    await step.run("log-failure", async () => {
      logger.error("Function failed", {
        functionId: function_id,
        runId: run_id,
        error: error.message,
        stack: error.stack
      });
    });

    // Alert on critical function failures
    if (function_id.includes("critical")) {
      await step.run("send-alert", async () => {
        return alerting.sendAlert({
          title: `Critical function failed: ${function_id}`,
          severity: "high",
          runId: run_id
        });
      });
    }

    // Auto-retry certain failures
    if (error.code === "RATE_LIMIT_EXCEEDED") {
      await step.run("schedule-retry", async () => {
        return inngest.send({
          name: "retry/function.requested",
          ts: Date.now() + 5 * 60 * 1000, // Retry in 5 minutes
          data: { originalRunId: run_id }
        });
      });
    }
  }
);

Sending Events

Client Setup

// inngest/client.ts
import { Inngest } from "inngest";

export const inngest = new Inngest({
  id: "my-app"
});
// You must set INNGEST_EVENT_KEY environment variable in production

Single Event

const result = await inngest.send({
  name: "order/placed",
  data: {
    orderId: "ord_123",
    customerId: "cus_456",
    amount: 2500,
    items: [
      { id: "item_1", quantity: 2 },
      { id: "item_2", quantity: 1 }
    ]
  }
});

// Returns event IDs for tracking
console.log(result.ids); // ["01HQ8PTAESBZPBDS8JTRZZYY3S"]

Batch Events

const orderItems = await getOrderItems(orderId);

// Convert to events
const events = orderItems.map((item) => ({
  name: "inventory/item.reserved",
  data: {
    itemId: item.id,
    orderId: orderId,
    quantity: item.quantity,
    warehouseId: item.warehouseId
  }
}));

// Send all at once (up to 512kb)
await inngest.send(events);

Sending from Functions

inngest.createFunction(
  { id: "process-order", triggers: [{ event: "order/placed" }] },
  async ({ event, step }) => {
    // Use step.sendEvent() instead of inngest.send() in functions
    // for reliability and deduplication
    await step.sendEvent("trigger-fulfillment", {
      name: "fulfillment/order.received",
      data: {
        orderId: event.data.orderId,
        priority: event.data.customerTier === "premium" ? "high" : "normal"
      }
    });
  }
);

Event Design Best Practices

Schema Versioning

// Use version field to track schema changes
await inngest.send({
  name: "user/profile.updated",
  v: "2024-01-15.1", // Schema version
  data: {
    userId: "user_123",
    changes: {
      email: "new@example.com",
      preferences: { theme: "dark" }
    },
    // New field in v2 schema
    auditInfo: {
      changedBy: "user_456",
      reason: "user_requested"
    }
  }
});

Rich Context Data

// Include enough context for all consumers
await inngest.send({
  name: "payment/charge.succeeded",
  data: {
    // Primary identifiers
    chargeId: "ch_123",
    customerId: "cus_456",

    // Amount details
    amount: 2500,
    currency: "usd",

    // Context for different consumers
    subscription: {
      id: "sub_789",
      plan: "pro_monthly"
    },
    invoice: {
      id: "inv_012",
      number: "INV-2024-001"
    },

    // Metadata for debugging
    paymentMethod: {
      type: "card",
      last4: "4242",
      brand: "visa"
    },
    metadata: {
      source: "stripe_webhook",
      environment: "production"
    }
  }
});

Event design principles:

1. Self-contained: Include all data consumers need 2. Immutable: Never modify event schemas after sending 3. Traceable: Include correlation IDs and audit trails 4. Actionable: Provide enough context for business logic 5. Debuggable: Include metadata for troubleshooting

Related skills

Forks & variants (1)

Inngest Events has 1 known copy in the catalog totaling 32 installs. They canonicalize to this original listing.

How it compares

Use inngest-events when you need event schema and delivery semantics on Inngest, not generic queue configuration guides.

FAQ

What is the recommended event naming pattern?

Use domain/noun.verb past-tense patterns such as billing/invoice.paid or user/profile.updated.

How long does event ID deduplication last?

Inngest deduplicates events with the same id for 24 hours from first reception.

When should fan-out be used?

Use fan-out when one event should trigger multiple independent functions such as email, billing, and CRM sync in parallel.

Is Inngest Events safe to install?

skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.