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
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.
Set up a worker
Section titled “Set up a worker”Install Inngest.Connect from NuGet. It brings in Inngest:
dotnet add package Inngest.ConnectThen call AddConnect instead of mapping an endpoint. A worker needs no web server at all; the generic host is
enough:
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.
Options
Section titled “Options”| 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.
Run it locally
Section titled “Run it locally”The dev server runs a Connect gateway on port 8289. Publish that port as well as 8288, and start the worker in dev mode:
curl -sSfL https://cli.inngest.com/install.sh | shinngest dev --no-discoverybrew install inngest/tap/inngestinngest dev --no-discoverynpx --ignore-scripts=false inngest-cli@latest dev --no-discoverydotnet tool install --global dotnet-inngestdotnet inngest dev --no-discoveryThe tool downloads the Inngest CLI once, checks its SHA-256 and runs it.
docker run --rm -p 8288:8288 -p 8289:8289 inngest/inngest:v1.49.0 inngest dev --no-discoveryservices: inngest: image: inngest/inngest:v1.49.0 command: inngest dev --no-discovery ports: - "8288:8288" - "8289:8289"docker compose upINNGEST_DEV=1 dotnet runThe 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.
What the worker does
Section titled “What the worker does”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.
Shutting down
Section titled “Shutting down”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.