# How Inngest runs .NET functions

> How durable execution works with the Inngest .NET SDK. Inngest calls your ASP.NET Core endpoint once per step, the function replays saved step results and answers 206 or 200. Step through a real run.

Web page: https://damikun.github.io/inngest-dotnet/how-it-works/

Inngest runs a durable function by calling your app once per step. Every call runs the function from the top, and
the steps that already finished return their saved results, so the function picks up where it stopped.

One run of a checkout function, call by call:

1. Nothing is saved yet, so `charge` runs. The app answers 206 with its result, and Inngest saves it.
2. `charge` returns its saved result at once, without charging again, and `send-receipt` runs.
3. Both finished steps return their saved results. The sleep is new: the app reports it, and Inngest calls again in 30 minutes.
4. Thirty minutes later, every finished step returns its saved result, and `ship` runs.
5. Every step returns its saved result, the function returns, and the app answers 200. The run is complete.

## The loop

1. **Inngest calls your app.** It sends a request to `/api/inngest`, or to a
   [Connect](https://damikun.github.io/inngest-dotnet/guides/connect.md) worker, with the saved result of every finished step.

2. **The function runs from the top.** A step that finished in an earlier call returns its saved result at once.
   It does not run again.

3. **The next step runs.** The first step without a saved result runs, or the run reports a sleep or a wait.

4. **The app answers.** `206` with the step's result, which Inngest saves before it calls again, now or when the
   sleep is over. `200` with the output once the function returns.

## Who does what

Your code sends the events, Inngest stores the run and decides when to call, and the app keeps no state between
calls, so any instance can take any call.

## Step through a real run

One run of `FulfillOrder`, call by call: what Inngest sent, which steps had saved results, and what the SDK
answered. The answers are what the SDK in this repository returns; the site's snippet check fails if they ever
stop matching.

```csharp
public sealed class FulfillOrder(
    IInventory inventory, IPayments payments, IMailer mailer, IWarehouse warehouse)
    : InngestFunction<OrderPlaced>
{
    public override async Task<object?> ExecuteAsync(
        FunctionContext<OrderPlaced> context, CancellationToken cancellationToken)
    {
        var order = context.Event.Data;

        var reservation = await context.Step.RunAsync("reserve-stock",
            ct => inventory.ReserveAsync(order.OrderId, ct));

        var payment = await context.Step.RunAsync("charge-card",
            ct => payments.ChargeAsync(order.CustomerId, order.Total, ct));

        await context.Step.SleepAsync("cooling-off", TimeSpan.FromMinutes(30));

        await Task.WhenAll(
            context.Step.RunAsync("send-receipt",
                ct => mailer.SendReceiptAsync(order.OrderId, payment.PaymentId, ct)),
            context.Step.RunAsync("ship-order",
                ct => warehouse.ShipAsync(reservation.ReservationId, ct)));

        return new OrderFulfilled(order.OrderId, payment.PaymentId);
    }
}
```

| Call | Inngest sends | The SDK answers |
| - | - | - |
| 1 | no saved results | `206` with a `StepRun` op for `reserve-stock` |
| 2 | the saved results of `reserve-stock` | `206` with a `StepRun` op for `charge-card` |
| 3 | the saved results of `reserve-stock`, `charge-card` | `206` with a `Sleep` op for `cooling-off` |
| 4 | the saved results of `reserve-stock`, `charge-card`, `cooling-off` | `206` with a `StepPlanned` op for `send-receipt` and a `StepPlanned` op for `ship-order` |
| 5 | the saved results of `reserve-stock`, `charge-card`, `cooling-off` and a request to run `send-receipt` | `206` with a `StepRun` op for `send-receipt` |
| 6 | the saved results of `reserve-stock`, `charge-card`, `cooling-off` and a request to run `ship-order` | `206` with a `StepRun` op for `ship-order` |
| 7 | the saved results of `reserve-stock`, `charge-card`, `cooling-off`, `send-receipt`, `ship-order` | `200` with the output `{"orderId":"ord_1042","paymentId":"pay_31d9"}` |

An interactive version is on [the web page](https://damikun.github.io/inngest-dotnet/how-it-works/).

## What the SDK answers

| The function | The SDK answers |
| - | - |
| reaches one new `RunAsync` step | runs it and answers `206` with a `StepRun` op and its result |
| starts several new steps together | runs none and answers `206` with a `StepPlanned` op for each; Inngest runs each in its own call |
| reaches a sleep, a wait for an event or an invocation | answers `206` with that op; Inngest carries it out |
| returns | answers `200` with the output |
| throws | answers `500` if Inngest should retry, `400` if the failure is final |

With [streaming](https://damikun.github.io/inngest-dotnet/guides/configuration.md#behind-a-proxy) on, a call that runs longer than three seconds
is answered with a `201` at once, and the same answer follows inside the stream when the call ends. The
[design document](https://damikun.github.io/inngest-dotnet/internals/design.md#protocol) lists every field.

## What it means for your code

- **Code outside steps runs on every call.** Side effects and anything that differs between calls, such as the
  clock or a new id, belong in a step. See [Steps](https://damikun.github.io/inngest-dotnet/guides/steps.md).
- **Step ids identify results.** Keep them the same while runs are in flight.
- **Results are JSON.** Return types that round-trip through `System.Text.Json`, such as records.
- **Nothing waits in your app.** A week-long sleep is a timestamp in Inngest; deploys and restarts do not affect it.

## Going deeper

- [Parallel steps](https://damikun.github.io/inngest-dotnet/guides/parallel-steps.md) explains the planned steps in call 4 of the run above.
- [Errors and retries](https://damikun.github.io/inngest-dotnet/guides/errors-and-retries.md) covers what happens when a step throws.
- Inngest's [durable execution concepts](https://www.inngest.com/docs/durable-execution/concepts) describe the
  model for all of its SDKs.
- The [order fulfillment example](https://damikun.github.io/inngest-dotnet/examples/order-fulfillment.md) is a production-style version of
  the function above.
