Claude Code Plugins

Community-maintained marketplace

Feedback

Create and manage Inngest functions for reliable background jobs, workflows, and scheduled tasks.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name inngest-handler
description Create and manage Inngest functions for reliable background jobs, workflows, and scheduled tasks.

Inngest Function Handler Skill

This skill defines the standards for building durable, multi-step workflows using Inngest.

🚨 HARD RULES (Strictly Follow)

  1. NO setTimeout / setInterval:

    • Bad: await new Promise(r => setTimeout(r, 1000))
    • Good: await step.sleep("wait-1s", "1s")
    • Reason: Serverless functions time out; Inngest sleeps persist for up to a year.
  2. NO Side Effects Outside Steps:

    • Any database write, API call, or non-deterministic logic (random, date) MUST be wrapped in step.run().
    • Reason: Inngest functions execute multiple times (memoization). Code outside steps runs every time.
  3. Deterministic Steps:

    • Steps are memoized by their ID (1st arg). IDs must be unique and stable.
    • Do not dynamically generate step IDs unless you know what you are doing (e.g., inside loops with index).
  4. Return Data from Steps:

    • If you need a value later, return it from the step.
    • Bad: let userId; await step.run(..., () => { userId = ... })
    • Good: const userId = await step.run(..., () => { return ... })

Core Patterns

1. Multi-Step Execution

Wrap all logic in steps to ensure retriability and resumability.

export const processOrder = inngest.createFunction(
  { id: "process-order" },
  { event: "shop/order.created" },
  async ({ event, step }) => {
    // 1. Step: Validate (Retriable)
    const user = await step.run("get-user", async () => {
      return await db.users.findById(event.data.userId);
    });

    // 2. Step: Sleep (Durable pause)
    await step.sleep("wait-for-payment", "1h");

    // 3. Step: Wait for Event (Human/System interaction)
    const payment = await step.waitForEvent("wait-payment", {
      event: "shop/payment.success",
      match: "data.orderId",
      timeout: "24h"
    });

    // 4. Step: Conditional Logic
    if (!payment) {
        await step.run("cancel-order", async () => { ... });
    }
  }
);

2. Parallelism

Run steps concurrently to speed up execution.

const [user, subscription] = await Promise.all([
  step.run("fetch-user", () => db.users.find(...)),
  step.run("fetch-sub", () => stripe.subscriptions.retrieve(...))
]);

3. Working with Loops

Inside loops, ensure step IDs are unique.

const items = event.data.items;
for (const item of items) {
  // Use dynamic ID to ensure uniqueness per item
  await step.run(`process-item-${item.id}`, async () => {
    await processItem(item);
    });
}

Configuration & Flow Control

Rate Limiting & Throttling

Prevent overwhelming 3rd party APIs.

inngest.createFunction({
    id: "sync-crm",
    // Max 10 requests per minute per user
    rateLimit: { limit: 10, period: "1m", key: "event.data.userId" },
    // Drop events if queue is full
    throttle: { limit: 5, period: "1s" } 
}, ...);

Debounce

Process only the latest event in a window (e.g., search indexing).

inngest.createFunction({
    id: "index-product",
    // Wait 10s for more events; only run with the latest data
    debounce: { period: "10s", key: "event.data.productId" }
}, ...);

Priority

Prioritize specific events (e.g., Paid users).

inngest.createFunction({
    id: "generate-report",
    // High number = High priority
    priority: { run: "event.data.plan === 'enterprise' ? 100 : 0" }
}, ...);

Error Handling

Automatic Retries

Inngest retries steps automatically on error (default ~4-5 times with backoff).

  • Customize: { retries: 10 } in config.

Non-Retriable Errors

Stop execution immediately if the error is fatal (e.g., 400 Bad Request).

import { NonRetriableError } from "inngest";

await step.run("validate", async () => {
  if (!isValid) throw new NonRetriableError("Invalid payload");
});

Failure Handlers (Rollbacks)

Execute cleanup logic if the function fails after all retries.

export const riskyFunc = inngest.createFunction(
  { 
    id: "risky-transfer",
    // Runs if main handler fails
    onFailure: async ({ error, event, step }) => {
      await step.run("rollback-funds", async () => {
        await reverseTransfer(event.data.transferId);
      });
      await step.run("notify-admin", async () => {
        await sendAlert(`Transfer failed: ${error.message}`);
      });
    }
  },
  { event: "bank/transfer.init" },
  async ({ step }) => { /* ... */ }
);

Registration

MANDATORY: All functions must be imported and exported in src/lib/inngest/functions/index.ts.