Skip to content

Getting started with Inngest in ASP.NET Core

In about five minutes you will have an ASP.NET Core app with a durable function running on your machine. When a user signs up, it sends a welcome email, waits, and sends a second one; if the second email fails, a retry sends only that one. The dev server shows every step as it happens.

Your ASP.NET Core app

localhost:5280, with /api/inngest

Inngest dev server

localhost:8288, with the runs

What you will run: the app sends an event when a user signs up, and the dev server runs the function by calling the app's endpoint once per step. Its UI shows every run.

Install Inngest.AspNetCore from NuGet. It brings in Inngest:

Terminal window
dotnet add package Inngest.AspNetCore

The SDK is split into these packages:

Package What it gives you
Inngest Functions, steps, triggers, flow control, middleware, the client that sends events, and request signing. A source generator that registers your function classes, which a project turns on, and an analyzer that finds the types a Native AOT app has not listed, come with it. See Source generator and analyzers.
Inngest.AspNetCore MapInngest, the endpoint Inngest calls in an ASP.NET Core app, and a health check for readiness probes.
Inngest.AwsLambda InngestLambdaHandler, which serves the endpoint from an AWS Lambda function behind API Gateway or a function URL. See Running on AWS Lambda.
Inngest.AzureFunctions ServeInngestAsync, the endpoint Inngest calls, served from an HTTP-triggered function of an Azure Functions app. See Azure Functions.
Inngest.Blazor A Blazor component that shows realtime messages on the server, in WebAssembly and in interactive Auto. See Realtime in Blazor.
Inngest.Connect AddConnect, a hosted worker that receives calls over an outbound WebSocket, with no endpoint to expose. See Connect.
Inngest.Aspire.Hosting An Aspire hosting integration for the AppHost project: runs the Inngest server as a container and gives projects the settings the SDK reads. See Aspire.
Inngest.Testing An in-memory test host that runs your functions on the real execution engine. See Testing functions.
Inngest.Testing.Containers A Testcontainers module that runs the Inngest dev server or a self-hosted server in Docker for end-to-end tests. See Testing with containers.
Inngest.Testing.Containers.AspNetCore A WebApplicationFactory that runs an ASP.NET Core app on a real port against that server (.NET 10). See Testing with containers.
Inngest.OpenTelemetry Traces and metrics for function calls and steps.
Inngest.Encryption End-to-end encryption of event data, step results and function output. See Encryption.
Inngest.Sentry Reports the errors of functions and steps to Sentry. See Middleware.
Inngest.Extensions.AI Makes the model calls and tool calls of a Microsoft.Extensions.AI IChatClient steps of the run. See AI chat clients.
Inngest.EntityFrameworkCore A transactional outbox for Entity Framework Core: events saved with your data and sent to Inngest afterwards. See Transactional outbox.
Inngest.Templates dotnet new inngest-web and dotnet new inngest-worker, which make a project like the one below. See Templates and tool.
dotnet-inngest A .NET tool: dotnet inngest dev downloads the Inngest CLI and runs the dev server for the app. See Templates and tool.
Inngest.SignalR Forwards Inngest realtime messages to the clients of a SignalR hub. See SignalR.
Inngest.UserContext Carries the signed-in user who caused a run into it, as ICurrentUser. See User context.
Inngest.UserContext.AspNetCore Takes the user an event is sent for from HttpContext.User.
Inngest.Webhooks Verifies the signatures of webhooks from Stripe, GitHub, Shopify, Clerk, Resend and other providers and sends each as an event, without a provider SDK. Inngest.AspNetCore brings it in. See Webhooks.

A function is a class. [InngestFunction] gives it an id and [EventTrigger] names the event that starts it. Each RunAsync is a step: it runs once and Inngest saves its result. SleepAsync pauses the run without holding a thread.

OnboardUser.cs
using Inngest;
public sealed record UserSignedUp(string UserId, string Email);
[InngestFunction("onboard-user")]
[EventTrigger("user/signed-up")]
public sealed class OnboardUser(IEmailSender email) : InngestFunction<UserSignedUp>
{
public override async Task<object?> ExecuteAsync(FunctionContext<UserSignedUp> context, CancellationToken cancellationToken)
{
var user = context.Event.Data;
await context.Step.RunAsync("send-welcome-email",
ct => email.SendAsync(user.Email, "Welcome!", ct));
await context.Step.SleepAsync("wait-before-tips", TimeSpan.FromSeconds(30));
await context.Step.RunAsync("send-tips",
ct => email.SendAsync(user.Email, "Three things to try first", ct));
return new { user.UserId };
}
}

The email service only logs for now:

EmailSender.cs
public interface IEmailSender
{
Task SendAsync(string to, string subject, CancellationToken cancellationToken);
}
public sealed class ConsoleEmailSender(ILogger<ConsoleEmailSender> logger) : IEmailSender
{
public Task SendAsync(string to, string subject, CancellationToken cancellationToken)
{
logger.LogInformation("Sending \"{Subject}\" to {To}", subject, to);
return Task.CompletedTask;
}
}

AddInngest registers the SDK and the function, MapInngest maps the endpoint Inngest calls, and /signup sends the event that starts a run:

Program.cs
using Inngest;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IEmailSender, ConsoleEmailSender>();
builder.Services
.AddInngest(options => options.AppId = "shop")
.AddFunction<OnboardUser>();
var app = builder.Build();
app.MapInngest("/api/inngest");
app.MapPost("/signup", async (SignupRequest request, IInngestClient inngest, CancellationToken cancellationToken) =>
{
var userId = Guid.NewGuid().ToString("N");
await inngest.SendAsync(new InngestEvent("user/signed-up", new UserSignedUp(userId, request.Email)), cancellationToken);
return Results.Accepted(value: new { userId });
});
app.Run();
public sealed record SignupRequest(string Email);

The function’s options are attributes on the class here. They can also be passed in code, as FunctionOptions given to AddFunction, which suits values the app reads from configuration; Functions and triggers shows both forms.

  1. Start the app in dev mode. INNGEST_DEV=1 points the SDK at the local dev server:

    Terminal window
    INNGEST_DEV=1 dotnet run --urls http://localhost:5280
  2. Start the dev server and point it at the app. Use the install script or Homebrew on macOS and Linux, npx when Node.js is installed, Docker when it is not, and Compose when a team shares the setup:

    Terminal window
    curl -sSfL https://cli.inngest.com/install.sh | sh
    inngest dev -u http://localhost:5280/api/inngest
  3. Sign up a user:

    Terminal window
    curl -X POST http://localhost:5280/signup -H 'Content-Type: application/json' -d '{"email": "ada@example.com"}'
  4. Open http://localhost:8288 and select Runs. The run shows send-welcome-email, the sleep, and after 30 seconds send-tips.

Inngest called the app four times for that one run, once per step and once more for the output:

Call 1runs send-welcome-email

send-welcome-email206

Call 2replays it, then sleeps

1 saved resultsleep 30s206

Call 3replays both, runs send-tips

2 saved resultssend-tips206

Call 4replays all three and returns

3 saved resultsreturn200
  • runs the handler
  • returns the saved result
  • sleeps
Every call runs ExecuteAsync from the top. Steps that finished earlier return their saved results at once, so the welcome email goes out once however many calls pass it.

That is why side effects belong in steps and why step ids must not change while runs are in flight. Steps explains both, and How it works lets you step through a run.

inngest-dotnet is a community Inngest SDK for .NET. It is not affiliated with or endorsed by Inngest Inc.