Skip to content

Native AOT and trimming

The SDK is trimming and AOT compatible: its own types use source-generated System.Text.Json metadata and it does not rely on reflection. Your types are the only thing left to cover. Everything the SDK serializes for you, event data, step results, invoke payloads and function output, goes through InngestOptions.SerializerOptions, and in a trimmed or AOT app that needs a source-generated JsonSerializerContext that knows your types.

Turn on Native AOT in the project:

Shop.csproj
<PropertyGroup>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Declare a context with every type that crosses the wire, and put it first in the resolver chain:

Program.cs
using System.Text.Json.Serialization;
using Inngest;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services
.AddInngest(options =>
{
options.AppId = "shop";
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJson.Default);
})
.AddFunction<Greeting>(new FunctionOptions { Id = "greet", Triggers = [Trigger.Event("demo/greet")] },
async (context, cancellationToken) =>
{
var reply = await context.Step.RunAsync("compose", () => new Reply($"Hello, {context.Event.Data.Name}!"));
await context.Step.SleepAsync("pause", TimeSpan.FromSeconds(1));
return reply;
});
var app = builder.Build();
app.MapInngest();
app.Run();
public sealed record Greeting(string Name);
public sealed record Reply(string Text);
[JsonSerializable(typeof(Greeting))]
[JsonSerializable(typeof(Reply))]
internal sealed partial class AppJson : JsonSerializerContext;

samples/Inngest.AotSample in the repository is this app. It publishes with no trimming or AOT warnings and runs against the dev server as a native binary:

Terminal window
dotnet publish -c Release -r linux-x64

List every type that the SDK serializes or deserializes for your code:

  • the event data type of every function, TEventData;
  • the result type of every RunAsync, the T in Task<T>;
  • the type each function returns as its output;
  • the TData of WaitForEventAsync<TData>, and the TResult and data of InvokeAsync;
  • the runtime types of Data and User in the events you send;
  • for each function that runs the chunks of a fan-out, FanOutChunk<TItem> and FanOutOutcome<TResult>[]. The methods return arrays, and the engine writes a function’s output by its runtime type, so a function that returns what MapAsync gave lists TResult[].

The SDK covers its own public types, such as FunctionFailure, SendEventsResult, JsonElement and object, so a step without a result or an untyped event needs nothing extra.

Anonymous types cannot be source generated, so use records where the other guides use new { ... }. A type the context does not know fails at run time with a NotSupportedException from System.Text.Json, in the step or call that serializes it; the publish warnings will not catch it, so test the paths that matter.

A forgotten type is the one thing the publish warnings do not catch. The Inngest package therefore includes an analyzer that runs in a project which turns reflection-based JSON off: PublishAot or PublishTrimmed set to true, or JsonSerializerIsReflectionEnabledByDefault set to false. It warns about every type of the list above that no context lists, for the event data and the result of your function classes, the event data of a delegate function and the types the steps save, such as the result of each RunAsync<T>:

warning INNGEST010: 'Receipt' is the result of function 'OnRefund', but no JsonSerializerContext lists it with
[JsonSerializable], and reflection-based JSON is turned off

In the IDE, a code fix adds the [JsonSerializable] attribute to your context. It is an analyzer and not a generator that fills the context, because the generator of System.Text.Json does not see code that another generator writes. Source generator and analyzers says what the analyzer checks and what it leaves to you, and covers the generator that registers the function classes.

MapInngestWebhook needs nothing added to the context. The payload of a webhook is a JsonElement, which the SDK covers, so the event goes out as the provider sent it, and the verifiers use System.Security.Cryptography without reflection. What stays yours is the data type of the function that receives the event, as for any event, and the type a Transform puts in Data instead of the payload, such as a JsonObject with a header added. An app that maps endpoints for Stripe, GitHub, Shopify, Clerk and an HMAC provider publishes as a Native AOT binary without warnings, and verifies and sends their webhooks. samples/Inngest.AotSample maps a Stripe endpoint, so the publish in CI covers the package.

AddFunction<TFunction> and AddMiddleware<TMiddleware> keep the constructors they need through DynamicallyAccessedMembers, so class-based functions and middleware work in AOT apps without extra configuration. AddInngestFunctions(), which the source generator writes once a project sets InngestGenerateFunctionRegistration to true, calls AddFunction<TFunction> for each class that has [InngestFunction], so the app lists no class and nothing scans for them. The AOT sample does this.

Attribute classes work too: AddFunction<TFunction>() reads the attributes of the class it is given when the app starts, the generic ones such as [EventTrigger<T>] included, which needs no reflection beyond what trimming keeps. Typed events need nothing either, since their names are static members. The AOT sample registers an attribute class, sends it a typed event and invokes another function by its class, and publishes without warnings. Settings from a configuration section, AddInngest(builder.Configuration.GetSection("Inngest")), are read key by key, not through the configuration binder, so they work in AOT apps as well.

Inngest.EntityFrameworkCore is the exception among the packages. Entity Framework Core is not trimming or Native AOT compatible, and the outbox runs on it, so the package does not declare itself compatible. Its code has no trim or AOT warning, and the events it stores are serialized from the same InngestOptions.SerializerOptions metadata as every other event, but an app that publishes with Native AOT cannot use it. See Transactional outbox.

Microsoft’s Native AOT deployment and source generation docs cover the general rules.

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