Skip to content

Connect: workers without a public endpoint

With Connect, the app opens a WebSocket to Inngest and receives work over it, instead of exposing an HTTP endpoint for Inngest to call. That suits workers behind a firewall or a NAT, apps without a public URL, and long-running processes such as a container that should not also be a web server.

The functions are the same. Only the way they are served changes.

HTTP endpoint, with MapInngest

Inngest

calls the app

ASP.NET Core app

needs a public URL

Connect worker, with AddConnect

Inngest

Connect gateway

Worker

private network, no inbound port

With MapInngest, Inngest calls the app over HTTPS, so the app must be reachable. With Connect, the worker opens a WebSocket to Inngest and receives work over it, so nothing has to reach the worker from outside.

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

Terminal window
dotnet add package Inngest.Connect

Then call AddConnect instead of mapping an endpoint. A worker needs no web server at all; the generic host is enough:

Program.cs
using Inngest;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>();
builder.Services
.AddInngest(options => options.AppId = "shop-worker")
.AddFunction<OnboardUser>()
.AddConnect(connect =>
{
connect.InstanceId = Environment.GetEnvironmentVariable("HOSTNAME");
connect.MaxWorkerConcurrency = 20;
});
await builder.Build().RunAsync();

Functions are defined and registered the same way for a worker as for an endpoint, with their options as attributes on the class or in code. AddConnect registers a hosted service. It connects when the host starts and syncs the app through the connection, so there is no URL to sync and nothing to call PUT on. An ASP.NET Core app can use AddConnect too, alongside its own endpoints; it then needs no MapInngest.

Option Variable Meaning
InstanceId Identifies this worker to Inngest, which tracks leases and capacity per instance. Keep it stable across restarts of one worker and unique between workers. Defaults to the host name, as in the TypeScript SDK; workers that share a host, such as several processes, need an id each.
MaxWorkerConcurrency INNGEST_CONNECT_MAX_WORKER_CONCURRENCY The most executions Inngest sends to this worker at once. Unlimited by default.
GatewayUrl INNGEST_CONNECT_GATEWAY_URL A ws:// or wss:// URL to dial instead of the gateway Inngest assigns, for when a proxy or port mapping sits in between.

The app id, keys, environment and Inngest’s address come from InngestOptions as usual; see Configuration. In cloud mode the worker needs a signing key and refuses to start without one.

The dev server runs a Connect gateway on port 8289. Publish that port as well as 8288, and start the worker in dev mode:

Terminal window
curl -sSfL https://cli.inngest.com/install.sh | sh
inngest dev --no-discovery
Terminal window
INNGEST_DEV=1 dotnet run

The app appears in the dev server’s Apps page as soon as the worker connects. --no-discovery stops the dev server from looking for HTTP apps, which a worker does not have.

In an Aspire AppHost, AddInngest runs the dev server for you, and WithReference gives the worker the address of the gateway, which Aspire publishes on a port of its own.

Once connected, the worker:

  • receives each call as a message, acknowledges it and runs it through the same execution engine as an HTTP call, so functions behave the same either way;
  • extends the call’s lease while it runs, so Inngest does not hand it to another worker;
  • sends heartbeats, and reconnects with backoff, from one second up to five minutes, when the connection drops;
  • opens a new connection before closing the old one when Inngest drains a gateway;
  • switches to the fallback signing key when Inngest refuses the current one.

A slow function never holds up heartbeats or lease extensions: the socket has its own reader and writer, and functions run on the thread pool.

When the host stops, the worker tells Inngest to send it no more work, waits for running calls to finish, and closes the connection. Calls still running when the host’s shutdown timeout passes are cancelled through their CancellationToken, and Inngest retries them elsewhere. Give the host enough time for your longest step:

builder.Services.Configure<HostOptions>(options => options.ShutdownTimeout = TimeSpan.FromMinutes(2));

On platforms that stop containers with SIGTERM, such as Kubernetes or ECS, set the platform’s grace period a little longer than that timeout.

The Connect worker example is a complete worker with a Dockerfile.

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