当服务器需要"稍后执行某项操作"时,过去你需要搭建 Redis、BullMQ、一个 worker 进程以及一整套部署方案。Inngest 用一个函数和一个事件取代了这一切。内置持久化重试,无需管理基础设施,支持 serverless。
这些操作应该在用户请求返回之后执行:
传统方案:Redis + BullMQ + worker 进程。现代 serverless 方案:Inngest、Trigger.dev ↗ 或 Temporal ↗。本教程选用 Inngest,因为它的开发者体验对初学者最友好。
前往 app.inngest.com ↗ 注册。免费套餐:每月 10 万次事件 + 1K 并发运行——对早期项目来说完全够用。
控制台 → Apps → 首次连接时会自动创建你的应用。后面我们会回到这里。
Inngest 函数就是普通的异步函数,由命名事件触发。
// inngest/functions.js
import { Inngest } from "inngest";
export const inngest = new Inngest({ id: "my-app" });
export const sendWelcomeEmail = inngest.createFunction(
{ id: "send-welcome-email" },
{ event: "user/signed-up" },
async ({ event, step }) => {
const { email } = event.data;
// Each `step.run` is durable — retried independently on failure
const customer = await step.run("create-customer", async () => {
return createStripeCustomer(email);
});
await step.run("send-email", async () => {
return sendResendEmail({
to: email,
subject: "Welcome",
body: `Hi! Customer ID: ${customer.id}`,
});
});
}
);
添加一个 HTTP 端点,让 Inngest 调用你的函数。各框架写法如下:
Next.js(App Router):
// app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest, sendWelcomeEmail } from "@/inngest/functions";
export const { GET, POST, PUT } = serve({
client: inngest,
functions: [sendWelcomeEmail],
});
Express:
import { serve } from "inngest/express";
import express from "express";
const app = express();
app.use(express.json());
app.use("/api/inngest", serve({
client: inngest,
functions: [sendWelcomeEmail],
}));
完整框架列表:Serving functions ↗。Vercel、Cloudflare Workers、Hono、Fastify、Remix、Astro、NestJS、AWS Lambda——均已支持。
在应用的任意位置调用——API 路由、webhook 处理器、cron 任务都行:
import { inngest } from "@/inngest/functions";
await inngest.send({
name: "user/signed-up",
data: { email: "[email protected]" },
});
立即返回,只需几毫秒。用户请求快速响应,后续执行交给 Inngest。
Inngest 内置本地开发服务器——无需 Docker,直接在终端运行:
npx inngest-cli@latest dev
访问 http://localhost:8288。开发服务器会自动发现你的 /api/inngest 端点,列出所有函数,支持从 UI 触发事件,并以步骤为单位展示执行追踪和重试行为。全程不离开本机,实时调试。
部署应用后,在 Inngest 控制台 → Apps → Sync new app → 粘贴以 /api/inngest 结尾的生产 URL。Inngest 会自动探测并发现你的函数,随后开始路由事件。
在生产环境添加以下环境变量:
INNGEST_EVENT_KEY=... # for inngest.send() to authenticate
INNGEST_SIGNING_KEY=... # for Inngest to verify it's calling YOUR app
两个密钥均可在控制台的 Settings → Keys 中查看。
休眠 / 延迟。在函数内等待数小时甚至数天;等待期间 Inngest 不会让你的服务器保持常驻。
await step.sleep("wait-a-day", "24h");
await step.run("send-reminder", () => sendEmail(...));
等待事件。暂停执行,直到特定事件到达。适合"用户 24 小时内未确认则发送提醒"的场景。
const confirmation = await step.waitForEvent("wait-for-confirm", {
event: "user/email-confirmed",
timeout: "24h",
match: "data.userId",
});
if (!confirmation) {
await step.run("send-nudge", () => sendNudgeEmail(...));
}
定时任务(cron)。
export const dailyDigest = inngest.createFunction(
{ id: "daily-digest" },
{ cron: "0 8 * * *" }, // every day at 08:00 UTC
async ({ step }) => {
await step.run("send-digest", () => sendDigestToAllUsers());
}
);
广播(Fan-out)。一个事件触发 N 个并行函数:
// Two functions, both listening for the same event
export const a = inngest.createFunction({ id: "a" }, { event: "order/paid" }, ...);
export const b = inngest.createFunction({ id: "b" }, { event: "order/paid" }, ...);
// One send triggers both
await inngest.send({ name: "order/paid", data: {...} });
并发限制 + 限流。限制同时执行的运行数量(例如针对有速率限制的第三方 API):
inngest.createFunction(
{
id: "process-image",
concurrency: { limit: 5 }, // at most 5 at once globally
throttle: { limit: 100, period: "1m" }, // and no more than 100/min
},
{ event: "image/uploaded" },
async ({ event, step }) => { /* ... */ }
);
如果某个 step.run 抛出异常,Inngest 会以指数退避方式自动重试(默认 4 次,约 10 分钟内完成)。已成功的步骤不会重新执行——只有失败的步骤才会重试。可按函数配置:
inngest.createFunction(
{ id: "syncs", retries: 10 },
{ event: "user/created" },
async ({ event, step }) => { ... }
);
抛出 NonRetriableError 可立即终止,不再重试(例如输入无效——重试也没有意义)。
step.run 包裹——在 step.run 外部的代码每次重试都会重新执行。数据库写入、API 调用等副作用必须放在 step 内部。step.run,Inngest 会将它们作为独立调用执行。user/signed-up、order/paid、image/uploaded 这样的事件命名。函数响应事件,事件就是 API。这种思维方式便于推理,但需要一点时间内化。事件指南 ↗。
免费套餐:每月 10 万次事件 + 1K 并发运行,早期项目绰绰有余。付费版起价 $50/月。Inngest 定价 ↗。
自托管:Inngest 开源了社区版 ↗。生产环境自托管需要一定运维投入。