2529Labs.Metronome.Scheduling.Hangfire
1.0.0
dotnet add package 2529Labs.Metronome.Scheduling.Hangfire --version 1.0.0
NuGet\Install-Package 2529Labs.Metronome.Scheduling.Hangfire -Version 1.0.0
<PackageReference Include="2529Labs.Metronome.Scheduling.Hangfire" Version="1.0.0" />
<PackageVersion Include="2529Labs.Metronome.Scheduling.Hangfire" Version="1.0.0" />
<PackageReference Include="2529Labs.Metronome.Scheduling.Hangfire" />
paket add 2529Labs.Metronome.Scheduling.Hangfire --version 1.0.0
#r "nuget: 2529Labs.Metronome.Scheduling.Hangfire, 1.0.0"
#:package 2529Labs.Metronome.Scheduling.Hangfire@1.0.0
#addin nuget:?package=2529Labs.Metronome.Scheduling.Hangfire&version=1.0.0
#tool nuget:?package=2529Labs.Metronome.Scheduling.Hangfire&version=1.0.0
Metronome
Metronome is a .NET engineering control plane for recurring jobs and background queues. It combines durable scheduling, execution history, full exception capture, logs, traces, metrics, alerts, and operational controls in one embeddable SDK and one optional standalone server.
It works natively and also integrates with Coravel and Hangfire. Application code can schedule and enqueue work through strongly typed C# APIs; operators can pause, resume, trigger, backfill, inspect, cancel, retry, and change schedules from the dashboard.
In standalone mode, operators can use New execution to start work by an arbitrary job/type name, task queue and JSON payload before its consumer is online. The pending job immediately appears in Jobs and Queue; a compatible consumer executes it when available.
What you get
- A dense engineering dashboard with fleet health, failure rate, throughput, latency, worker nodes, alert state, live activity, job mini-dashboards, queue mini-dashboards, run history, logs, and traces.
- Per-job controls: enable, pause, stop, resume, run now, rerun, cancel, and interval/cron schedule overrides.
- A Metronome-native durable scheduler inspired by Temporal schedules: interval or cron, time zones, durable state, pause/resume, manual trigger, bounded backfill, catch-up windows, revision tracking, and distributed claims.
- A first-party durable queue: typed enqueue API, named queues, priorities, delayed availability, retries with backoff, idempotency keys, leases, cancellation, and dead-letter state.
- Full
Exception.ToString()capture, including inner exceptions and stack traces, at Metronome execution boundaries. StandardMicrosoft.Extensions.Loggingis captured directly, so Serilog is optional. Optional first-chance capture records exceptions thrown and later caught while a Metronome run is active. - PostgreSQL, Redis, and in-memory providers; distributed locks and cancellation signals.
- Rich HTML alert email plus webhooks, with job/run context, failure details, full stack trace, and a direct dashboard link.
- Embedded and standalone deployment modes, feature-by-feature switches, Docker Compose, health check, and package publishing workflows.
Packages
| Package | Purpose |
|---|---|
2529Labs.Metronome |
Core APIs, native jobs, scheduler, queue, observability, Coravel support |
2529Labs.Metronome.AspNetCore |
Dashboard and versioned HTTP API |
2529Labs.Metronome.Client |
Typed .NET client for a standalone/remote control plane |
2529Labs.Metronome.Persistence.InMemory |
Development/test persistence |
2529Labs.Metronome.Persistence.Postgres |
Durable PostgreSQL storage, locks, cancellation |
2529Labs.Metronome.Persistence.Redis |
Durable Redis storage, locks, pub/sub cancellation |
2529Labs.Metronome.Scheduling.Hangfire |
Hangfire discovery, controls, queue history, execution filter |
2529Labs.Metronome.Integrations.Serilog |
Optional Serilog sink |
2529Labs.Metronome.Notifications.Email |
Enriched HTML SMTP alerts |
Embedded quick start
Packages are published to GitHub Packages. Configure the source and a token with read:packages (NuGet.org publishing is enabled automatically when the repository secret is supplied):
dotnet nuget add source https://nuget.pkg.github.com/2529Labs/index.json --name github --username YOUR_GITHUB_USERNAME --password YOUR_GITHUB_TOKEN --store-password-in-clear-text
Install the packages for your chosen persistence provider:
dotnet add package 2529Labs.Metronome
dotnet add package 2529Labs.Metronome.AspNetCore
dotnet add package 2529Labs.Metronome.Persistence.Postgres
Register Metronome and a native job:
var metronome = builder.Services
.AddMetronome(builder.Configuration)
.UsePostgres()
.AddJob<ReconcileRevenueJob>("revenue-reconciliation", "Every 5 minutes");
app.UseMetronome(schedule =>
{
// Existing Coravel jobs can continue to be configured here.
schedule.Job<ExistingCoravelJob>("existing-job", "Every hour")
.EveryHour();
});
Create a native job with or without a typed input:
public sealed record ReconcileRequest(DateOnly BusinessDate);
public sealed class ReconcileRevenueJob(ILogger<ReconcileRevenueJob> log)
: IMetronomeJobHandler<ReconcileRequest>
{
public async Task ExecuteAsync(
ReconcileRequest input,
JobExecutionContext context,
CancellationToken cancellationToken)
{
context.SetTitle($"Reconciliation for {input.BusinessDate}");
context.AddItem("settlement-42", "Settlement batch", "Processing");
log.LogInformation("Reconciliation started for {Date}", input.BusinessDate);
await DoWork(cancellationToken);
}
}
Enqueue from application code:
var receipt = await queue.EnqueueAsync<ReconcileRevenueJob, ReconcileRequest>(
new ReconcileRequest(DateOnly.FromDateTime(DateTime.UtcNow)),
new QueueEnqueueRequest
{
Queue = "finance",
Priority = 20,
MaxAttempts = 5,
IdempotencyKey = $"reconcile:{DateOnly.FromDateTime(DateTime.UtcNow)}"
});
Create a durable schedule from code:
await schedules.CreateAsync(new ScheduleRequest
{
TypeName = typeof(ReconcileRevenueJob).FullName!,
Name = "quarter-hour reconciliation",
CronExpression = "0 */15 * * * *",
TimeZoneId = "Africa/Lagos",
CatchupWindowSeconds = 900
}, createdBy: "bootstrap");
Configuration
{
"Metronome": {
"Enabled": true,
"NodeId": "revas-worker-01",
"Persistence": { "Enabled": true, "Provider": "Postgres" },
"Postgres": {
"ConnectionString": "Host=localhost;Database=revas;Username=app;Password=secret",
"Schema": "metronome"
},
"Dashboard": {
"Enabled": true,
"Path": "/metronome",
"PublicUrl": "https://revas.example.com/metronome",
"Username": "operator",
"Password": "use-a-secret-provider"
},
"Features": {
"Jobs": true,
"Scheduler": true,
"Queue": true,
"Workers": true,
"Dashboard": true,
"Api": true,
"Logs": true,
"Traces": true,
"Exceptions": true,
"Metrics": true,
"Alerts": true
},
"Logging": {
"CaptureMicrosoftExtensionsLogging": true,
"CaptureFirstChanceExceptions": true
},
"Email": {
"Host": "smtp.example.com",
"Port": 587,
"EnableSsl": true,
"Username": "alerts@example.com",
"Password": "use-a-secret-provider",
"FromAddress": "alerts@example.com"
}
}
}
Metronome:Enabled=false disables the entire system. Every feature can also be switched independently. Disable Workers to enqueue without consuming, Dashboard to run headless, or Scheduler while retaining manual/API execution.
Standalone control plane
Run the prebuilt server against PostgreSQL:
docker compose up --build
Open http://localhost:8080/metronome and use admin / change-me from the example Compose file. Change these credentials before exposing the service. The standalone server can share a PostgreSQL or Redis data plane with SDK-enabled workers; embedded applications can disable their dashboard/API while leaving jobs, workers, telemetry, and storage enabled.
Remote execution uses the shared durable queue as an agent protocol: the standalone control plane enqueues a command by job name, and an SDK-enabled application worker that has that job type loaded claims it, links the resulting run, renews its lease, and publishes status, logs, and traces back to the shared provider. The control plane therefore does not need the host application's assemblies or inbound access to it. Configure the same provider and queue name on both sides; set Features:Workers=false on the standalone server and Features:Dashboard=false/Features:Api=false on headless workers.
For external .NET automation, install 2529Labs.Metronome.Client:
var control = new MetronomeClient(new HttpClient(), new MetronomeClientOptions
{
BaseAddress = new Uri("https://ops.example.com"),
DashboardPath = "/metronome",
Username = "automation",
Password = secret
});
await control.EnqueueAsync("MyApp.Jobs.ReconcileJob", new { businessDate = "2026-08-01" },
new QueueEnqueueRequest { Queue = "finance", IdempotencyKey = "reconcile:2026-08-01" });
Coravel and Hangfire
Coravel is supported by the core package, including tracked scheduled invocables and queue invocables. Hangfire support is opt-in:
builder.Services.AddMetronome(builder.Configuration)
.UsePostgres()
.UseHangfire();
The dashboard discovers Hangfire recurring definitions, allows immediate trigger/cron edits/removal, and includes enqueued, processing, succeeded, and failed Hangfire records in the queue view.
IJobItemSource is optional. Use it only when a job has domain-level children—approvals, invoices, settlements, files—and operators need to inspect or retry those individual items. It is not required to schedule or execute a job.
Errors, logs, and traces
Metronome wraps every native/Coravel-managed execution. Unhandled exceptions are stored using full Exception.ToString() and structured recursive exception data. This works even when the application does not log the failure. The built-in logger provider captures normal ILogger events and correlates them with job, run, node, and trace IDs.
Click a log event to open its investigation page, which shows the complete exception/stack text plus surrounding timeline and correlation context. Trace pages show all spans for a trace. First-chance capture is enabled by default and captures thrown exceptions inside an active Metronome run even when application code catches them and never logs them; disable it explicitly if its volume is unsuitable.
No in-process library can recover managed stack frames after a hard process kill, runtime crash, stack overflow, or power loss. Metronome records the last heartbeat and marks abandoned runs failed during recovery; attach a crash-dump/APM facility when post-mortem native/runtime stacks are required.
HTTP API and documents
The API is available below the dashboard path at both /api/... and /api/v1/.... See:
Build and package
dotnet restore Metronome.sln
dotnet test Metronome.sln -c Release
dotnet pack Metronome.sln -c Release -o artifacts/packages
Tags matching v* publish packages to GitHub Packages; adding the NUGET_API_KEY repository secret also publishes them to NuGet.org.
Package-name migration
Version 1.0 establishes 2529Labs.Metronome.* as the official package family. The public C# namespaces remain Metronome.*; only NuGet package IDs changed, so application source code does not need namespace edits.
The former Raimi.Metronome.* IDs are published as deprecated compatibility packages. Each contains no duplicate runtime implementation and forwards to its matching 2529Labs.Metronome.* package. Existing consumers can therefore upgrade safely, then replace their package references at their convenience. Compatibility packages receive 1.x forwarding releases only and will no longer be published beginning with Metronome 2.0.
Example migration:
<PackageReference Include="Raimi.Metronome.AspNetCore" Version="0.2.1" />
<PackageReference Include="2529Labs.Metronome.AspNetCore" Version="1.0.0" />
License
MIT © 2529 Labs.
| 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
- 2529Labs.Metronome (>= 1.0.0)
- Hangfire.AspNetCore (>= 1.8.14)
- Hangfire.Core (>= 1.8.14)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on 2529Labs.Metronome.Scheduling.Hangfire:
| Package | Downloads |
|---|---|
|
Raimi.Metronome.Scheduling.Hangfire
Deprecated compatibility package. Migrate to 2529Labs.Metronome.Scheduling.Hangfire. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 163 | 8/1/2026 |