NexJob 0.1.0
See the version list below for details.
dotnet add package NexJob --version 0.1.0
NuGet\Install-Package NexJob -Version 0.1.0
<PackageReference Include="NexJob" Version="0.1.0" />
<PackageVersion Include="NexJob" Version="0.1.0" />
<PackageReference Include="NexJob" />
paket add NexJob --version 0.1.0
#r "nuget: NexJob, 0.1.0"
#:package NexJob@0.1.0
#addin nuget:?package=NexJob&version=0.1.0
#tool nuget:?package=NexJob&version=0.1.0
<div align="center">
<br/>
███╗ ██╗███████╗██╗ ██╗ ██╗ ██████╗ ██████╗
████╗ ██║██╔════╝╚██╗██╔╝ ██║██╔═══██╗██╔══██╗
██╔██╗ ██║█████╗ ╚███╔╝ ██║██║ ██║██████╔╝
██║╚██╗██║██╔══╝ ██╔██╗ ██ ██║██║ ██║██╔══██╗
██║ ╚████║███████╗██╔╝ ██╗╚█████╔╝╚██████╔╝██████╔╝
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═════╝
Background jobs for .NET that stay out of your way.
<br/>
</div>
NexJob is the background job library that .NET deserved from day one.
No expression trees. No lock-in. No paid tiers for Redis. No wrestling with serialization. Just a clean interface, two lines of configuration, and a scheduler that handles the hard parts — retries, concurrency, cron, observability — while you focus on what your job actually does.
public class WelcomeEmailJob : IJob<WelcomeEmailInput>
{
public async Task ExecuteAsync(WelcomeEmailInput input, CancellationToken ct)
=> await _email.SendAsync(input.UserId, ct);
}
That's your entire job. NexJob handles the rest.
Why NexJob exists
Every .NET developer has used Hangfire. And every .NET developer has hit the same walls:
- The Redis adapter costs money. So does the MongoDB one.
async/awaitis bolted on — Hangfire serializesTask, it doesn't await it.- The dashboard looks like it was designed in 2014. Because it was.
- There's no concept of priority queues, resource throttling, or payload versioning.
- You can't change workers or pause a queue without restarting the server.
- The license is LGPL for the core and paid for anything production-worthy.
NexJob was built to solve all of that. MIT license, end to end. Every storage adapter open-source. Native async/await from the ground up. Priority queues and idempotency built in. A dark-mode dashboard to inspect every queue, job state, and retry — without touching the code. OpenTelemetry, appsettings.json support, and live runtime config are on the roadmap.
At a glance
✅ = implemented · 🔜 = on the roadmap
| NexJob | Hangfire | |
|---|---|---|
| License | MIT | LGPL / paid Pro |
async/await native |
✅ | ❌ |
| Priority queues | ✅ | ❌ |
| Idempotency keys | ✅ | ❌ |
| In-memory for testing | ✅ | ✅ |
| Cron / recurring jobs | ✅ | ✅ |
| PostgreSQL + MongoDB adapters free | ✅ | ❌ |
| Dashboard (dark mode) | ✅ | ❌ |
| All storage adapters free | ✅ | ❌ |
| Resource throttling | ✅ | ❌ |
| Job continuations (chaining) | ✅ | ❌ |
| OpenTelemetry built-in | 🔜 | ❌ |
| Payload versioning | 🔜 | ❌ |
appsettings.json support |
🔜 | ❌ |
| Execution windows per queue | 🔜 | ❌ |
| Live config without restart | 🔜 | ❌ |
Installation
# Core (includes in-memory provider for dev/tests)
dotnet add package NexJob
# Pick your storage — all free, all open-source
dotnet add package NexJob.Postgres
dotnet add package NexJob.SqlServer
dotnet add package NexJob.Redis
dotnet add package NexJob.MongoDB
dotnet add package NexJob.Oracle
# Optional dashboard
dotnet add package NexJob.Dashboard
Getting started
1 — Define your job
public record SendInvoiceInput(Guid OrderId, string CustomerEmail);
public class SendInvoiceJob : IJob<SendInvoiceInput>
{
private readonly IInvoiceService _invoices;
private readonly IEmailService _email;
public SendInvoiceJob(IInvoiceService invoices, IEmailService email)
{
_invoices = invoices;
_email = email;
}
public async Task ExecuteAsync(SendInvoiceInput input, CancellationToken ct)
{
var pdf = await _invoices.GenerateAsync(input.OrderId, ct);
await _email.SendAsync(input.CustomerEmail, pdf, ct);
}
}
2 — Register
builder.Services.AddNexJob(opt =>
{
opt.Workers = 10;
opt.PollingInterval = TimeSpan.FromSeconds(1);
});
// Register your jobs
builder.Services.AddTransient<SendInvoiceJob>();
3 — Schedule
// Fire and forget
await scheduler.EnqueueAsync<SendInvoiceJob, SendInvoiceInput>(
new(orderId, email));
// Delayed
await scheduler.ScheduleAsync<SendInvoiceJob, SendInvoiceInput>(
new(orderId, email),
delay: TimeSpan.FromMinutes(5));
// Recurring (cron) — default: skip if already running
await scheduler.RecurringAsync<MonthlyReportJob, MonthlyReportInput>(
id: "monthly-report",
input: new(DateTime.UtcNow.Month),
cron: "0 9 1 * *");
// Recurring — allow multiple instances in parallel (range-based sharding etc.)
await scheduler.RecurringAsync<ImportChunkJob, ImportChunkInput>(
id: "import-chunk",
input: new(ShardId: 0),
cron: "*/5 * * * *",
concurrencyPolicy: RecurringConcurrencyPolicy.AllowConcurrent);
// Continuation — runs only after parent succeeds
var jobId = await scheduler.EnqueueAsync<ProcessPaymentJob, PaymentInput>(paymentInput);
await scheduler.ContinueWithAsync<SendReceiptJob, ReceiptInput>(jobId, receiptInput);
// With idempotency key — safe to call multiple times
await scheduler.EnqueueAsync<SendInvoiceJob, SendInvoiceInput>(
new(orderId, email),
idempotencyKey: $"invoice-{orderId}");
4 — Dashboard
app.UseNexJobDashboard("/dashboard");
Open /dashboard and see every queue, every job state, every retry — live.
Priority queues
Jobs with Critical priority jump the queue. No workarounds, no separate deployments.
await scheduler.EnqueueAsync<AlertJob, AlertInput>(
input,
priority: JobPriority.Critical); // Critical → High → Normal → Low
Resource throttling
Limit how many instances of a job run concurrently across all workers — no extra infrastructure required.
[Throttle(resource: "stripe", maxConcurrent: 3)]
public class ChargeCardJob : IJob<ChargeInput> { ... }
Recurring concurrency policy
By default, NexJob prevents a recurring job from having more than one active instance at a time. If the previous execution is still running when the cron fires again, the new firing is silently skipped — no duplicates, no queued pile-up.
// SkipIfRunning (default) — safe for jobs that must not overlap
await scheduler.RecurringAsync<SyncInventoryJob, Unit>(
id: "sync-inventory",
input: Unit.Value,
cron: "*/5 * * * *");
// concurrencyPolicy defaults to RecurringConcurrencyPolicy.SkipIfRunning
Some jobs are designed to run in parallel — for example, range-based imports that each
process a different shard of data. Use AllowConcurrent to opt out of the overlap guard:
// AllowConcurrent — each firing spawns a new instance regardless of running ones
await scheduler.RecurringAsync<ImportShardJob, ShardInput>(
id: "import-shard",
input: new(ShardId: myShardId),
cron: "*/10 * * * *",
concurrencyPolicy: RecurringConcurrencyPolicy.AllowConcurrent);
The dashboard shows a ⟳ concurrent badge on any recurring job registered with
AllowConcurrent, so the behaviour is always visible at a glance.
Observability
🔜 Coming in v0.6 — OpenTelemetry spans for every job lifecycle event.
Payload versioning
🔜 Coming in v0.6 — migrate job inputs across schema versions without losing queued jobs.
Testing
The in-memory provider requires zero setup:
builder.Services.AddNexJob(opt => opt.Workers = 1);
// InMemoryStorageProvider is the default — no extra config needed
🔜
TestSchedulerwithShouldHaveEnqueuedassertions coming in v0.6.
Retry policy
Failed jobs retry with exponential backoff and jitter. Dead-lettered jobs are preserved — never silently dropped.
| Attempt | Delay |
|---|---|
| 1 | ~16 seconds |
| 2 | ~1 minute |
| 3 | ~5 minutes |
| 4 | ~17 minutes |
| 5 | ~42 minutes |
Configure globally:
builder.Services.AddNexJob(opt =>
{
opt.MaxAttempts = 3; // default: 10
});
🔜 Per-job
[Retry(attempts: 3)]attribute coming in v0.6.
Storage providers
All open-source. No license walls. Ever.
| Package | Storage | Status |
|---|---|---|
NexJob |
In-memory | ✅ Dev and testing |
NexJob.Postgres |
PostgreSQL 14+ | ✅ SELECT FOR UPDATE SKIP LOCKED |
NexJob.MongoDB |
MongoDB 6+ | ✅ Atomic findAndModify |
NexJob.SqlServer |
SQL Server 2019+ | 🔜 Coming soon |
NexJob.Redis |
Redis 7+ | 🔜 Coming soon |
NexJob.Oracle |
Oracle 19c+ | 🔜 Coming soon |
Bring your own? Implement IStorageProvider — one interface, ~15 methods.
Configuration reference
// Storage — pick one and register BEFORE AddNexJob (InMemory is the default)
builder.Services.AddNexJobPostgres(connectionString);
builder.Services.AddNexJobMongoDB(connectionString, databaseName: "nexjob");
builder.Services.AddNexJob(opt =>
{
// Workers & queues
opt.Workers = 10;
opt.Queues = ["default", "critical"]; // polled in order
// Timing
opt.PollingInterval = TimeSpan.FromSeconds(5);
opt.HeartbeatInterval = TimeSpan.FromSeconds(30);
opt.HeartbeatTimeout = TimeSpan.FromMinutes(5);
// Retries
opt.MaxAttempts = 5;
});
Roadmap
v0.1 ✅ Core interfaces · in-memory provider · fire-and-forget
v0.2 ✅ PostgreSQL + MongoDB providers · delayed jobs · cron · dashboard (Blazor SSR)
v0.3 ✅ Priority queues · resource throttling ([Throttle]) · job continuations
v0.4 ✅ Recurring job execution status · unit + integration tests · CI pipeline
v0.5 ○ SQL Server · Redis · Oracle providers
v0.6 ○ OpenTelemetry · payload versioning (IJobMigration) · [Retry] per-job
v1.0 ○ Stable API · production-ready · published to NuGet
Contributing
NexJob is built in the open. Issues, ideas, and PRs are welcome.
git clone git@github.com:oluciano/NexJob.git
cd NexJob
dotnet restore
dotnet test
Read CONTRIBUTING.md before opening a PR.
License
MIT © 2025 Luciano Azevedo
<div align="center"> <br/>
Built with obsession over developer experience.
If Hangfire is the past, NexJob is what comes next.
<br/> </div>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Cronos (>= 0.8.4)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
NuGet packages (12)
Showing the top 5 NuGet packages that depend on NexJob:
| Package | Downloads |
|---|---|
|
NexJob.Dashboard
Blazor SSR dashboard middleware for NexJob — real-time monitoring of background jobs. |
|
|
NexJob.Postgres
PostgreSQL storage provider for NexJob — the open-source background job scheduler for .NET 8+. |
|
|
NexJob.MongoDB
MongoDB storage provider for NexJob — the open-source background job scheduler for .NET 8+. |
|
|
NexJob.SqlServer
SQL Server storage provider for NexJob. |
|
|
NexJob.Redis
Redis storage provider for NexJob. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 0 | 4/16/2026 |
| 2.0.0 | 135 | 4/14/2026 |
| 1.0.0 | 164 | 4/8/2026 |
| 0.8.0 | 161 | 4/8/2026 |
| 0.7.0 | 158 | 4/7/2026 |
| 0.6.0 | 164 | 4/3/2026 |
| 0.5.2 | 170 | 4/1/2026 |
| 0.5.1 | 161 | 4/1/2026 |
| 0.5.0 | 170 | 3/31/2026 |
| 0.4.0 | 165 | 3/31/2026 |
| 0.3.2 | 208 | 3/28/2026 |
| 0.3.1 | 222 | 3/28/2026 |
| 0.3.0 | 183 | 3/27/2026 |
| 0.2.0 | 181 | 3/27/2026 |
| 0.1.1 | 133 | 3/27/2026 |
| 0.1.0 | 127 | 3/26/2026 |
| 0.1.0-alpha | 126 | 3/26/2026 |