Background jobs, scheduling, and workflows for .NET on the database you already run.

BackWave handles your typed jobs, cron schedules, dependency workflows, and a dashboard you can act from. It runs inside your own app, on the Postgres, SQL Server, or SQLite database you already have. Nothing new to stand up.

The scheduling core is deterministic, so it can be tested for correctness the way the rest of your code is.

Everything you need to run background work

A complete job system, not a box of parts you wire together yourself. Pick a piece to see how it works, then follow it into the docs.

Typed jobs, generated wiring

Attribute a method with [Job] and the source generator emits the payload, handler, and a stable wire name that survives refactors. Enqueue a typed payload and BackWave runs it once, with retries.

Read the docs →
Program.cs
// 1. Wire BackWave onto your existing database.
builder.Services.AddBackWave(backwave =>
{
    backwave
        .UseStore(_ => new PostgresJobStore(connectionString))
        // source-generated handler registry
        .UseJobs(BackWaveJobs.Module);

    backwave.AddWorkerGroup(new WorkerGroupOptions
    {
        Name = "default",
        Policy = new DispatchPolicy.Strict(["critical"]),
    });
});

// 2. Declare a job. [Job] generates the payload + handler.
public sealed class Greetings(ILogger<Greetings> log)
{
    [Job("greet", Queue = "critical")]
    public Task GreetAsync(string name, CancellationToken ct)
    {
        log.LogInformation("hello {Name}", name);
        return Task.CompletedTask;
    }
}

// 3. Enqueue it.
await client.EnqueueAsync(new Greet("world"));
Why you can trust it in production

Correctness, proven in layers

It starts with a scheduling core that does no I/O and never reads the wall clock, so the same inputs always produce the same decisions. On top of that determinism sits a stack of testing: deterministic simulation, an executable storage contract, and concurrency torture against real databases. Each layer catches what the others cannot.

A deterministic core

The scheduling logic does no I/O and never reads the wall clock, so the same inputs always produce the same decisions.

Tested on virtual time

Drive the scheduler from a test on a clock you control. Years of schedule activity replay in milliseconds, without real timers or flaky waits.

Simulation-tested

The core runs continuously against seeded fault injection (crashes, clock skew, lost heartbeats, store faults), and any failure replays exactly from a single seed.

One contract, every store

A single executable spec of the storage contract runs against Postgres, SQL Server, and SQLite alike, so every adapter is held to the exact same behavior.

Tortured against real databases

Swarms of concurrent clients hammer a live database, right down to genuine multi-process SQLite on one file, to surface the race conditions no deterministic test can reach.

Soaked and benchmarked

Long-running soak flows and throughput benchmarks catch slow leaks and performance regressions before they ever reach a release.

Coming from Hangfire?

The same at-least-once model, but testable, transactional, and with no broker to run. See the side-by-side.

Why BackWave →

Ready to enqueue your first job?

The quickstart walks you from a fresh project to a running worker on the database you already have.

Read the quickstart