Inngest-skills inngest-setup
Set up Inngest in a TypeScript project. Install the SDK, create a client, configure environment variables, serve endpoints or connect as a worker, and run the local dev server.
git clone https://github.com/inngest/inngest-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/inngest/inngest-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/inngest-setup" ~/.claude/skills/inngest-inngest-skills-inngest-setup && rm -rf "$T"
skills/inngest-setup/SKILL.mdInngest Setup
This skill sets up Inngest in a TypeScript project from scratch, covering installation, client configuration, connection modes, and local development.
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.
Prerequisites
- Node.js 18+ (Node.js 22.4+ r ecommended for WebSocket support)
- TypeScript project
- Package manager (npm, yarn, pnpm, or bun)
Step 1: Install the Inngest SDK
Install the
inngest npm package in your project:
npm install inngest # or yarn add inngest # or pnpm add inngest # or bun add inngest
Step 2: Create an Inngest Client
Create a shared client file that you'll import throughout your codebase:
// src/inngest/client.ts import { Inngest } from "inngest"; export const inngest = new Inngest({ id: "my-app" // Unique identifier for your application (hyphenated slug) }); // In development, you must set the INNGEST_DEV=1 env var or use isDev: true // In production, INNGEST_SIGNING_KEY is required (v4 defaults to Cloud mode)
Key Configuration Options
(required): Unique identifier for your app. Use a hyphenated slug likeid
or"my-app""user-service"
: Event key for sending events (prefereventKey
env var)INNGEST_EVENT_KEY
: Environment name for Branch Environmentsenv
: Force Dev mode (isDev
) or Cloud mode (true
). v4 defaults to Cloud mode, so setfalse
orisDev: true
for local developmentINNGEST_DEV=1
: Signing key for production (prefersigningKey
env var). Moved fromINNGEST_SIGNING_KEY
to client in v4serve()
: Fallback signing key for key rotation (prefersigningKeyFallback
env var)INNGEST_SIGNING_KEY_FALLBACK
: Custom Inngest API base URL (preferbaseUrl
env var)INNGEST_BASE_URL
: Custom logger instance (e.g. winston, pino) — enableslogger
in function contextlogger
: Array of middleware (see inngest-middleware skill)middleware
Typed Events with eventType()
import { Inngest, eventType } from "inngest"; import { z } from "zod"; const signupCompleted = eventType("user/signup.completed", { schema: z.object({ userId: z.string(), email: z.string(), plan: z.enum(["free", "pro"]) }) }); const orderPlaced = eventType("order/placed", { schema: z.object({ orderId: z.string(), amount: z.number() }) }); export const inngest = new Inngest({ id: "my-app" }); // Use event types as triggers for full type safety: inngest.createFunction( { id: "handle-signup", triggers: [signupCompleted] }, async ({ event }) => { event.data.userId; /* typed as string */ } ); // Use event types when sending events: await inngest.send( signupCompleted.create({ userId: "user_123", email: "user@example.com", plan: "pro" }) );
Environment Variables Setup
Set these environment variables in your
.env file or deployment environment:
# Required for production INNGEST_EVENT_KEY=your-event-key-here INNGEST_SIGNING_KEY=your-signing-key-here # Force dev mode during local development INNGEST_DEV=1 # Optional - custom dev server URL (default: http://localhost:8288) INNGEST_BASE_URL=http://localhost:8288
⚠️ Common Gotcha: Never hardcode keys in your source code. Always use environment variables for
INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY.
Step 3: Choose Your Connection Mode
Inngest supports two connection modes:
Mode A: Serve Endpoint (HTTP)
Best for serverless platforms (Vercel, Lambda, etc.) and existing APIs.
Mode B: Connect (WebSocket)
Best for container runtimes (Kubernetes, Docker) and long-running processes.
Step 4A: Serving an Endpoint (HTTP Mode)
Create an API endpoint that exposes your functions to Inngest:
// For Next.js App Router: src/app/api/inngest/route.ts import { serve } from "inngest/next"; import { inngest } from "../../../inngest/client"; import { myFunction } from "../../../inngest/functions"; export const { GET, POST, PUT } = serve({ client: inngest, functions: [myFunction] });
// For Next.js Pages Router: pages/api/inngest.ts import { serve } from "inngest/next"; import { inngest } from "../../inngest/client"; import { myFunction } from "../../inngest/functions"; export default serve({ client: inngest, functions: [myFunction] });
// For Express.js import express from "express"; import { serve } from "inngest/express"; import { inngest } from "./inngest/client"; import { myFunction } from "./inngest/functions"; const app = express(); app.use(express.json({ limit: "10mb" })); // Required for Inngest, increase limit for larger function state app.use( "/api/inngest", serve({ client: inngest, functions: [myFunction] }) );
🔧 Framework-Specific Notes:
- Express: Must use
middleware to support larger function state.express.json({ limit: "10mb" }) - Fastify: Use
fromfastifyPlugininngest/fastify - Cloudflare Workers: Use
inngest/cloudflare - AWS Lambda: Use
inngest/lambda - For all other frameworks, check the
reference here: https://www.inngest.com/docs-markdown/learn/serving-inngest-functionsserve
⚠️ v4 Change: Options like
signingKey, signingKeyFallback, and baseUrl are now configured on the Inngest client constructor, not on serve(). The serve() function only accepts client, functions, and streaming.
⚠️ Common Gotcha: Always use
/api/inngest as your endpoint path. This enables automatic discovery. If you must use a different path, you'll need to configure discovery manually with the -u flag.
Step 4B: Connect as Worker (WebSocket Mode)
For long-running applications that maintain persistent connections:
// src/worker.ts import { connect } from "inngest/connect"; import { inngest } from "./inngest/client"; import { myFunction } from "./inngest/functions"; (async () => { const connection = await connect({ apps: [{ client: inngest, functions: [myFunction] }], instanceId: process.env.HOSTNAME, // Unique worker identifier maxWorkerConcurrency: 10 // Max concurrent steps }); console.log("Worker connected:", connection.state); // Graceful shutdown handling await connection.closed; console.log("Worker shut down"); })();
Requirements for Connect Mode:
- Node.js 22.4+ (or Deno 1.4+, Bun 1.1+) for WebSocket support
- Long-running server environment (not serverless)
andINNGEST_SIGNING_KEY
for productionINNGEST_EVENT_KEY- Set the
parameter on theappVersion
client for production to support rolling deploysInngest
v4 Connect Changes:
- Worker thread isolation is enabled by default — WebSocket connections execute in a worker thread to prevent event loop starvation. Set
to use a single process (orisolateExecution: false
)INNGEST_CONNECT_ISOLATE_EXECUTION=false
callback has been replaced with therewriteGatewayEndpoint
string option (orgatewayUrl
env var)INNGEST_CONNECT_GATEWAY_URL
Step 5: Organizing with Apps
As your system grows, organize functions into logical apps:
// User service const userService = new Inngest({ id: "user-service" }); // Payment service const paymentService = new Inngest({ id: "payment-service" }); // Email service const emailService = new Inngest({ id: "email-service" });
Each app gets its own section in the Inngest dashboard and can be deployed independently. Use descriptive, hyphenated IDs that match your service architecture.
⚠️ Common Gotcha: Changing an app's
id creates a new app in Inngest. Keep IDs consistent across deployments.
Step 6: Local Development with inngest-cli
Start the Inngest Dev Server for local development:
# Auto-discover your app on common ports/endpoints npx --ignore-scripts=false inngest-cli@latest dev # Specify your app's URL manually npx --ignore-scripts=false inngest-cli@latest dev -u http://localhost:3000/api/inngest # Custom port for dev server npx --ignore-scripts=false inngest-cli@latest dev -p 9999 # Disable auto-discovery npx --ignore-scripts=false inngest-cli@latest dev --no-discovery -u http://localhost:3000/api/inngest # Multiple apps npx --ignore-scripts=false inngest-cli@latest dev -u http://localhost:3000/api/inngest -u http://localhost:4000/api/inngest
The dev server will be available at
http://localhost:8288 by default.
Configuration File (Optional)
Create
inngest.json for complex setups:
{ "sdk-url": [ "http://localhost:3000/api/inngest", "http://localhost:4000/api/inngest" ], "port": 8289, "no-discovery": true }
Environment-Specific Setup
Local Development
INNGEST_DEV=1 # No keys required in dev mode
Production
INNGEST_EVENT_KEY=evt_your_production_event_key INNGEST_SIGNING_KEY=signkey_your_production_signing_key
Custom Dev Server Port
INNGEST_DEV=1 INNGEST_BASE_URL=http://localhost:9999
If your app runs on a non-standard port (not 3000), make sure the dev server can reach it by specifying the URL with
-u flag.
Common Issues & Solutions
Port Conflicts: If port 8288 is in use, specify a different port:
-p 9999
Auto-discovery Not Working: Use manual URL specification:
-u http://localhost:YOUR_PORT/api/inngest
Signature Verification Errors: Ensure
INNGEST_SIGNING_KEY is set correctly in production
WebSocket Connection Issues: Verify Node.js version 22.4+ for connect mode
Docker Development: Use
host.docker.internal for app URLs when running dev server in Docker
Next Steps
- Create your first Inngest function with
inngest.createFunction() - Test functions using the dev server's "Invoke" button
- Send events with
to trigger functionsinngest.send() - Deploy to production with proper environment variables
- See inngest-middleware for adding logging, error tracking, and other cross-cutting concerns
- Monitor functions in the Inngest dashboard
The dev server automatically reloads when you change functions, making development fast and iterative.