inngest-events
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInngest Events
Inngest 事件
Master Inngest event design and delivery patterns. Events are the foundation of Inngest - learn to design robust event schemas, implement idempotency, leverage fan-out patterns, and handle system events effectively.
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.
掌握Inngest事件设计与交付模式。事件是Inngest的核心基础——学习如何设计健壮的事件schema、实现幂等性、利用扇出模式,并高效处理系统事件。
本技能内容以TypeScript为核心。 对于Python或Go语言,请参考Inngest文档获取语言专属指导。核心概念适用于所有语言。
Event Payload Format
事件负载格式
Every Inngest event is a JSON object with required and optional properties:
每个Inngest事件都是一个JSON对象,包含必填和可选属性:
Required Properties
必填属性
typescript
type Event = {
name: string; // Event type (triggers functions)
data: object; // Payload data (any nested JSON)
};typescript
type Event = {
name: string; // Event type (triggers functions)
data: object; // Payload data (any nested JSON)
};Complete Schema
完整Schema
typescript
type EventPayload = {
name: string; // Required: event type
data: Record<string, any>; // Required: event data
id?: string; // Optional: deduplication ID
ts?: number; // Optional: timestamp (Unix ms)
v?: string; // Optional: schema version
};typescript
type EventPayload = {
name: string; // Required: event type
data: Record<string, any>; // Required: event data
id?: string; // Optional: deduplication ID
ts?: number; // Optional: timestamp (Unix ms)
v?: string; // Optional: schema version
};Basic Event Example
基础事件示例
typescript
await inngest.send({
name: "billing/invoice.paid",
data: {
customerId: "cus_NffrFeUfNV2Hib",
invoiceId: "in_1J5g2n2eZvKYlo2C0Z1Z2Z3Z",
userId: "user_03028hf09j2d02",
amount: 1000,
metadata: {
accountId: "acct_1J5g2n2eZvKYlo2C0Z1Z2Z3Z",
accountName: "Acme.ai"
}
}
});typescript
await inngest.send({
name: "billing/invoice.paid",
data: {
customerId: "cus_NffrFeUfNV2Hib",
invoiceId: "in_1J5g2n2eZvKYlo2C0Z1Z2Z3Z",
userId: "user_03028hf09j2d02",
amount: 1000,
metadata: {
accountId: "acct_1J5g2n2eZvKYlo2C0Z1Z2Z3Z",
accountName: "Acme.ai"
}
}
});Event Naming Conventions
事件命名规范
Use the Object-Action pattern:
domain/noun.verb采用对象-动作模式:
domain/noun.verbRecommended Patterns
推荐模式
typescript
// ✅ Good: Clear object-action pattern
"billing/invoice.paid";
"user/profile.updated";
"order/item.shipped";
"ai/summary.completed";
// ✅ Good: Domain prefixes for organization
"stripe/customer.created";
"intercom/conversation.assigned";
"slack/message.posted";
// ❌ Avoid: Unclear or inconsistent
"payment"; // What happened?
"user_update"; // Use dots, not underscores
"invoiceWasPaid"; // Too verbosetypescript
// ✅ Good: Clear object-action pattern
"billing/invoice.paid";
"user/profile.updated";
"order/item.shipped";
"ai/summary.completed";
// ✅ Good: Domain prefixes for organization
"stripe/customer.created";
"intercom/conversation.assigned";
"slack/message.posted";
// ❌ Avoid: Unclear or inconsistent
"payment"; // What happened?
"user_update"; // Use dots, not underscores
"invoiceWasPaid"; // Too verboseNaming Guidelines
命名指南
- Past tense: Events describe what happened (,
created,updated)failed - Dot notation: Use dots for hierarchy ()
billing/invoice.paid - Prefixes: Group related events (,
api/user.created)webhook/stripe.received - Consistency: Establish patterns and stick to them
- 过去式: 事件描述已发生的动作(、
created、updated)failed - 点标记法: 使用点来表示层级关系(如)
billing/invoice.paid - 前缀分组: 对相关事件进行分组(如、
api/user.created)webhook/stripe.received - 一致性: 建立规范并严格遵循
Event IDs and Idempotency
事件ID与幂等性
When to use IDs: Prevent duplicate processing when events might be sent multiple times.
何时使用ID: 当事件可能被重复发送时,防止重复处理。
Basic Deduplication
基础去重实现
typescript
await inngest.send({
id: "cart-checkout-completed-ed12c8bde", // Unique per event type
name: "storefront/cart.checkout.completed",
data: {
cartId: "ed12c8bde",
items: ["item1", "item2"]
}
});typescript
await inngest.send({
id: "cart-checkout-completed-ed12c8bde", // Unique per event type
name: "storefront/cart.checkout.completed",
data: {
cartId: "ed12c8bde",
items: ["item1", "item2"]
}
});ID Best Practices
ID最佳实践
typescript
// ✅ Good: Specific to event type and instance
id: `invoice-paid-${invoiceId}`;
id: `user-signup-${userId}-${timestamp}`;
id: `order-shipped-${orderId}-${trackingNumber}`;
// ❌ Bad: Generic IDs shared across event types
id: invoiceId; // Could conflict with other events
id: "user-action"; // Too generic
id: customerId; // Same customer, different eventsDeduplication window: 24 hours from first event reception
See inngest-durable-functions for idempotency configuration.
typescript
// ✅ Good: Specific to event type and instance
id: `invoice-paid-${invoiceId}`;
id: `user-signup-${userId}-${timestamp}`;
id: `order-shipped-${orderId}-${trackingNumber}`;
// ❌ Bad: Generic IDs shared across event types
id: invoiceId; // Could conflict with other events
id: "user-action"; // Too generic
id: customerId; // Same customer, different events去重窗口: 从首次接收事件起24小时
更多幂等性配置请参考inngest-durable-functions。
The ts
Parameter for Delayed Delivery
ts用于延迟交付的ts
参数
tsWhen to use: Schedule events for future processing or maintain event ordering.
使用场景: 调度事件在未来执行,或维持事件执行顺序。
Future Scheduling
未来调度示例
typescript
const oneHourFromNow = Date.now() + 60 * 60 * 1000;
await inngest.send({
name: "trial/reminder.send",
ts: oneHourFromNow, // Deliver in 1 hour
data: {
userId: "user_123",
trialExpiresAt: "2024-02-15T12:00:00Z"
}
});typescript
const oneHourFromNow = Date.now() + 60 * 60 * 1000;
await inngest.send({
name: "trial/reminder.send",
ts: oneHourFromNow, // Deliver in 1 hour
data: {
userId: "user_123",
trialExpiresAt: "2024-02-15T12:00:00Z"
}
});Maintaining Event Order
维持事件顺序
typescript
// Events with timestamps are processed in chronological order
const events = [
{
name: "user/action.performed",
ts: 1640995200000, // Earlier
data: { action: "login" }
},
{
name: "user/action.performed",
ts: 1640995260000, // Later
data: { action: "purchase" }
}
];
await inngest.send(events);typescript
// Events with timestamps are processed in chronological order
const events = [
{
name: "user/action.performed",
ts: 1640995200000, // Earlier
data: { action: "login" }
},
{
name: "user/action.performed",
ts: 1640995260000, // Later
data: { action: "purchase" }
}
];
await inngest.send(events);Fan-Out Patterns
扇出模式
Use case: One event triggers multiple independent functions for reliability and parallel processing.
使用场景: 单个事件触发多个独立函数,提升可靠性与并行处理能力。
Basic Fan-Out Implementation
基础扇出实现
typescript
// Send single event
await inngest.send({
name: "user/signup.completed",
data: {
userId: "user_123",
email: "user@example.com",
plan: "pro"
}
});
// Multiple functions respond to same event
const sendWelcomeEmail = inngest.createFunction(
{ id: "send-welcome-email" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
await step.run("send-email", async () => {
return sendEmail({
to: event.data.email,
template: "welcome"
});
});
}
);
const createTrialSubscription = inngest.createFunction(
{ id: "create-trial" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
await step.run("create-subscription", async () => {
return stripe.subscriptions.create({
customer: event.data.stripeCustomerId,
trial_period_days: 14
});
});
}
);
const addToCrm = inngest.createFunction(
{ id: "add-to-crm" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
await step.run("crm-sync", async () => {
return crm.contacts.create({
email: event.data.email,
plan: event.data.plan
});
});
}
);typescript
// Send single event
await inngest.send({
name: "user/signup.completed",
data: {
userId: "user_123",
email: "user@example.com",
plan: "pro"
}
});
// Multiple functions respond to same event
const sendWelcomeEmail = inngest.createFunction(
{ id: "send-welcome-email" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
await step.run("send-email", async () => {
return sendEmail({
to: event.data.email,
template: "welcome"
});
});
}
);
const createTrialSubscription = inngest.createFunction(
{ id: "create-trial" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
await step.run("create-subscription", async () => {
return stripe.subscriptions.create({
customer: event.data.stripeCustomerId,
trial_period_days: 14
});
});
}
);
const addToCrm = inngest.createFunction(
{ id: "add-to-crm" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
await step.run("crm-sync", async () => {
return crm.contacts.create({
email: event.data.email,
plan: event.data.plan
});
});
}
);Fan-Out Benefits
扇出模式优势
- Independence: Functions run separately; one failure doesn't affect others
- Parallel execution: All functions run simultaneously
- Selective replay: Re-run only failed functions
- Cross-service: Trigger functions in different codebases/languages
- 独立性: 函数独立运行,单个函数失败不会影响其他函数
- 并行执行: 所有函数同时运行
- 选择性重放: 仅重新运行失败的函数
- 跨服务触发: 可触发不同代码库/语言中的函数
Advanced Fan-Out with waitForEvent
waitForEvent结合waitForEvent
的高级扇出
waitForEventIn expressions, = the original triggering event, = the new event being matched. See Expression Syntax Reference for full details.
eventasynctypescript
const orchestrateOnboarding = inngest.createFunction(
{ id: "orchestrate-onboarding" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
// Fan out to multiple services
await step.sendEvent("fan-out", [
{ name: "email/welcome.send", data: event.data },
{ name: "subscription/trial.create", data: event.data },
{ name: "crm/contact.add", data: event.data }
]);
// Wait for all to complete
const [emailResult, subResult, crmResult] = await Promise.all([
step.waitForEvent("email-sent", {
event: "email/welcome.sent",
timeout: "5m",
if: `event.data.userId == async.data.userId`
}),
step.waitForEvent("subscription-created", {
event: "subscription/trial.created",
timeout: "5m",
if: `event.data.userId == async.data.userId`
}),
step.waitForEvent("crm-synced", {
event: "crm/contact.added",
timeout: "5m",
if: `event.data.userId == async.data.userId`
})
]);
// Complete onboarding
await step.run("complete-onboarding", async () => {
return completeUserOnboarding(event.data.userId);
});
}
);See inngest-steps for additional patterns including .
step.invoke在表达式中,指原始触发事件,指待匹配的新事件。完整细节请参考表达式语法参考。
eventasynctypescript
const orchestrateOnboarding = inngest.createFunction(
{ id: "orchestrate-onboarding" },
{ event: "user/signup.completed" },
async ({ event, step }) => {
// Fan out to multiple services
await step.sendEvent("fan-out", [
{ name: "email/welcome.send", data: event.data },
{ name: "subscription/trial.create", data: event.data },
{ name: "crm/contact.add", data: event.data }
]);
// Wait for all to complete
const [emailResult, subResult, crmResult] = await Promise.all([
step.waitForEvent("email-sent", {
event: "email/welcome.sent",
timeout: "5m",
if: `event.data.userId == async.data.userId`
}),
step.waitForEvent("subscription-created", {
event: "subscription/trial.created",
timeout: "5m",
if: `event.data.userId == async.data.userId`
}),
step.waitForEvent("crm-synced", {
event: "crm/contact.added",
timeout: "5m",
if: `event.data.userId == async.data.userId`
})
]);
// Complete onboarding
await step.run("complete-onboarding", async () => {
return completeUserOnboarding(event.data.userId);
});
}
);更多包含的模式请参考inngest-steps。
step.invokeSystem Events
系统事件
Inngest emits system events for function lifecycle monitoring:
Inngest会发送系统事件用于函数生命周期监控:
Available System Events
可用系统事件
typescript
// Function execution events
"inngest/function.failed"; // Function failed after retries
"inngest/function.finished"; // Function finished - completed or failed
"inngest/function.cancelled"; // Function cancelled before completiontypescript
// Function execution events
"inngest/function.failed"; // Function failed after retries
"inngest/function.finished"; // Function finished - completed or failed
"inngest/function.cancelled"; // Function cancelled before completionHandling Failed Functions
处理失败的函数
typescript
const handleFailures = inngest.createFunction(
{ id: "handle-failed-functions" },
{ event: "inngest/function.failed" },
async ({ event, step }) => {
const { function_id, run_id, error } = event.data;
await step.run("log-failure", async () => {
logger.error("Function failed", {
functionId: function_id,
runId: run_id,
error: error.message,
stack: error.stack
});
});
// Alert on critical function failures
if (function_id.includes("critical")) {
await step.run("send-alert", async () => {
return alerting.sendAlert({
title: `Critical function failed: ${function_id}`,
severity: "high",
runId: run_id
});
});
}
// Auto-retry certain failures
if (error.code === "RATE_LIMIT_EXCEEDED") {
await step.run("schedule-retry", async () => {
return inngest.send({
name: "retry/function.requested",
ts: Date.now() + 5 * 60 * 1000, // Retry in 5 minutes
data: { originalRunId: run_id }
});
});
}
}
);typescript
const handleFailures = inngest.createFunction(
{ id: "handle-failed-functions" },
{ event: "inngest/function.failed" },
async ({ event, step }) => {
const { function_id, run_id, error } = event.data;
await step.run("log-failure", async () => {
logger.error("Function failed", {
functionId: function_id,
runId: run_id,
error: error.message,
stack: error.stack
});
});
// Alert on critical function failures
if (function_id.includes("critical")) {
await step.run("send-alert", async () => {
return alerting.sendAlert({
title: `Critical function failed: ${function_id}`,
severity: "high",
runId: run_id
});
});
}
// Auto-retry certain failures
if (error.code === "RATE_LIMIT_EXCEEDED") {
await step.run("schedule-retry", async () => {
return inngest.send({
name: "retry/function.requested",
ts: Date.now() + 5 * 60 * 1000, // Retry in 5 minutes
data: { originalRunId: run_id }
});
});
}
}
);Sending Events
发送事件
Client Setup
客户端配置
typescript
// inngest/client.ts
import { Inngest } from "inngest";
export const inngest = new Inngest({
id: "my-app"
});
// You must set INNGEST_EVENT_KEY environment variable in productiontypescript
// inngest/client.ts
import { Inngest } from "inngest";
export const inngest = new Inngest({
id: "my-app"
});
// You must set INNGEST_EVENT_KEY environment variable in productionSingle Event
发送单个事件
typescript
const result = await inngest.send({
name: "order/placed",
data: {
orderId: "ord_123",
customerId: "cus_456",
amount: 2500,
items: [
{ id: "item_1", quantity: 2 },
{ id: "item_2", quantity: 1 }
]
}
});
// Returns event IDs for tracking
console.log(result.ids); // ["01HQ8PTAESBZPBDS8JTRZZYY3S"]typescript
const result = await inngest.send({
name: "order/placed",
data: {
orderId: "ord_123",
customerId: "cus_456",
amount: 2500,
items: [
{ id: "item_1", quantity: 2 },
{ id: "item_2", quantity: 1 }
]
}
});
// Returns event IDs for tracking
console.log(result.ids); // ["01HQ8PTAESBZPBDS8JTRZZYY3S"]Batch Events
批量发送事件
typescript
const orderItems = await getOrderItems(orderId);
// Convert to events
const events = orderItems.map((item) => ({
name: "inventory/item.reserved",
data: {
itemId: item.id,
orderId: orderId,
quantity: item.quantity,
warehouseId: item.warehouseId
}
}));
// Send all at once (up to 512kb)
await inngest.send(events);typescript
const orderItems = await getOrderItems(orderId);
// Convert to events
const events = orderItems.map((item) => ({
name: "inventory/item.reserved",
data: {
itemId: item.id,
orderId: orderId,
quantity: item.quantity,
warehouseId: item.warehouseId
}
}));
// Send all at once (up to 512kb)
await inngest.send(events);Sending from Functions
在函数内发送事件
typescript
inngest.createFunction(
{ id: "process-order" },
{ event: "order/placed" },
async ({ event, step }) => {
// Use step.sendEvent() instead of inngest.send() in functions
// for reliability and deduplication
await step.sendEvent("trigger-fulfillment", {
name: "fulfillment/order.received",
data: {
orderId: event.data.orderId,
priority: event.data.customerTier === "premium" ? "high" : "normal"
}
});
}
);typescript
inngest.createFunction(
{ id: "process-order" },
{ event: "order/placed" },
async ({ event, step }) => {
// Use step.sendEvent() instead of inngest.send() in functions
// for reliability and deduplication
await step.sendEvent("trigger-fulfillment", {
name: "fulfillment/order.received",
data: {
orderId: event.data.orderId,
priority: event.data.customerTier === "premium" ? "high" : "normal"
}
});
}
);Event Design Best Practices
事件设计最佳实践
Schema Versioning
Schema版本控制
typescript
// Use version field to track schema changes
await inngest.send({
name: "user/profile.updated",
v: "2024-01-15.1", // Schema version
data: {
userId: "user_123",
changes: {
email: "new@example.com",
preferences: { theme: "dark" }
},
// New field in v2 schema
auditInfo: {
changedBy: "user_456",
reason: "user_requested"
}
}
});typescript
// Use version field to track schema changes
await inngest.send({
name: "user/profile.updated",
v: "2024-01-15.1", // Schema version
data: {
userId: "user_123",
changes: {
email: "new@example.com",
preferences: { theme: "dark" }
},
// New field in v2 schema
auditInfo: {
changedBy: "user_456",
reason: "user_requested"
}
}
});Rich Context Data
丰富的上下文数据
typescript
// Include enough context for all consumers
await inngest.send({
name: "payment/charge.succeeded",
data: {
// Primary identifiers
chargeId: "ch_123",
customerId: "cus_456",
// Amount details
amount: 2500,
currency: "usd",
// Context for different consumers
subscription: {
id: "sub_789",
plan: "pro_monthly"
},
invoice: {
id: "inv_012",
number: "INV-2024-001"
},
// Metadata for debugging
paymentMethod: {
type: "card",
last4: "4242",
brand: "visa"
},
metadata: {
source: "stripe_webhook",
environment: "production"
}
}
});Event design principles:
- Self-contained: Include all data consumers need
- Immutable: Never modify event schemas after sending
- Traceable: Include correlation IDs and audit trails
- Actionable: Provide enough context for business logic
- Debuggable: Include metadata for troubleshooting
typescript
// Include enough context for all consumers
await inngest.send({
name: "payment/charge.succeeded",
data: {
// Primary identifiers
chargeId: "ch_123",
customerId: "cus_456",
// Amount details
amount: 2500,
currency: "usd",
// Context for different consumers
subscription: {
id: "sub_789",
plan: "pro_monthly"
},
invoice: {
id: "inv_012",
number: "INV-2024-001"
},
// Metadata for debugging
paymentMethod: {
type: "card",
last4: "4242",
brand: "visa"
},
metadata: {
source: "stripe_webhook",
environment: "production"
}
}
});事件设计原则:
- 自包含: 包含所有消费者所需的数据
- 不可变: 事件发送后绝不能修改其schema
- 可追踪: 包含关联ID与审计轨迹
- 可执行: 提供足够上下文以支撑业务逻辑
- 可调试: 包含用于故障排查的元数据