Overview

BackWave is a background-job system for .NET with typed jobs, cron schedules, dependency workflows, and a write-capable dashboard, running in-process on the database you already run.


BackWave is a background-job system for .NET. You get typed jobs, cron schedules, dependency workflows, and a write-capable dashboard, all running in-process on a database you already run. It is deliberately small in surface area, and built so the background work you never see in production is something you can test like ordinary code.

What you get#

BackWave is a library you add to an existing .NET application. It gives you a typed way to enqueue work, a storage-backed queue, and a runtime that processes jobs reliably. The pieces a real job system needs are already in the box:

  • Jobs & handlers. 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 it runs once, with retries.
  • Scheduling. Every job carries a due time: fire it now, hold it for later, or mint it on a cron with a recurring schedule.
  • Workflows. Compose jobs into a named DAG. A step waits for its parents and can read their output, and the whole graph can be cancelled or restarted as a unit.
  • Transactional enqueue. Enqueuing a job participates in your database transaction. Commit and the job is durably queued; roll back and it never existed. There's no outbox to hand-roll, and no dual-write race between your data and your queue.
  • Runs on your database. BackWave stores its queue in the database you already run, whether that's Postgres, SQL Server, or SQLite. There's no broker to provision, secure, and monitor, which is one fewer moving part in production and in CI.
  • A write-capable dashboard. Inspect queues and job timelines, then act: requeue, cancel, or pause a queue, with every action authorized by your own app.

Here's a minimal wiring. Register BackWave, point it at your store and jobs, and enqueue work inside your existing transaction:

Program.cs
using BackWave;
using BackWave.Generated;
using BackWave.Postgres;
 
var builder = WebApplication.CreateBuilder(args);
 
builder.Services.AddBackWave(backwave =>
{
    backwave
        .UseStore(_ => new PostgresJobStore(new PostgresStoreOptions
        {
            ConnectionString = connectionString,
            AutoMigrate = true,
        }))
        .UseJobs(BackWaveJobs.Module);
 
    backwave.AddWorkerGroup(new WorkerGroupOptions
    {
        Name = "default",
        Policy = new DispatchPolicy.Strict(["default"]),
        RetryPolicy = new RetryPolicy { MaxAttempts = 5 },
    });
});
 
var app = builder.Build();
 
app.MapPost("/orders", async (OrderRequest req, BackWaveClient client, AppDbContext db) =>
{
    db.Orders.Add(new Order(req.Id));
 
    // Enqueued inside the same transaction as the write above:
    // commit and the job is durably queued; roll back and it never existed.
    await client.EnqueueAsync(new SendOrderNotification(req.Id), db, dueTime: DateTimeOffset.UtcNow);
 
    await db.SaveChangesAsync();
    return Results.Accepted();
});
 
app.Run();

Why you can trust it#

Those capabilities are table stakes; what makes BackWave safe to put in front of a production queue is that its scheduling logic is a deterministic core. It does no I/O and never reads the wall clock, so the same inputs always produce the same decisions. Two things fall out of that:

  • You can test it like ordinary code. Drive the scheduler from a test on a clock you control, advance it, and assert on exactly what ran. Years of schedule activity replay in milliseconds, without real timers or flaky waits.
  • It is simulation-tested for correctness. The core runs continuously against seeded fault injection (crashes, clock skew, lost heartbeats, store faults), and any failure it finds replays exactly from a single seed.

What BackWave is not#

BackWave is not a durable-execution engine. It does ship first-class Workflows: named groups of jobs wired together by dependency edges, with a graph view and lifecycle controls, where a step can read the output of the jobs it depends on. What it does not do is record and replay the steps inside a job the way Temporal does: no signals or waits, no conditionals, and no graph that reshapes itself based on results. If you need that, reach for a tool built for it. BackWave's job is to run discrete background work reliably and make it easy to test, without dragging in extra infrastructure.

Where to go next#

This Overview is the entry point to the Introduction. From here the docs move into the core concepts: how jobs are defined and enqueued, how transactional enqueue works against your storage, and how to drive the runtime deterministically in tests.