Background jobs for .NET that survive the worst day.
Retries, dead-letter handling, deadlines and a built-in dashboard.
PostgreSQL · SQL Server · Redis · MongoDB · in-memory.
📖 Documentation: oluciano.github.io/NexJob | 🎮 Live Demo: nexjob-playground.fly.dev | 📦 NuGet
A job fails three times and lands in the dead-letter queue with its error, ready to requeue. The two others succeed.
dotnet add package NexJob// Program.cs: register NexJob (in-memory storage by default) and scan for jobs
builder.Services.AddNexJob();
builder.Services.AddNexJobJobs(typeof(Program).Assembly);// A job
public sealed class SendInvoiceJob : IJob<SendInvoiceInput>
{
private readonly IEmailService _email;
public SendInvoiceJob(IEmailService email) => _email = email;
public async Task ExecuteAsync(SendInvoiceInput input, CancellationToken ct)
=> await _email.SendAsync(input.Email, "Your invoice", ct);
}
public sealed record SendInvoiceInput(string Email);// Enqueue it from anywhere. It expires if it does not start within 5 minutes.
var scheduler = app.Services.GetRequiredService<IScheduler>();
await scheduler.EnqueueAsync<SendInvoiceJob, SendInvoiceInput>(
new SendInvoiceInput("customer@example.com"),
deadlineAfter: TimeSpan.FromMinutes(5));To watch it run, add the dashboard (dotnet add package NexJob.Dashboard) and app.UseNexJobDashboard("/dashboard");, then open /dashboard. See Dashboard for the Worker Service variant, or try the live demo first.
To use a database instead of memory, see the storage providers.
A job library is judged on the worst day: a node that dies, a deploy in the middle of a job, an upgrade from the previous version. Every failure has a documented cost (see Delivery guarantees). This is what the test suite proves (the full page):
- The same tests on every provider. One contract suite runs on InMemory, PostgreSQL, SQL Server, Redis and MongoDB.
- Several nodes, one database. Reliability scenarios run many workers and nodes on the four databases: each job runs once, no job is lost under concurrent enqueue, and a job left behind by a dead node is recovered and runs once.
- A killed process. On SQL Server a node runs in its own process and is killed in the middle of a job.
- Upgrades. A storage filled by the previous version opens with the new one, with its history and orphans intact.
- An honest list of gaps. The process kill is tested on SQL Server only, and wake-up latency is not asserted. The list is in the page above.
The Scorecard, Best Practices and CodeQL badges rate the repository's security and engineering practices (pinned actions, least-privilege tokens, dependency updates, documented process, static analysis). The Best Practices badge is self-declared; the CodeQL badge says the analysis ran on the default branch, and the open alerts are in the Security tab. None of them says anything about throughput or behaviour under failure.
| You need | NexJob gives you |
|---|---|
| Jobs that retry and then stop | Global and per-job retries, maxAttempts per enqueue, dead-letter handlers and forwarders, failures that skip retries, [ExecutionTimeout] |
| No zombie jobs | Deadlines, crash recovery by heartbeat, DuplicatePolicy idempotency |
| Protect a dependency | Queue circuit breaker, [Throttle] per resource (cluster-wide with Redis), execution windows |
| Several services, one database | Queue isolation by default ({prefix}.default per application), multi-service guide |
| Schedule | Recurring jobs from code or appsettings.json, with time zones; delayed jobs; continuations; priority |
| See and control it | Dashboard, runtime control (pause, requeue, delete), OpenTelemetry, health checks, alerts |
| Feed it from brokers | Triggers for Kafka, RabbitMQ, AWS SQS, Azure Service Bus, Google Pub/Sub and Salesforce, and a resilient outbox for RabbitMQ and Kafka |
| Provider | Package | Status |
|---|---|---|
| InMemory | NexJob (core) |
Production ready |
| PostgreSQL | NexJob.Postgres |
Production ready |
| SQL Server | NexJob.SqlServer |
Production ready |
| Redis | NexJob.Redis |
Production ready |
| MongoDB | NexJob.MongoDB |
Production ready |
All providers implement IRuntimeSettingsStore — runtime configuration persists across restarts.
All packages share one version number.
| Package | NuGet | Description |
|---|---|---|
NexJob.Dashboard |
NuGet | Embedded ASP.NET Core dashboard middleware |
NexJob.Dashboard.Standalone |
NuGet | Embedded HTTP dashboard server for Worker Services |
NexJob.OpenTelemetry |
NuGet | OTel SDK instrumentation |
NexJob.Trigger.AzureServiceBus |
NuGet | Azure Service Bus trigger |
NexJob.Trigger.AwsSqs |
NuGet | AWS SQS trigger |
NexJob.RabbitMQ |
NuGet | RabbitMQ trigger & resilient outbox producer |
NexJob.Kafka |
NuGet | Apache Kafka trigger & resilient outbox producer |
NexJob.Trigger.GooglePubSub |
NuGet | Google Cloud Pub/Sub trigger |
NexJob.Trigger.Salesforce |
NuGet | Salesforce Pub/Sub API trigger (gRPC & Avro) |
NexJob.Trigger.SalesforceStreaming |
NuGet | Salesforce Streaming API trigger (CometD & Bayeux) |
Real-time visibility into jobs, worker nodes and broker listeners, with no extra infrastructure.
- Live cluster status,
Ctrl + Kjob search and a topology map from triggers to queues to worker nodes. - Streaming logs (SSE), progress bars and five themes.
- Opt-in scenario simulator (
EnablePlayground = true, off by default) to try burst loads and failures without scripts.
Setup for ASP.NET Core and for Worker Services: Dashboard guide.
Complete documentation is on the Documentation Site. Key pages:
- Mental Model — how NexJob works, read this first
- Quickstart — run your first job in 2 minutes
- Delivery Guarantees — what each failure costs a job: crashes, shutdown, throttling, pauses, deadlines
- Circuit Breaker and Throttling — protect a queue from a failing dependency, and limit concurrency
- Alerts — get notified when a job fails for good
- Best Practices — production and Kubernetes guidelines
- Common Scenarios — real-world use cases with code
- How We Test — what the test suite proves, and what it does not
- Troubleshooting — debug common issues
Runnable examples for the web, worker, storage and broker scenarios are in samples/, with the full catalog and a Docker Compose stack (PostgreSQL, Redis, RabbitMQ, Kafka) in samples/README.md.
Hangfire is the usual reference for .NET background jobs, so it is the comparison here.
The figures below measure the cost of enqueueing one job, not processing throughput. Measured per individual enqueue operation with the benchmark in this repository (BenchmarkDotNet 0.15.8, .NET 8.0.31, RyuJIT, Intel Xeon E5-2667 v4, in-memory storage on both sides, Hangfire.Core 1.8.25 and Hangfire.InMemory 0.9.0), a job with no payload:
| Metric | NexJob | Hangfire | Comparison |
|---|---|---|---|
| Latency (mean) | 34.5 µs | 36.7 µs | Same range; the difference is within the noise of the run |
| Latency (median) | 31.7 µs | 36.6 µs | |
| Memory (allocated) | 4.82 KB | 12.57 KB | 62% less memory |
| GC Gen0 (per 1k ops) | 0.12 | 0.98 | 8× fewer Gen0 collections |
| GC Gen1 (per 1k ops) | 0.00 | 0.24 | No Gen1 collections |
With a 1 KB payload NexJob took 44 µs and allocated 6 KB, against 54 µs and 26 KB for Hangfire; with 10 KB, 123 µs and 30 KB against 103 µs and 143 KB. Latency differs little between the two; the difference that holds in every run is allocation. This is enqueue only: processing throughput, a database storage and concurrent enqueue were not measured in this run. Earlier versions of this page showed 13.35 µs and 2.10 KB for NexJob; that figure did not reproduce, see #511.
Run it yourself: cd benchmarks/NexJob.Benchmarks && dotnet run -c Release -- --filter *EnqueueLatencyBenchmark*.
Benchmarks can be parameterized by payload size (PayloadBytes: 0, 1024, 10240) and concurrency levels (ConcurrencyLevel: 10, 50) in benchmarks/NexJob.Benchmarks.
- qKafka: If you need event-driven choreographies, distributed sagas with compensating transactions, and native Kafka stream state machines, check out qKafka. NexJob focuses on background job scheduling and resilient outbox dispatch, seamlessly bridging events to and from Kafka.
Found a bug or have an idea? Open an issue after searching the existing ones. To send code, read CONTRIBUTING.md: branch from develop, open the pull request against develop, build with zero warnings, and cover the change with tests (positive, negative and invalid input). Vulnerabilities go through the private channel in SECURITY.md, not a public issue.
The history is in the GitHub releases and the CHANGELOG. Security issues: see SECURITY.md.


