Agent Native

inngest-steps

This skill enables building durable workflows using Inngest's step methods, which automatically handle retries and survive process restarts.

by InngestOfficial publisherNot reviewed

Identity proven by GitHub's verified organisation badge (inngest)

Agents
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, claude.ai and API
License
Apache-2.0 (permissive)
Pin
082798apinned Oct 9, 2026
Context cost
about 2,700 tokenswhen the skill loads
GitHub stars
28on inngest/inngest-skills
Scripts
None found

What it does

“Use when implementing delays that must survive process restarts (e.g., 24-hour cart abandonment, scheduled follow-ups), waiting for human approval or external events with timeouts (review gates, webhook callbacks, async API completion), polling external services without losing state on crashes, calling other functions and awaiting their results, memoizing expensive operations so they don't re-run on retry, or running async work in parallel inside a workflow. Covers Inngest step methods: step.run, step.sleep, step.waitForEvent, step.waitForSignal, step.sendEvent, step.invoke, step.ai, plus patterns for loops and parallel execution.”
The author's description, from SKILL.md

Generated summary from the skill's own text

This skill enables building durable workflows using Inngest's step methods, which automatically handle retries and survive process restarts. It provides step.run for retriable code execution, step.sleep and step.sleepUntil for scheduling, step.waitForEvent and step.waitForSignal for handling external triggers, step.invoke for calling other functions, and patterns for parallel execution and loops. Each step executes as a separate HTTP request, allowing complex workflows with delays, approvals, and async work to persist across crashes.

When to use it

  • Implementing long delays that survive restarts, like 24-hour cart abandonment reminders
  • Waiting for human approvals or external events with timeout handling
  • Processing large datasets in parallel with chunked steps
  • Building workflows that call other functions and compose complex logic

When not to

  • Synchronous, single-operation tasks without retry or durability needs
  • Code that must run instantly without HTTP request overhead

Install

These commands install from the publisher's repository and need no account.

skills CLI

npx skills add inngest/inngest-skills --skill inngest-steps

Installs from the repository's default branch, which may have changed since the commit we checked.

GitHub CLI 2.90 or later

gh skill install inngest/inngest-skills inngest-steps@082798a627bbe9c3ed3a6638a9f56116a035f122

Installs the commit we checked (082798a).

Copy by hand

Download the folder at 082798a and put it in .claude/skills/inngest-steps for Claude Code and in .agents/skills/inngest-steps for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.

Skill folder on GitHub at 082798a

Copy for your agent

A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.

Prompt

Install the agent skill "inngest-steps" by Inngest.

Source, pinned to commit 082798a627bbe9c3ed3a6638a9f56116a035f122:
https://github.com/inngest/inngest-skills/tree/082798a627bbe9c3ed3a6638a9f56116a035f122/skills/inngest-steps

1. Download that folder at exactly this commit. Do not use the default branch.
2. Copy every file unchanged, keeping the folder structure, into .claude/skills/inngest-steps/ for Claude Code and .agents/skills/inngest-steps/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist.
3. Do not edit the files, and do not run any script in the folder unless I ask you to.
4. When you are done, list the files you copied and where you put them.

Listing and checks: https://www.agentnative.dev/skills/inngest/inngest-steps-inngest-skills

SKILL.md at the pin

---
name: inngest-steps
description: "Use when implementing delays that must survive process restarts (e.g., 24-hour cart abandonment, scheduled follow-ups), waiting for human approval or external events with timeouts (review gates, webhook callbacks, async API completion), polling external services without losing state on crashes, calling other functions and awaiting their results, memoizing expensive operations so they don't re-run on retry, or running async work in parallel inside a workflow. Covers Inngest step methods: step.run, step.sleep, step.waitForEvent, step.waitForSignal, step.sendEvent, step.invoke, step.ai, plus patterns for loops and parallel execution."
---

# Inngest Steps

Build robust, durable workflows with Inngest's step methods. Each step is a separate HTTP request that can be independently retried and monitored.

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

## Core Concept

**🔄 Critical: Each step re-runs your function from the beginning.** Put ALL non-deterministic code (API calls, DB queries, randomness) inside steps, never outside.

**📊 Step Limits:** Every function has a maximum of 1,000 steps and 4MB total step data.

```typescript
// ❌ WRONG - will run 4 times
export default inngest.createFunction(
  { id: "bad-example", triggers: [{ event: "test" }] },
  async ({ step }) => {
    console.log("This logs 4 times!"); // Outside step = bad
    await step.run("a", () => console.log("a"));
    await step.run("b", () => console.log("b"));
    await step.run("c", () => console.log("c"));
  }
);

// ✅ CORRECT - logs once each
export default inngest.createFunction(
  { id: "good-example", triggers: [{ event: "test" }] },
  async ({ step }) => {
    await step.run("log-hello", () => console.log("hello"));
    await step.run("a", () => console.log("a"));
    await step.run("b", () => console.log("b"));
    await step.run("c", () => console.log("c"));
  }
);
```

## step.run()

Execute retriable code as a step. **Each step ID can be reused** - Inngest automatically handles counters.

```typescript
// Basic usage
const result = await step.run("fetch-user", async () => {
  const user = await db.user.findById(userId);
  return user; // Always return useful data
});

// Synchronous code works too
const transformed = await step.run("transform-data", () => {
  return processData(result);
});

// Side effects (no return needed)
await step.run("send-notification", async () => {
  await sendEmail(user.email, "Welcome!");
});
```

**✅ DO:**

- Put ALL non-deterministic logic inside steps
- Return useful data for subsequent steps
- Reuse step IDs in loops (counters handled automatically)

**❌ DON'T:**

- Put deterministic logic in steps unnecessarily
- Forget that each step = separate HTTP request

## step.sleep()

Pause execution without using compute time.

```typescript
// Duration strings
await step.sleep("wait-24h", "24h");
await step.sleep("short-delay", "30s");
await step.sleep("weekly-pause", "7d");

// Use in workflows
await step.run("send-welcome", () => sendEmail(email));
await step.sleep("wait-for-engagement", "3d");
await step.run("send-followup", () => sendFollowupEmail(email));
```

## step.sleepUntil()

Sleep until a specific datetime.

```typescript
const reminderDate = new Date("2024-12-25T09:00:00Z");
await step.sleepUntil("wait-for-christmas", reminderDate);

// From event data
const scheduledTime = new Date(event.data.remind_at);
await step.sleepUntil("wait-for-scheduled-time", scheduledTime);
```

## step.waitForEvent()

**🚨 CRITICAL: waitForEvent ONLY catches events sent AFTER this step executes.**

- ❌ Event sent before waitForEvent runs → will NOT be caught
- ✅ Event sent after waitForEvent runs → will be caught
- Always check for `null` return (means timeout, event never arrived)

```typescript
// Basic event waiting with timeout
const approval = await step.waitForEvent("wait-for-approval", {
  event: "app/invoice.approved",
  timeout: "7d",
  match: "data.invoiceId" // Simple matching
});

// Expression-based matching (CEL syntax)
const subscription = await step.waitForEvent("wait-for-subscription", {
  event: "app/subscription.created",
  timeout: "30d",
  if: "event.data.userId == async.data.userId && async.data.plan == 'pro'"
});

// Handle timeout
if (!approval) {
  await step.run("handle-timeout", () => {
    // Approval never came
    return notifyAccountingTeam();
  });
}
```

**✅ DO:**

- Use unique IDs for matching (userId, sessionId, requestId)
- Always set reasonable timeouts
- Handle null return (timeout case)
- Use with Realtime for human-in-the-loop flows

**❌ DON'T:**

- Expect events sent before this step to be handled
- Use without timeouts in production

### Expression Syntax

In expressions, `event` = the **original** triggering event, `async` = the **new** event being matched. See [Expression Syntax Reference](../references/expressions.md) for full syntax, operators, and patterns.

## step.waitForSignal()

Wait for unique signals (not events). Better for 1:1 matching.

```typescript
const taskId = "task-" + crypto.randomUUID();

const signal = await step.waitForSignal("wait-for-task-completion", {
  signal: taskId,
  timeout: "1h",
  onConflict: "replace" // Required: "replace" overwrites pending signal, "fail" throws an error
});

// Send signal elsewhere via Inngest API or SDK
// POST /v1/events with signal matching taskId
```

**When to use:**

- **waitForEvent**: Multiple functions might handle the same event
- **waitForSignal**: Exact 1:1 signal to specific function run

## step.sendEvent()

Fan out to other functions without waiting for results.

```typescript
// Trigger other functions
await step.sendEvent("notify-systems", {
  name: "user/profile.updated",
  data: { userId: user.id, changes: profileChanges }
});

// Multiple events at once
await step.sendEvent("batch-notifications", [
  { name: "billing/invoice.created", data: { invoiceId } },
  { name: "email/invoice.send", data: { email: user.email, invoiceId } }
]);
```

**Use when:** You want to trigger other functions but don't need their results in the current function.

## step.invoke()

Call other functions and handle their results. Perfect for composition.

```typescript
const computeSquare = inngest.createFunction(
  { id: "compute-square", triggers: [{ event: "calculate/square" }] },
  async ({ event }) => {
    return { result: event.data.number * event.data.number };
  }
);

// Invoke and use result
const square = await step.invoke("get-square", {
  function: computeSquare,
  data: { number: 4 }
});

console.log(square.result); // 16, fully typed!

// For cross-app invocation (when you can't import the function directly):
import { referenceFunction } from "inngest";

const externalFn = referenceFunction({
  appId: "other-app",
  functionId: "other-fn"
});

const result = await step.invoke("call-external", {
  function: externalFn,
  data: { key: "value" }
});
```

**Warning: v4 Breaking Change:** String function IDs (e.g., `function: "my-app-other-fn"`) are no longer supported in `step.invoke()`. Use an imported function reference or `referenceFunction()` for cross-app calls.

**Great for:**

- Breaking complex workflows into composable functions
- Reusing logic across multiple workflows
- Map-reduce patterns

## Patterns

### Loops with Steps

Reuse step IDs - Inngest handles counters automatically.

```typescript
const allProducts = [];
let cursor = null;
let hasMore = true;

while (hasMore) {
  // Same ID "fetch-page" reused - counters handled automatically
  const page = await step.run("fetch-page", async () => {
    return shopify.products.list({ cursor, limit: 50 });
  });

  allProducts.push(...page.products);

  if (page.products.length < 50) {
    hasMore = false;
  } else {
    cursor = page.products[49].id;
  }
}

await step.run("process-products", () => {
  return processAllProducts(allProducts);
});
```

### Parallel Execution

Use Promise.all for parallel steps. **In v4, parallel step execution is optimized by default**

```typescript
// Create steps without awaiting
const sendEmail = step.run("send-email", async () => {
  return await sendWelcomeEmail(user.email);
});

const updateCRM = step.run("update-crm", async () => {
  return await crmService.addUser(user);
});

const createSubscription = step.run("create-subscription", async () => {
  return await subscriptionService.create(user.id);
});

// Run all in parallel
const [emailId, crmRecord, subscription] = await Promise.all([
  sendEmail,
  updateCRM,
  createSubscription
]);

// Parallel steps are optimized by default in v4
export default inngest.createFunction(
  {
    id: "parallel-heavy-function",
    triggers: [{ event: "process/batch" }]
  },
  async ({ event, step }) => {
    const results = await Promise.all(
      event.data.items.map((item, i) =>
        step.run(`process-item-${i}`, () => processItem(item))
      )
    );
  }
);

// ⚠️ Promise.race() behavior with v4's optimized parallelism:
// All promises settle before race resolves. Use group.parallel() for true race:
const winner = await group.parallel(async () => {
  return Promise.race([
    step.run("fast-service", () => callFastService()),
    step.run("slow-service", () => callSlowService())
  ]);
});

// To disable optimized parallelism if needed:
// At the client level: new Inngest({ id: "app", optimizeParallelism: false })
// At the function level: { id: "fn", optimizeParallelism: false, triggers: [...] }
```

See **inngest-flow-control** for concurrency and throttling options.

### Chunking Jobs

Perfect for batch processing with parallel steps.

```typescript
export default inngest.createFunction(
  { id: "process-large-dataset", triggers: [{ event: "data/process.large" }] },
  async ({ event, step }) => {
    const chunks = chunkArray(event.data.items, 10);

    // Process chunks in parallel
    const results = await Promise.all(
      chunks.map((chunk, index) =>
        step.run(`process-chunk-${index}`, () => processChunk(chunk))
      )
    );

    // Combine results
    await step.run("combine-results", () => {
      return aggregateResults(results);
    });
  }
);
```

## Key Gotchas

**🔄 Function Re-execution:** Code outside steps runs on every step execution
**⏰ Event Timing:** waitForEvent only catches events sent AFTER the step runs
**🔢 Step Limits:** Max 1,000 steps per function, 4MB per step output, 32MB per function run in total
**📨 HTTP Requests:** Checkpointing is enabled by default in v4, reducing HTTP overhead. For serverless platforms, configure `maxRuntime` on the client
**🔁 Step IDs:** Can be reused in loops - Inngest handles counters
**⚡ Parallelism:** Use Promise.all for parallel steps (optimized by default in v4). Note that Promise.race() waits for all promises to settle — use `group.parallel()` for true race semantics

## Common Use Cases

- **Human-in-the-loop:** waitForEvent + Realtime UI
- **Multi-step onboarding:** sleep between steps, waitForEvent for user actions
- **Data processing:** Parallel steps for chunked work
- **External integrations:** step.run for reliable API calls
- **AI workflows:** step.ai for durable LLM orchestration
- **Function composition:** step.invoke to build complex workflows

Remember: Steps make your functions durable, observable, and debuggable. Embrace them!

From inngest/inngest-skills at 082798a, shown as plain text under its license: Apache-2.0 (permissive). The license text is in LICENSE.

View SKILL.md on GitHub at 082798a

Files

1 file, 12 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.

Skill folder on GitHub at 082798a

Also in

The same SKILL.md content appears in these places too. We list it once and credit the oldest public source.

All 18 skills from Inngest

More from Inngest

  • inngest-agent-evals

    Builds production evaluation workflows for AI agents on Inngest using direct and deferred scoring, sessions for grouping related runs, step experiments for…

    Claude Code, Codex, Cursor +4 · about 2.6K tokens · pinned 6 days ago

  • inngest-agents

    Build durable AI agents and multi-step agentic workflows using Inngest and AgentKit.

    Claude Code, Codex, Cursor +4 · about 2.1K tokens · pinned 6 days ago

  • inngest-api-cli

    This skill routes the agent to use the Inngest CLI `inngest api` command for operational tasks like debugging runs, querying events, syncing apps, invoking…

    Claude Code, Codex, Cursor +4 · about 1.9K tokens · pinned 6 days ago

  • inngest-api

    This skill enables agents to interact with the Inngest REST API v2 for Cloud operations, raw HTTP requests, and API reference lookups.

    Claude Code, Codex, Cursor +4 · about 1.1K tokens · pinned 6 days ago

  • inngest-brownfield-audit

    This skill audits existing TypeScript or JavaScript codebases to identify where Inngest can improve reliability and durability.

    Claude Code, Codex, Cursor +4 · about 2.5K tokens · pinned 6 days ago

  • inngest-cli

    This skill enables installation and operation of the Inngest CLI for local development and testing with the Dev Server, which provides a browser UI at…

    Claude Code, Codex, Cursor +4 · about 3.4K tokens · pinned 6 days ago

All Backend and APIs skills
  • inngest-durable-functions

    Build fault-tolerant, long-running workflows using Inngest's durable execution model in TypeScript.

    by InngestOfficial publisher

    Claude Code, Codex, Cursor +4 · about 3.5K tokens · pinned 6 days ago

  • inngest-events

    This skill teaches event design and delivery patterns for Inngest, covering event payload structure, naming conventions, idempotency with event IDs, scheduled…

    by InngestOfficial publisher

    Claude Code, Codex, Cursor +4 · about 2.9K tokens · pinned 6 days ago

  • inngest-flow-control

    This skill provides Inngest flow control mechanisms to manage API rate limits, prevent duplicate processing, and ensure fair resource allocation.

    by InngestOfficial publisher

    Claude Code, Codex, Cursor +4 · about 2.1K tokens · pinned 6 days ago

  • inngest-middleware

    This skill teaches how to implement middleware in Inngest durable functions to handle cross-cutting concerns like structured logging, error tracking…

    by InngestOfficial publisher

    Claude Code, Codex, Cursor +4 · about 2.4K tokens · pinned 6 days ago

  • inngest-realtime

    Stream live updates from Inngest v4 durable workflows to browser UIs via typed channels and topics.

    by InngestOfficial publisher

    Claude Code, Codex, Cursor +4 · about 4.5K tokens · pinned 6 days ago

  • inngest-setup

    This skill sets up Inngest, a durable execution platform, in TypeScript projects for building retry-safe webhook handlers, background jobs, and scheduled tasks.

    by InngestOfficial publisher

    Claude Code, Codex, Cursor +4 · about 2.9K tokens · pinned 6 days ago

Open roles at Inngest

No open roles at Inngest on the Agent Native jobs board right now.

Browse the jobs board

The skills listed here are third-party software, collected from public GitHub repositories as a catalog of recent skills and provided as is. Agent Native does not write or control them and is not responsible for any harm, loss or damage caused by installing or using them. Labels and checks describe what we saw at one commit and are not a guarantee. Read a skill's files before you run it. Terms of use