Inngest Integration
Inngest provides serverless-first background jobs, event-driven workflows, and durable execution without managing queues or workers. Events are the primitive; steps are durable checkpoints; sleeps are real non-blocking waits.
When to Use
Activate this skill when the user mentions or implies any of the following:
inngestby name- Serverless background jobs or serverless queues
- Event-driven workflows or event fan-out
- Step functions or durable execution
- Vercel background jobs
- Scheduled functions or cron jobs
- Concurrency control for downstream services
- Idempotent webhook handling
- AI pipelines with long-running, multi-step processing
Prerequisites
- An Inngest account and an event key (use
YOUR_INNGEST_EVENT_KEYplaceholder; never commit live keys). - A project using a supported HTTP framework: Next.js (App Router), Express, Hono, Remix, SvelteKit, or any platform that serves HTTP (Vercel, Cloudflare Workers, Netlify, Railway, Fly.io).
- TypeScript recommended for typed event schemas.
- Inngest CLI for local dev:
npx inngest-cli@latest dev(Windows PowerShell compatible).
Procedure
1. Create the Inngest Client with Typed Events
Define your client and event schemas for type safety.
// lib/inngest/client.ts
import { Inngest, EventSchemas } from 'inngest';
type Events = {
'user/signed.up': { data: { userId: string; email: string } };
'order/placed': { data: { orderId: string; total: number } };
};
export const inngest = new Inngest({
id: 'my-app',
schemas: new EventSchemas().fromRecord<Events>(),
});
2. Define Functions
Each function must have a unique id. Use step.run for durable checkpoints, step.sleep for non-blocking waits, and step.sendEvent for fan-out.
// lib/inngest/functions.ts
import { inngest } from './client';
export const sendWelcomeEmail = inngest.createFunction(
{ id: 'send-welcome-email' },
{ event: 'user/signed.up' },
async ({ event, step }) => {
const user = await step.run('get-user', async () => {
return await db.users.findUnique({ where: { id: event.data.userId } });
});
await step.run('send-email', async () => {
await resend.emails.send({
to: user.email,
subject: 'Welcome!',
template: 'welcome',
});
});
await step.sleep('wait-for-tips', '24h');
await step.run('send-tips', async () => {
await resend.emails.send({
to: user.email,
subject: 'Getting Started Tips',
template: 'tips',
});
});
}
);
3. Register the Serve Handler
Inngest requires an HTTP endpoint to receive events. For Next.js App Router:
// app/api/inngest/route.ts
import { serve } from 'inngest/next';
import { inngest } from '@/lib/inngest/client';
import { sendWelcomeEmail } from '@/lib/inngest/functions';
export const { GET, POST, PUT } = serve({
client: inngest,
functions: [sendWelcomeEmail],
});
For other frameworks, use the corresponding import: inngest/express, inngest/hono, inngest/remix, inngest/sveltekit.
4. Run the Dev Server
Start the Inngest dev server locally (Windows PowerShell):
npx inngest-cli@latest dev
This provides a local dashboard at http://localhost:8288 for inspecting events and function runs.
5. Multi-Step Workflow with Parallel Steps and Concurrency
export const processOrder = inngest.createFunction(
{
id: 'process-order',
retries: 3,
concurrency: { limit: 10 },
},
{ event: 'order/placed' },
async ({ event, step }) => {
const { orderId } = event.data;
const [inventory, payment] = await Promise.all([
step.run('check-inventory', () => checkInventory(orderId)),
step.run('validate-payment', () => validatePayment(orderId)),
]);
if (!inventory.available) {
await step.sendEvent('notify-backorder', {
name: 'order/backordered',
data: { orderId, items: inventory.missing },
});
return { status: 'backordered' };
}
const charge = await step.run('charge-payment', async () => {
return await stripe.charges.create({
amount: event.data.total,
customer: payment.customerId,
});
});
await step.run('ship-order', () => fulfillment.ship(orderId));
return { status: 'completed', chargeId: charge.id };
}
);
6. Scheduled / Cron Functions
export const dailyDigest = inngest.createFunction(
{ id: 'daily-digest' },
{ cron: '0 9 * * *' },
async ({ step }) => {
const users = await step.run('get-users', async () => {
return await db.users.findMany({ where: { digestEnabled: true } });
});
await step.sendEvent(
'send-digests',
users.map(user => ({
name: 'digest/send',
data: { userId: user.id },
}))
);
return { sent: users.length };
}
);
export const sendDigest = inngest.createFunction(
{ id: 'send-digest', concurrency: { limit: 50 } },
{ event: 'digest/send' },
async ({ event, step }) => {
// ... send individual digest
}
);
7. Webhook Handler with Idempotency
export const handleStripeWebhook = inngest.createFunction(
{
id: 'stripe-webhook',
idempotency: 'event.data.stripeEventId',
},
{ event: 'stripe/webhook.received' },
async ({ event, step }) => {
const { type, data } = event.data;
switch (type) {
case 'checkout.session.completed':
await step.run('fulfill-order', async () => {
await fulfillOrder(data.session.id);
});
break;
case 'customer.subscription.deleted':
await step.run('cancel-subscription', async () => {
await cancelSubscription(data.subscription.id);
});
break;
}
}
);
8. AI Pipeline with Long Processing
export const processDocument = inngest.createFunction(
{
id: 'process-document',
retries: 2,
concurrency: { limit: 5 },
},
{ event: 'document/uploaded' },
async ({ event, step }) => {
const text = await step.run('extract-text', async () => {
return await extractTextFromPDF(event.data.fileUrl);
});
const chunks = await step.run('chunk-text', async () => {
return chunkText(text, { maxTokens: 500 });
});
const embeddings = await step.run('generate-embeddings', async () => {
return await openai.embeddings.create({
model: 'text-embedding-3-small',
input: chunks,
});
});
await step.run('store-vectors', async () => {
await vectorDb.upsert({
vectors: embeddings.data.map((e, i) => ({
id: `${event.data.documentId}-${i}`,
values: e.embedding,
metadata: { chunk: chunks[i] },
})),
});
});
return { chunks: chunks.length, status: 'indexed' };
}
);
Pitfalls
- Missing serve handler (CRITICAL): Inngest requires a serve handler to receive events. Without
app/api/inngest/route.ts(or framework equivalent), no events will be delivered. - Functions not registered in serve() (ERROR): Every function must be added to the
functionsarray in theserve()call. A function defined but not registered will never execute. - Missing or duplicate function ID (CRITICAL): Every
createFunctioncall must have a uniqueid. Duplicate IDs cause silent overwrites. - waitForEvent without timeout (ERROR):
waitForEventmust include a timeout option:{ timeout: '24h' }. Without it, functions can wait indefinitely. - Payment functions without idempotency (ERROR): Payment-related functions must use
idempotency: 'event.data.orderId'(or equivalent) to prevent duplicate charges. - Step names not descriptive (WARNING): Use kebab-case descriptive names like
'fetch-user'or'send-email'. Generic names make debugging difficult. - No concurrency limit (WARNING): Add
concurrency: { limit: N }to protect downstream services from overload. - No event schemas (WARNING): Define
schemas: new EventSchemas().fromRecord<Events>()for type safety and autocomplete. - Sleep using milliseconds (WARNING): Use duration strings like
'1h','30m','24h'— not raw milliseconds. - No retry policy (WARNING): Add
retries: 3orretries: { attempts: 3, backoff: { ... } }for failure handling. - Blocking sleep instead of step.sleep: Never use
setTimeoutorawait new Promise(resolve => setTimeout(...))inside an Inngest function. Usestep.sleep('name', '24h')for durable, non-blocking waits. - Direct function calls instead of events for fan-out: Use
step.sendEventto trigger other functions rather than calling them directly. This preserves durability and retry semantics.
Verification
Serve handler present:
# Check that the route file exists (Next.js App Router) Test-Path app\api\inngest\route.ts # Expected: TrueAll functions registered:
# List all createFunction IDs Select-String -Path "lib\inngest\functions.ts" -Pattern "id:\s*'" -AllMatches | ForEach-Object { $_.Matches.Value } # Then verify each ID appears in the serve() functions arrayLocal dev server running:
npx inngest-cli@latest dev # Expected: Dashboard available at http://localhost:8288Send a test event:
curl -X POST http://localhost:8288/e/key ` -H "Content-Type: application/json" ` -d '{\"name\":\"user/signed.up\",\"data\":{\"userId\":\"test-123\",\"email\":\"test@example.com\"}}' # Expected: 200 OK with event IDCheck function run in dashboard:
Open
http://localhost:8288in a browser. Verify the function appears with statusCompletedand each step shows its durably stored output.Verify concurrency and retry config:
Select-String -Path "lib\inngest\functions.ts" -Pattern "concurrency|retries|idempotency" # Expected: matches for functions that need them
Related Skills
Works well with: nextjs-app-router, vercel-deployment, supabase-backend, email-systems, ai-agents-architect, stripe-integration.
Delegation Triggers
redis,queue infrastructure,bullmq→bullmq-specialist(Need Redis-based queue with existing infrastructure)saga,compensation,rollback,long-running workflow→temporal-craftsman(Need complex workflow orchestration with compensation)event sourcing,event store,cqrs→event-architect(Need event sourcing patterns)vercel,deploy,production→vercel-deployment(Need deployment configuration)database,schema,data model→supabase-backend(Need database for event data)api,endpoint,route→backend(Need API to trigger events)
Collaboration Workflows
Vercel Background Jobs (inngest, nextjs-app-router, vercel-deployment):
- Define Inngest functions (
inngest) - Set up serve handler in Next.js (
nextjs-app-router) - Configure function timeouts (
vercel-deployment) - Deploy and test (
vercel-deployment)
AI Pipeline (inngest, ai-agents-architect, supabase-backend):
- Design AI workflow steps (
ai-agents-architect) - Implement with Inngest durability (
inngest) - Store results in database (
supabase-backend) - Handle retries for API failures (
inngest)
Webhook Processing (inngest, stripe-integration, backend):
- Receive webhook (
backend) - Send to Inngest with idempotency (
inngest) - Process payment logic (
stripe-integration) - Update application state (
backend)
Email Automation (inngest, email-systems, supabase-backend):
- Trigger event from user action (
inngest) - Schedule drip emails with
step.sleep(inngest) - Send emails with retry (
email-systems) - Track email status (
supabase-backend)
Scheduled Tasks (inngest, backend, analytics-architecture):
- Define cron triggers (
inngest) - Implement processing logic (
backend) - Aggregate and report data (
analytics-architecture) - Handle failures with alerting (
inngest)
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.