inngest
semver
>=3.0.0 <5.0.0postconditions5functions5last verified2026-04-13coverage score100%Postconditions — what we check
- send · send-no-try-catcherrorWheninngest.send() called in an async context without a surrounding try/catch block. Network failures, authentication errors (invalid/missing INNGEST_EVENT_KEY), rate limit (429), and Inngest API errors all throw unhandled exceptions.Throws
Network errors: ECONNREFUSED, ETIMEDOUT, fetch failures when Inngest API unreachable. Auth errors: thrown when eventKey / INNGEST_EVENT_KEY is missing or invalid. API errors: thrown on 4xx/5xx responses from the Inngest API.Required handlingCaller MUST wrap inngest.send() in try/catch (or equivalent .catch()). Minimum handling: try { await inngest.send({ name: "app/event.name", data: { ... } }); } catch (error) { // Log the failure — event not sent, functions will not trigger logger.error("Failed to send Inngest event", { error }); } For critical workflows (payment confirmation, user provisioning), consider adding retry logic or a queue to ensure event delivery under transient failures. Note: serve() from inngest/next, inngest/hono, etc. does NOT need try/catch — it is a factory function, not a network call.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - step.waitForEvent · wait-for-event-null-not-checkederrorWhenstep.waitForEvent() result is used without a null check before accessing properties. When the timeout elapses, the function returns null instead of an event payload. Accessing event.data, event.name, or any property on the null result throws a TypeError at runtime.Throws
TypeError: Cannot read properties of null (reading 'data') — thrown when the waitForEvent result is accessed without checking for null first. This is a silent failure mode because the timeout is a normal operational condition (event simply wasn't received in time).Required handlingCallers MUST null-check the result before accessing any properties: const event = await step.waitForEvent("wait-for-approval", { event: "app/approval.submitted", timeout: "1h", }); if (!event) { // Timeout — handle the "no response" case await step.run("handle-timeout", async () => { await sendTimeoutNotification(); }); return { status: "timed_out" }; } // Safe to access event.data here const approved = event.data.approved;costmediumin prodimmediate exceptionusers seedegraded performancevisibilitysilent - step.invoke · invoke-no-try-catchwarningWhenstep.invoke() called without a surrounding try/catch block inside an Inngest function handler. Invocation failures throw NonRetriableError — if uncaught, this terminates the entire calling function and marks it as failed. The docs explicitly recommend wrapping step.invoke() in try/catch to handle rate limiting and other failure scenarios.Throws
NonRetriableError — thrown in all failure scenarios to prevent compounding retries in function chains. Specific cases: - Function ID not found - Invoked function exhausts all retries - Timeout duration exceeded (invoked function continues running) - Invoked function is rate-limited (skipped) - Invoked function is debounced (skipped after timeout)Required handlingCallers should wrap step.invoke() in try/catch when failure is recoverable: try { const result = await step.invoke("invoke-processing-fn", { function: processingFunction, data: { id: payload.id }, timeout: "30 mins", }); return { success: true, result }; } catch (error) { // NonRetriableError — the invoked function failed or was skipped await step.run("handle-invocation-failure", async () => { await notifyFailure(payload.id, error.message); }); return { success: false }; } Note: step.invoke throws NonRetriableError specifically to prevent exponential retry multiplication across nested function chains.costmediumin prodsilent failureusers seedegraded performancevisibilitysilent - step.run · step-run-step-error-uncaughtwarningWhenstep.run() is called in a function handler without awareness that after all retries are exhausted a StepError is thrown. Without try/catch around step.run(), step failures that exhaust retries mark the entire function as permanently failed with no opportunity for rollback or graceful recovery. This is particularly impactful for multi-step workflows where earlier steps have already committed side effects.Throws
StepError (available in TypeScript SDK v3.12.0+) — thrown after a step exhausts all configured retries. Extends Error with step-specific context. If StepError propagates uncaught, the Inngest function is marked as failed and the onFailure handler fires (if configured).Required handlingFor multi-step workflows with side effects, wrap critical steps in try/catch to enable rollback or fallback behavior: try { await step.run("charge-payment", async () => { await stripe.charges.create({ amount: 1000 }); }); } catch (err) { // StepError: payment step exhausted all retries await step.run("rollback-order", async () => { await cancelOrder(orderId); }); return { status: "payment_failed" }; } Alternatively, use .catch() chaining for inline rollbacks: await step.run("create-record", async () => { ... }) .catch((err) => step.run("rollback-record", async () => { ... })); Note: for simple single-step functions, uncaught StepError is acceptable if the onFailure handler is configured to handle all terminal failures.costmediumin prodsilent failureusers seelost datavisibilityvisible - step.sendEvent · step-send-event-not-awaitedwarningWhenstep.sendEvent() is called without await or a Promise handler inside an Inngest function handler. The documentation explicitly states this MUST be awaited. Without await, the function execution continues before the event is confirmed sent, breaking the function's execution flow and potentially causing the event to never be delivered.Throws
Silent failure: the event send may be incomplete or never executed if the function completes before the Promise resolves. In retry scenarios, the non-awaited call may cause duplicate or missed event delivery since memoization only works correctly with awaited calls.Required handlingAlways await step.sendEvent(): await step.sendEvent("emit-fan-out-events", [ { name: "app/user.welcome-email", data: { userId } }, { name: "app/user.setup-workspace", data: { userId } }, ]); Use inside function handlers for fan-out patterns. Do NOT use inngest.send() inside function handlers — it is not memoized and will re-send on retries.costlowin prodsilent failureusers seedegraded performancevisibilitysilent
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]inngest.com/docs/referencehttps://www.inngest.com/docs/reference/typescript/events/send
- [2]github.com/documenso/documensohttps://github.com/documenso/documenso/blob/main/packages/lib/jobs/client/inngest.ts
- [3]inngest.com/docs/getting-startedhttps://www.inngest.com/docs/getting-started/nextjs-quick-start
- [4]inngest.com/docs/referencehttps://www.inngest.com/docs/reference/functions/step-wait-for-event
- [5]inngest.com/docs/learnhttps://www.inngest.com/docs/learn/inngest-steps
- [6]inngest.com/docs/referencehttps://www.inngest.com/docs/reference/functions/step-invoke
- [7]inngest.com/docs/referencehttps://www.inngest.com/docs/reference/typescript/functions/errors
- [8]inngest.com/docs/featureshttps://www.inngest.com/docs/features/inngest-functions/error-retries/rollbacks
- [9]inngest.com/docs/referencehttps://www.inngest.com/docs/reference/functions/step-run
- [10]inngest.com/docs/referencehttps://www.inngest.com/docs/reference/functions/step-send-event
Need a different package?
Request a profile