Lumarin.Notify.Scheduling 0.8.0-preview.199

This is a prerelease version of Lumarin.Notify.Scheduling.
dotnet add package Lumarin.Notify.Scheduling --version 0.8.0-preview.199
                    
NuGet\Install-Package Lumarin.Notify.Scheduling -Version 0.8.0-preview.199
                    
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="Lumarin.Notify.Scheduling" Version="0.8.0-preview.199" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lumarin.Notify.Scheduling" Version="0.8.0-preview.199" />
                    
Directory.Packages.props
<PackageReference Include="Lumarin.Notify.Scheduling" />
                    
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 Lumarin.Notify.Scheduling --version 0.8.0-preview.199
                    
#r "nuget: Lumarin.Notify.Scheduling, 0.8.0-preview.199"
                    
#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 Lumarin.Notify.Scheduling@0.8.0-preview.199
                    
#: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=Lumarin.Notify.Scheduling&version=0.8.0-preview.199&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Lumarin.Notify.Scheduling&version=0.8.0-preview.199&prerelease
                    
Install as a Cake Tool

Lumarin.Notify.Scheduling

Lumarin.Notify.Scheduling is an optional preview package for durable one-time scheduled notifications in Lumarin.Notify.

It is notification-native, not a general-purpose scheduler. A schedule stores a concrete NotificationRequest, waits until the due time, then hands that request back into the normal Lumarin.Notify acceptance pipeline. That means the scheduled send still goes through the same channel selection, preference enforcement, idempotency, delivery tracking, and optional outbox path as an immediate send.

Preview status

  • optional package
  • preview-only maturity
  • one-time scheduling only
  • durable EF Core persistence required
  • not part of the minimal Release 0 validation path

The current preview intentionally does not add recurrence, cron expressions, workflow orchestration, dashboards, HTTP endpoints, or a typed scheduling client.

When to use it

Use Lumarin.Notify.Scheduling when:

  • you already use Lumarin.Notify and need one-time delayed delivery for notifications
  • you want scheduled work to reuse the normal notification acceptance pipeline
  • you need cancel, reschedule, inspection, expiry, and bounded handoff retry semantics for notification sends

Use Hangfire or Quartz instead when:

  • you need recurring jobs, calendars, misfire policies, or arbitrary background work
  • you need a general scheduler for non-notification tasks
  • you need ecosystem features such as dashboards, richer trigger models, or broad job orchestration

Installation

dotnet add package Lumarin.Notify.Scheduling
dotnet add package Lumarin.Notify.Scheduling.EntityFrameworkCore

If you want to use the built-in scheduling migration helpers, reference the matching provider-specific scheduling migration package as well:

dotnet add package Lumarin.Notify.Scheduling.EntityFrameworkCore.PostgreSQL
# or
dotnet add package Lumarin.Notify.Scheduling.EntityFrameworkCore.SqlServer

Enable scheduling

using Lumarin.Notify.DependencyInjection;
using Lumarin.Notify.EntityFrameworkCore.PostgreSQL.DependencyInjection;
using Lumarin.Notify.Scheduling.DependencyInjection;
using Lumarin.Notify.Scheduling.EntityFrameworkCore.Extensions;

builder.Services.AddLumarinNotify(options =>
{
    options.UsePostgreSql(builder.Configuration.GetConnectionString("LumarinNotify")!);
    options.EnableScheduling(scheduling =>
    {
        scheduling.BatchSize = 100;
        scheduling.ClaimLeaseDuration = TimeSpan.FromMinutes(1);
        scheduling.MaxDispatchAttempts = 5;
    });
});

var app = builder.Build();
await app.Services.MigrateLumarinNotifySchedulingAsync();

Important activation rule:

  • EnableScheduling(...) inside AddLumarinNotify(...) is the primary activation path.
  • When Lumarin.Notify.Scheduling.EntityFrameworkCore is referenced, the root builder wires scheduling persistence automatically.
  • Direct UseLumarinNotifyScheduling(...) plus AddLumarinNotifySchedulingEntityFrameworkCore() remains available for advanced composition.
  • Registering or mutating SchedulingOptions alone does not activate scheduling.

Schedule a notification

using Lumarin.Notify.Models;
using Lumarin.Notify.Scheduling.Abstractions;
using Lumarin.Notify.Scheduling.Models;

var scheduling = app.Services.GetRequiredService<ISchedulingOperations>();

var result = await scheduling.ScheduleAsync(new ScheduleNotificationRequest(
    Notification: new NotificationRequest(
        ScopeType: "user",
        ScopeId: "user-123",
        Category: "digest.morning",
        Content: new NotificationContent(
            "Your morning summary",
            "3 new jobs match your filters."),
        Recipients: new NotificationRecipients(["user-123"]),
        Options: new NotificationOptions(
            IdempotencyKey: "digest:user-123:2026-04-02")),
    Schedule: ScheduleInput.ForUtc(DateTime.UtcNow.Date.AddDays(1).AddHours(8))));

Other schedule input shapes:

ScheduleInput.ForDelay(TimeSpan.FromMinutes(30));

ScheduleInput.ForLocal(
    localTime: new DateTime(2026, 11, 1, 8, 30, 0, DateTimeKind.Unspecified),
    timeZoneId: "W. Europe Standard Time",
    ambiguousTimeResolution: ScheduleAmbiguousTimeResolution.EarlierUtcInstant);

Operational semantics

  • Schedules are durable rows with lease-based claiming.
  • Due rows are claimed in batches, then handed into INotificationHub.SendAsync(...).
  • Finalization writes are guarded by the state and claim token that owned the lease. If a lease expires and another worker reclaims the row, the stale worker cannot overwrite the newer owner.
  • If the handoff succeeds, the schedule becomes Fired.
  • If the handoff fails, scheduling retries with bounded exponential backoff and jitter until MaxDispatchAttempts is exhausted, then marks the row Failed.
  • If ExpiresAt has passed before firing, the row becomes Expired.
  • CancelAsync(...) cancels a scheduled row before it fires.
  • RescheduleAsync(...) can move non-fired rows to a new one-time instant and resets claim/retry state.
  • GetScheduledNotificationAsync(...) exposes due time, original local-time metadata, payload metadata, attempt counts, and lifecycle timestamps for inspection.

The remaining duplicate-fire risk is narrow but real: if a send is accepted and the process crashes before the Fired update is persisted, another worker may reclaim the row after lease expiry and attempt the same handoff again. That risk is bounded rather than eliminated. Keep a stable dispatch correlation / idempotency key so downstream acceptance can absorb that edge.

State policy

  • CancelAsync(...) only succeeds while the row is still Scheduled.
  • RescheduleAsync(...) is allowed for Scheduled, Cancelled, Expired, and Failed rows.
  • Claimed rows cannot be cancelled or rescheduled until the lease is gone and the row is reclaimable again.
  • Fired rows are terminal and cannot be rescheduled.

If you enable Lumarin.Notify Outbox, a scheduled notification still flows through the outbox acceptance pipeline because scheduling hands the stored payload back through the normal notification hub.

Timezone model

The preview supports three one-time input shapes:

  • absolute UTC instant
  • relative delay
  • local wall-clock time plus a time zone id

For local wall-clock scheduling:

  • pass a DateTime with DateTimeKind.Unspecified
  • daylight-saving gaps are rejected
  • ambiguous fall-back times require an explicit ScheduleAmbiguousTimeResolution
  • the resolved UTC instant plus the original local metadata are persisted for inspection

The default resolver uses TimeZoneInfo.FindSystemTimeZoneById(...). That means callers must supply time zone ids valid on the host OS. This preview does not translate between Windows and IANA identifiers for you.

Known limitations

  • preview-only maturity; ship it as a bounded scheduler feature, not as a mature job platform
  • one-time scheduling only; no recurrence, cron, calendars, or workflow orchestration
  • no library-owned HTTP management surface and no typed scheduling client in this preview
  • host-dependent time zone ids; TimeZoneInfo.FindSystemTimeZoneById(...) does not normalize Windows/IANA differences for you
  • long-lived schedules depend on payload compatibility; after future payload-shape changes, old rows may need to be rescheduled or recreated instead of assumed forward-compatible forever
  • external schedulers such as Hangfire or Quartz are still the better fit for many cases, especially recurring or non-notification work
  • no template recipe scheduling; materialize the concrete NotificationRequest before scheduling it
  • durable persistence required; scheduling is not available in Simple mode

Design intent

This package exists so teams can keep lightweight notification scheduling inside Lumarin.Notify without turning Core into a general scheduler. The boundary is deliberate: scheduling stays optional, durable, and narrowly scoped to notification delivery.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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 (2)

Showing the top 2 NuGet packages that depend on Lumarin.Notify.Scheduling:

Package Downloads
Lumarin.Notify.Scheduling.EntityFrameworkCore

Preview EF Core persistence support for the Lumarin.Notify scheduling feature.

Lumarin.Notify.PolicyRouting.Scheduling

Optional scheduling bridge for policy-routing quiet-hours deferral in Lumarin.Notify

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.0-preview.199 73 5/14/2026
0.8.0-preview.162 69 4/26/2026
0.8.0-preview.161 60 4/26/2026
0.8.0-preview.160 55 4/26/2026
0.8.0-preview.159 56 4/26/2026
0.8.0-preview.158 57 4/26/2026
0.8.0-preview.157 54 4/25/2026
0.8.0-preview.156 64 4/25/2026
0.8.0-preview.155 62 4/25/2026
0.8.0-preview.154 55 4/25/2026
0.8.0-preview.153 57 4/25/2026
0.8.0-preview.150 57 4/25/2026
0.8.0-preview.133 58 4/23/2026
0.8.0-preview.132 59 4/23/2026
0.8.0-preview.130 54 4/23/2026
0.8.0-preview.128 63 4/23/2026
0.8.0-preview.120 71 4/21/2026
0.8.0-preview.115 54 4/18/2026
0.8.0-preview.114 61 4/18/2026
0.8.0-preview.113 65 4/17/2026
Loading failed