Skip to main content

Steps

A handler can use every one of Inngest's step tools through context.getStep():

@InngestEventPattern('orders.placed')
async placed(@Payload() order: Order, @Ctx() context: InngestContext) {
const step = context.getStep();

const reservation = await step.run('reserve-stock', () => this.stock.reserve(order));

const payment = await step.waitForEvent('wait-for-payment', {
event: 'orders.paid',
timeout: '1d',
if: `async.data.orderId == '${order.id}'`,
});
if (!payment) {
await step.run('release-stock', () => this.stock.release(reservation));
return { cancelled: true };
}

await step.sleep('cool-off', '1h');
await step.run('ship', () => this.shipping.ship(order));
return { shipped: true };
}

How it runs​

A function with steps is invoked many times — once per step, roughly. Each time, the steps that already ran are not run again: their results are replayed from what Inngest stored. So:

  • Each step runs once, even if a later step fails and the function is retried.
  • Everything outside a step runs on every invocation. Keep side effects inside step.run.
  • Each invocation goes through the Nest pipeline — guards, interceptors and pipes run every time, so keep them free of side effects too, and cheap.
  • A step's id is its identity. Two steps with the same id in one run are one step; renaming a step in code that is running in production restarts it.

What step results are​

A step's return value is serialized to JSON and back. A Date comes back as a string, a class instance as a plain object. Return plain data from a step.

Sending events from a step​

step.sendEvent sends from inside a function and records the send as a step, so a retry does not send it twice:

await step.sendEvent('notify', { name: 'orders.confirmed', data: { id: order.id } });

Prefer it to the InngestClientProxy inside a handler: an emit() outside a step would be sent again on every invocation.