--- name: inngest-setup description: Use when adding durable execution to a TypeScript project — building retry-safe webhook handlers, background jobs that survive crashes, scheduled tasks, or long-running workflows that outlive a single request. Covers Inngest SDK installation, client config, environment variables, serve endpoints (Next.js, Express, Hono, Fastify), connect-as-worker mode, and the local dev server. --- # Inngest 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](https://www.inngest.com/llms.txt) 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: ```bash 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: ```typescript // src/inngest/client.ts import { Inngest } from "inngest"; export const inngest = new Inngest({ id: "my-app" // Unique identifier for your application (hyphenated slug) }); // IMPORTANT: v4 defaults to Cloud mode. For local dev, set INNGEST_DEV=1 env var. // Without it, your serve endpoint will return 500 ("In cloud mode but no signing key"). // In production, set INNGEST_SIGNING_KEY (required for Cloud mode). ``` ### Key Configuration Options - **`id`** (required): Unique identifier for your app. Use a hyphenated slug like `"my-app"` or `"user-service"` - **`eventKey`**: Event key for sending events (prefer `INNGEST_EVENT_KEY` env var) - **`env`**: Environment name for Branch Environments - **`isDev`**: Force Dev mode (`true`) or Cloud mode (`false`). **v4 defaults to Cloud mode**, so set `INNGEST_DEV=1` env var for local development. **Never hardcode `isDev: true` in source code** — it will silently break in production. Always use the env var. - **`signingKey`**: Signing key for production (prefer `INNGEST_SIGNING_KEY` env var). Moved from `serve()` to client in v4 - **`signingKeyFallback`**: Fallback signing key for key rotation (prefer `INNGEST_SIGNING_KEY_FALLBACK` env var) - **`baseUrl`**: Custom Inngest API base URL (prefer `INNGEST_BASE_URL` env var) - **`logger`**: Custom logger instance (e.g. winston, pino) — enables `logger` in function context - **`middleware`**: Array of middleware (see **inngest-middleware** skill) ### Typed Events with eventType() ```typescript 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: ```env # 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`. ## CRITICAL: Enable Dev Mode for Local Development **Before creating serve endpoints or connecting workers, ensure dev mode is enabled.** Without it, Inngest defaults to Cloud mode and your endpoints will fail with 500 errors. Add to your `.env` file (or your dev script in package.json): ```env INNGEST_DEV=1 ``` Or in `package.json` scripts: ```json { "scripts": { "dev": "INNGEST_DEV=1 tsx --watch src/server.ts" } } ``` **Symptoms of missing INNGEST_DEV:** - GET `/api/inngest` returns `{"code":"internal_server_error"}` - Server logs: "In cloud mode but no signing key found" - Dev server can't sync with your app ## 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: ```typescript // 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] }); ``` ```typescript // 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] }); ``` ```typescript // 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 `express.json({ limit: "10mb" })` middleware to support larger function state. - **Fastify**: Use `fastifyPlugin` from `inngest/fastify` - **Cloudflare Workers**: Use `inngest/cloudflare` - **AWS Lambda**: Use `inngest/lambda` - For all other frameworks, check the `serve` reference here: https://www.inngest.com/docs-markdown/learn/serving-inngest-functions **⚠️ 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: ```typescript // 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) - `INNGEST_SIGNING_KEY` and `INNGEST_EVENT_KEY` for production - Set the `appVersion` parameter on the `Inngest` client for production to support rolling deploys **v4 Connect Changes:** - **Worker thread isolation** is enabled by default — WebSocket connections execute in a worker thread to prevent event loop starvation. Set `isolateExecution: false` to use a single process (or `INNGEST_CONNECT_ISOLATE_EXECUTION=false`) - **`rewriteGatewayEndpoint`** callback has been replaced with the `gatewayUrl` string option (or `INNGEST_CONNECT_GATEWAY_URL` env var) ## Step 5: Organizing with Apps As your system grows, organize functions into logical apps: ```typescript // 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: ```bash # 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: ```json { "sdk-url": [ "http://localhost:3000/api/inngest", "http://localhost:4000/api/inngest" ], "port": 8289, "no-discovery": true } ``` ## Environment-Specific Setup ### Local Development ```env INNGEST_DEV=1 # No keys required in dev mode ``` ### Production ```env INNGEST_EVENT_KEY=evt_your_production_event_key INNGEST_SIGNING_KEY=signkey_your_production_signing_key ``` ### Custom Dev Server Port ```env 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`. If using `--no-discovery` flag, the `-u` flag is **required** — the dev server will not find your app without it. **Functions Not Showing in Dev Server**: Your app must register with the dev server. This happens automatically when your serve endpoint receives its first request from the dev server. If registration isn't happening: (1) verify `INNGEST_DEV=1` is set, (2) verify the dev server can reach your app URL, (3) try restarting your app while the dev server is running. **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 1. Create your first Inngest function with `inngest.createFunction()` 2. Test functions using the dev server's "Invoke" button 3. Send events with `inngest.send()` to trigger functions 4. Deploy to production with proper environment variables 5. See **inngest-middleware** for adding logging, error tracking, and other cross-cutting concerns 6. Monitor functions in the Inngest dashboard The dev server automatically reloads when you change functions, making development fast and iterative.