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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="2529Labs.Metronome.Scheduling.Hangfire" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="2529Labs.Metronome.Scheduling.Hangfire" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="2529Labs.Metronome.Scheduling.Hangfire" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add 2529Labs.Metronome.Scheduling.Hangfire --version 1.0.0
                    
#r "nuget: 2529Labs.Metronome.Scheduling.Hangfire, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package 2529Labs.Metronome.Scheduling.Hangfire@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=2529Labs.Metronome.Scheduling.Hangfire&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=2529Labs.Metronome.Scheduling.Hangfire&version=1.0.0
                    
Install as a Cake Tool

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. Standard Microsoft.Extensions.Logging is 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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