Lumarin.Notify.Scheduling
0.8.0-preview.199
dotnet add package Lumarin.Notify.Scheduling --version 0.8.0-preview.199
NuGet\Install-Package Lumarin.Notify.Scheduling -Version 0.8.0-preview.199
<PackageReference Include="Lumarin.Notify.Scheduling" Version="0.8.0-preview.199" />
<PackageVersion Include="Lumarin.Notify.Scheduling" Version="0.8.0-preview.199" />
<PackageReference Include="Lumarin.Notify.Scheduling" />
paket add Lumarin.Notify.Scheduling --version 0.8.0-preview.199
#r "nuget: Lumarin.Notify.Scheduling, 0.8.0-preview.199"
#:package Lumarin.Notify.Scheduling@0.8.0-preview.199
#addin nuget:?package=Lumarin.Notify.Scheduling&version=0.8.0-preview.199&prerelease
#tool nuget:?package=Lumarin.Notify.Scheduling&version=0.8.0-preview.199&prerelease
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(...)insideAddLumarinNotify(...)is the primary activation path.- When
Lumarin.Notify.Scheduling.EntityFrameworkCoreis referenced, the root builder wires scheduling persistence automatically. - Direct
UseLumarinNotifyScheduling(...)plusAddLumarinNotifySchedulingEntityFrameworkCore()remains available for advanced composition. - Registering or mutating
SchedulingOptionsalone 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
MaxDispatchAttemptsis exhausted, then marks the rowFailed. - If
ExpiresAthas passed before firing, the row becomesExpired. 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 stillScheduled.RescheduleAsync(...)is allowed forScheduled,Cancelled,Expired, andFailedrows.Claimedrows cannot be cancelled or rescheduled until the lease is gone and the row is reclaimable again.Firedrows 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
DateTimewithDateTimeKind.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
NotificationRequestbefore 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 | Versions 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. |
-
net10.0
- Lumarin.Notify (>= 0.8.0-preview.199)
- Lumarin.Notify.Abstractions (>= 0.8.0-preview.199)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.7)
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 |