Background jobs for .NET that say what they promise

Write a job to PostgreSQL, SQLite or Redis. A worker in your app, or in its own process, picks it up and runs your handler. Schedules, retries, rate limits, a dashboard and a CLI come with it.

[JobName("invoice.send")]
public sealed record SendInvoice(Guid InvoiceId) : IQueueJob;

public sealed class SendInvoiceHandler(IBilling billing)
    : IJobHandler<SendInvoice>
{
    // May run more than once: pass a stable key.
    public Task HandleAsync(SendInvoice job, CancellationToken ct) =>
        billing.SendAsync(job.InvoiceId, idempotencyKey:
            job.InvoiceId.ToString(), ct);
}

Built for jobs that matter

A lost welcome email is an annoyance. A lost invoice is not. Runwick is careful about the gap between the two, and tells you exactly where its guarantees stop.

It says what it promises

Delivery is at-least-once, cancellation is cooperative, and the docs list what Runwick does not claim. The one exactly-once case is completing a job in the same transaction as your own work.

One API, three stores

Jobs, handlers and registration are the same everywhere. Moving from SQLite on a laptop to PostgreSQL in production changes one line.

Operations are part of the library

A management HTTP API, a web dashboard and a runwick command line share one permission model and one audit trail. There is no anonymous mode.

Observable from day one

OpenTelemetry traces and metrics with fixed names, structured logs, health checks per role, a Grafana dashboard and Prometheus alert rules in the box.

What Runwick promises, and what it does not

invoice.send

2 attempts · max 3

  1. pending09:14:02.118

    Dispatched. The store confirmed the write.

  2. running09:14:02.164

    Attempt 1 on worker web-2

  3. retrying09:14:03.902

    Billing API answered 503. Next attempt in about 5 s.

  4. running09:14:09.217

    Attempt 2 on worker web-1, same idempotency key

  5. succeeded09:14:09.629

    One invoice sent. The retry did not send a second.

At-least-once means attempt 2 can happen. The idempotency key is what makes it harmless.

See every job

The dashboard ships inside the Runwick.Dashboard package, with no Node at runtime. It shows each job's status history, attempts and the resources they used, and lets operators cancel, retry and pause queues with the same permissions as the API.

About the dashboard
The Runwick dashboard showing a job's details, status history and the resources each attempt used

How it works

  1. 1

    Dispatch

    Your app calls DispatchAsync. It returns only after the store has confirmed the write, so a job you were told about is a job that exists.

  2. 2

    Store

    The job waits in PostgreSQL, SQLite or Redis with its queue, priority, schedule and limits. Nothing lives only in memory.

  3. 3

    Run

    A worker, in your app or a separate process, leases the job, runs your handler and records the outcome. Failures retry with backoff.

Working with an AI agent?

The whole documentation is available as plain text, and the runwick CLI has an agent mode with stable, machine-readable output.