Lumarin.Notify.EntityFrameworkCore.PostgreSQL 0.8.0-preview.113

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

Lumarin.Notify

Multi-channel notification infrastructure for .NET. Reliable delivery across in-app feeds, realtime SignalR push, email, SMS, and mobile push — from a single unified API, with durable delivery tracking, templating, retry, rate limiting, and user preference enforcement built in.

var result = await hub.SendAsync(new NotificationRequest(
    ScopeType: "user",
    ScopeId:   "user-123",
    Category:  "order.shipped",
    Content: new NotificationContent("Order shipped", "Your order #456 is on the way!"),
    Recipients: new NotificationRecipients(
        Identities: ["user-123"],
        Channels:   [NotificationChannel.InApp, NotificationChannel.Email]),
    Options: new NotificationOptions(
        IdempotencyKey: "order-456-shipped",
        Priority:       NotificationPriority.High)));

Choose your packages

What you need Packages to add
Send notifications, in-app feed, delivery tracking Core + EntityFrameworkCore
HTTP endpoints for feed, preferences, devices + AspNetCore
Template rendering (SendFromTemplateAsync) + Templates
Ordered fallback routing and simulation + PolicyRouting
Quiet-hours deferral for policy routing (durable only) + PolicyRouting + Scheduling + Scheduling.EntityFrameworkCore + PolicyRouting.Scheduling
One-time scheduled notifications (preview) + Scheduling + Scheduling.EntityFrameworkCore
Realtime WebSocket push + Channels.SignalR
Email delivery + Channels.Email + one email adapter package
Push notifications (FCM / OneSignal) + Channels.Push + one push adapter package
SMS (Twilio) + Channels.Sms + one SMS adapter package

Installation

# Always required
dotnet add package Lumarin.Notify.Core
dotnet add package Lumarin.Notify.EntityFrameworkCore

# HTTP endpoints for feed, preferences, devices, and template admin
dotnet add package Lumarin.Notify.AspNetCore

# Template engine — required only for SendFromTemplateAsync
dotnet add package Lumarin.Notify.Templates

# Optional policy-driven ordered fallback routing
dotnet add package Lumarin.Notify.PolicyRouting

# Optional durable quiet-hours deferral bridge for policy routing
dotnet add package Lumarin.Notify.PolicyRouting.Scheduling

# Optional one-time scheduling preview
dotnet add package Lumarin.Notify.Scheduling
dotnet add package Lumarin.Notify.Scheduling.EntityFrameworkCore

# Add the channels you need
dotnet add package Lumarin.Notify.Channels.SignalR

# Email requires the base channel plus one provider adapter
dotnet add package Lumarin.Notify.Channels.Email
dotnet add package Lumarin.Notify.Channels.Email.Smtp
# or: Lumarin.Notify.Channels.Email.Ses
# or: Lumarin.Notify.Channels.Email.SendGrid

# Push requires the base channel plus one provider adapter
dotnet add package Lumarin.Notify.Channels.Push
dotnet add package Lumarin.Notify.Channels.Push.Fcm
# or: Lumarin.Notify.Channels.Push.OneSignal

# SMS requires the base channel plus one provider adapter
dotnet add package Lumarin.Notify.Channels.Sms
dotnet add package Lumarin.Notify.Channels.Sms.Twilio

Provider credential requirements

Channel package Required credentials
Channels.Email + .Email.Smtp / .Email.Ses / .Email.SendGrid SMTP host/port/credentials, AWS SES credentials, or SendGrid API key
Channels.Push + .Push.Fcm / .Push.OneSignal Firebase service account (FCM) or OneSignal App ID + API key
Channels.Sms + .Sms.Twilio Twilio Account SID, Auth Token, and sender number
Channels.SignalR None — runs in-process; Redis or Azure SignalR Service for multi-node

Channel registration fails fast at startup if the matching provider adapter package or required credentials are missing.

Prerequisites

  • .NET 10 SDK
  • PostgreSQL or SQL Server for production; EF Core InMemory for dev/tests

Architecture and governance

For repository rules and contribution guidance:

  • human contributors: CONTRIBUTING.md
  • AI coding agents: AGENTS.md
  • canonical governance docs: docs/architecture/00-README.md

The durable source of truth for architecture guardrails, package boundaries, composition modes, docs governance, and review checklists lives under docs/architecture/.

Adoption profiles

Profile When to use it Core registration
Simple Operational without EF Core using in-memory defaults, no hosted delivery runtime, consumer-owned orchestration and dispatch AddLumarinNotifySimple(...)
Durable Notifications and deliveries stored in EF Core, direct-delivery runtime when background workers are enabled, no outbox by default AddLumarinNotify(...) + AddLumarinNotifyEntityFrameworkCore(...) + AddLumarinNotifyPostgreSql() or AddLumarinNotifySqlServer()
Outbox Explicit durable outbox mode with mandatory startup validation and outbox runtime selection when background workers are enabled Durable profile + UseLumarinNotifyOutbox(...) + AddLumarinNotifyOutboxEntityFrameworkCore()

Simple setup

// Program.cs
using Lumarin.Notify.Core.DependencyInjection;

builder.Services.AddLumarinNotifySimple(options =>
{
    options.Delivery.MaxRetries = 3;
    options.Templates.Engine = "scriban";
});

Use this profile when you want the public notification services without EF Core. The library registers in-memory defaults for the required persistence seams and does not register a hosted delivery runtime in simple mode. Notifications and queued deliveries are created in-memory, and dispatch remains consumer-owned.

Durable setup

// Program.cs
using Lumarin.Notify.Core.DependencyInjection;
using Lumarin.Notify.EntityFrameworkCore.Extensions;
using Lumarin.Notify.EntityFrameworkCore.PostgreSQL.DependencyInjection;

LumarinNotifyOptions? notifyOptions = null;

builder.Services.AddLumarinNotify(options =>
{
    options.UseDatabase(
        builder.Configuration.GetConnectionString("LumarinNotify")!,
        "postgresql"); // or "sqlserver"

    options.Delivery.MaxRetries = 3;
    notifyOptions = options;
});

builder.Services.AddLumarinNotifyEntityFrameworkCore(notifyOptions!);
builder.Services.AddLumarinNotifyPostgreSql(); // or AddLumarinNotifySqlServer()

var app = builder.Build();
await app.Services.MigrateLumarinNotifyAsync(); // applies EF migrations

app.Run();

This profile replaces the core in-memory defaults with EF Core repositories and keeps the direct-delivery runtime unless you explicitly opt into outbox mode.

Inject INotificationHub and send:

var result = await hub.SendAsync(new NotificationRequest(
    ScopeType: "user",
    ScopeId:   "user-123",
    Category:  "order.shipped",
    Content: new NotificationContent("Order shipped", "Your order #456 is on the way!"),
    Recipients: new NotificationRecipients(
        Identities: ["user-123"],
        Channels:   [NotificationChannel.InApp, NotificationChannel.Email]),
    Options: new NotificationOptions(
        IdempotencyKey: "order-456-shipped",
        Priority:       NotificationPriority.High)));

Outbox setup

using Lumarin.Notify.Outbox.DependencyInjection;
using Lumarin.Notify.Outbox.EntityFrameworkCore.DependencyInjection;

builder.Services.UseLumarinNotifyOutbox(outbox =>
{
    outbox.BatchSize = 50;
    outbox.MaxRetries = 5;
});
builder.Services.AddLumarinNotifyOutboxEntityFrameworkCore();

Outbox settings are configured through UseLumarinNotifyOutbox(...), not through LumarinNotifyOptions. When background workers are enabled, UseLumarinNotifyOutbox(...) selects the outbox runtime profile instead of mutating existing hosted-service registrations. When background workers are disabled, the consumer owns outbox dispatch and retention orchestration explicitly. Mandatory outbox startup validation still runs even if optional startup validation diagnostics were disabled earlier in core options. Outbox EF persistence stays opt-in for runtime and migrations. Base SQL Server/PostgreSQL provider packages no longer pull the outbox EF package by default; generate outbox-aware migrations from the dedicated provider-specific Outbox migration assemblies or from an application-owned migration assembly that references Lumarin.Notify.Outbox.EntityFrameworkCore. If you bootstrap schema creation at runtime, use the helper that matches the active profile:

// Durable profile
await app.Services.MigrateLumarinNotifyAsync();

// Outbox profile
await app.Services.MigrateLumarinNotifyOutboxAsync();

If the host also registers AddLumarinNotifyAdminEntityFrameworkCore(), use await app.Services.MigrateLumarinNotifyAdminAsync(); so the admin control-plane provider and policy schema is applied before enabling admin mutation or policy routes on an existing database.

Scheduling preview

Lumarin.Notify.Scheduling is an optional preview package for durable one-time scheduled notifications. It is notification-native, not a general scheduler, and it is not part of the minimal Release 0 validation path.

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

builder.Services.AddLumarinNotifySchedulingEntityFrameworkCore();
builder.Services.UseLumarinNotifyScheduling(options =>
{
    options.BatchSize = 100;
    options.ClaimLeaseDuration = TimeSpan.FromMinutes(1);
    options.MaxDispatchAttempts = 5;
});

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

var scheduling = app.Services.GetRequiredService<ISchedulingOperations>();
await scheduling.ScheduleAsync(new ScheduleNotificationRequest(
    new NotificationRequest(
        ScopeType: "user",
        ScopeId: "user-123",
        Category: "digest.morning",
        Content: new NotificationContent("Morning digest", "3 new jobs match your search."),
        Recipients: new NotificationRecipients(["user-123"])),
    ScheduleInput.ForDelay(TimeSpan.FromMinutes(30))));

UseLumarinNotifyScheduling(...) activates the feature runtime and startup validation. AddLumarinNotifySchedulingEntityFrameworkCore() wires persistence only. If you need recurrence, cron, workflows, dashboards, or general background jobs, use Hangfire or Quartz instead.

Policy routing

Lumarin.Notify.PolicyRouting is an optional package for ordered channel routing, priority-based route profiles, bounded delivery-path escalation, tenant overrides, bounded cost-aware ordering, structured traces, simulation, and failure-time continuation when a delivery step reaches a terminal failure.

using Lumarin.Notify.Models;
using Lumarin.Notify.PolicyRouting.DependencyInjection;
using Lumarin.Notify.PolicyRouting.Models;

builder.Services.UseLumarinNotifyPolicyRouting(options =>
{
    options.Policy.DefaultChannels.AddRange([
        NotificationChannel.Push,
        NotificationChannel.Email,
    ]);

    options.Policy.LowPriorityChannels.AddRange([
        NotificationChannel.InApp,
        NotificationChannel.Email,
    ]);

    options.Policy.HighPriorityEscalationChannels.AddRange([
        NotificationChannel.Email,
        NotificationChannel.Sms,
    ]);

    options.Policy.QuietHoursStrategy = PolicyRoutingQuietHoursStrategy.DeferWhenAllRoutesQuietHoursBlocked;
    options.Policy.CriticalBypassesQuietHours = true;
});

UseLumarinNotifyPolicyRouting(...) is the feature switch. It does not add a hosted runtime, it works with Simple, Durable, and Outbox profiles, and it keeps delivery on the normal Lumarin.Notify pipeline.

Release B Track 3 supports ordered channel routing, priority-based route profiles, bounded high/critical route widening, static cost hints, tenant overrides, structured execution traces, simulation, and continuation after terminal failure. Continuation depends on internal route-plan metadata; if that metadata is missing or unreadable, the runtime falls back to the normal dead-letter path. The base package stays channel-level and mode-neutral: no provider-level routing, timed workflow escalation, percentage rollout, or workflow/state-machine orchestration is included in this slice.

Policy-routing quiet-hours deferral bridge

Lumarin.Notify.PolicyRouting.Scheduling is the only package that turns policy-routing quiet-hours suppression into durable deferral.

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

builder.Services.AddLumarinNotifySchedulingEntityFrameworkCore();
builder.Services.UseLumarinNotifyScheduling(options =>
{
    options.BatchSize = 100;
    options.ClaimLeaseDuration = TimeSpan.FromMinutes(1);
    options.MaxDispatchAttempts = 5;
});
builder.Services.UseLumarinNotifyPolicyRoutingScheduling();

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

This bridge is durable-only. It requires the durable or outbox profile plus active policy routing and scheduling registrations, and it fails fast in AddLumarinNotifySimple(...) mode.

When the effective policy uses DeferWhenAllRoutesQuietHoursBlocked, SendAsync(...) may return Success = true with AcceptedMode = Deferred, a ScheduledNotificationId, a DeferredUntilUtc timestamp, and a compact DecisionTrace. If any identity still has an immediate route, Lumarin.Notify keeps the immediate path; Release B does not split one request into mixed immediate and scheduled clones.

HTTP endpoints (AspNetCore adapter)

Lumarin.Notify.AspNetCore adds ready-made minimal API endpoints for the notification feed, user preferences, device registration, template admin, and delivery tracking.

// Register the host adapter (after AddLumarinNotify)
builder.Services.AddLumarinNotifyAspNetCore(options =>
{
    options.ScopeType = "user";
    // Default identity claim order: ClaimTypes.NameIdentifier, "sub", "oid", "user_id"
    // options.IdentityClaimTypes = ["sub", "oid"]; // e.g. raw JWT / MapInboundClaims = false
    options.Features.EnableTemplates = true;  // template admin endpoints (off by default)
    options.Features.EnableTracking  = true;  // delivery/dead-letter tracking endpoints (off by default)
});

// Optional: register a custom resolver if claim-order configuration is not enough
builder.Services.AddSingleton<ILumarinNotifyIdentityResolver, YourIdentityResolver>();

// Map the endpoints
app.MapLumarinNotifyFeedApi();
app.MapLumarinNotifyPreferencesApi();
app.MapLumarinNotifyDevicesApi();
app.MapLumarinNotifyTemplatesApi();  // only active when Features.EnableTemplates = true
app.MapLumarinNotifyTrackingApi();   // only active when Features.EnableTracking = true

Default routes (rebaseable via app.MapGroup(...)):

Endpoint Route
Feed GET /api/notify/feed
Unread count GET /api/notify/feed/unread-count
Mark read POST /api/notify/feed/{notificationId}/read
Mark all read POST /api/notify/feed/mark-all-read
Dismiss POST /api/notify/feed/{notificationId}/dismiss
Archive POST /api/notify/feed/{notificationId}/archive
Unarchive DELETE /api/notify/feed/{notificationId}/archive
Dismiss (stable delete alias) DELETE /api/notify/feed/{notificationId}
Bulk mark read POST /api/notify/feed/bulk/mark-read
Bulk archive POST /api/notify/feed/bulk/archive
Bulk unarchive POST /api/notify/feed/bulk/unarchive
Bulk dismiss POST /api/notify/feed/bulk/dismiss
Preferences GET/PUT /api/notify/preferences
Register device POST /api/notify/devices
Delete device DELETE /api/notify/devices/{deviceId}
Templates GET/PUT /api/notify/templates/{templateKey}
Deliveries GET /api/notify/deliveries/{notificationId}
Dead letters GET /api/notify/dead-letters

Feed queries accept optional isRead, category, and lifecycle=Active|Archived|Dismissed|All filters. Feed items now expose optional archivedAt and dismissedAt timestamps. Bulk lifecycle endpoints return NotificationFeedBulkMutationResponse with per-item machine-readable result codes instead of partial-failure transport semantics.

Upgrade note: inbox read, archive, and dismiss state is tracked per recipient. The explicit dismiss route and the stable delete alias both affect only the resolved recipient. A read, archive, or dismiss action for one recipient no longer changes feed or unread state for other recipients who share the same notification.

Security: AddLumarinNotifyAspNetCore registers DevelopmentOnlyLumarinNotifyAuthorizationPolicy by default, which grants all callers access to template admin and tracking endpoints. Register a custom ILumarinNotifyAuthorizationPolicy before deploying to production.

Template sends

var result = await hub.SendFromTemplateAsync(
    scopeType:   "user",
    scopeId:     "user-123",
    templateKey: "order-shipped",
    data:        new { Name = "Alex", OrderId = "456" },
    recipients:  new NotificationRecipients(["user-123"]),
    locale:      "en");

Requires Lumarin.Notify.Templates. Template engine is "scriban" (default) or "liquid".

Channel registration

Each channel is registered separately. Examples:

// SignalR (realtime WebSocket push)
builder.Services.AddLumarinNotifySignalR(options =>
{
    options.HubPath = "/hubs/notifications";
    options.RequireAuthenticatedUser = true;
});
app.MapLumarinNotifySignalR(); // maps the hub

// Email (read from configuration; fails fast if credentials missing)
var emailOptions = builder.Configuration
    .GetSection("Lumarin.Notify:Channels:Email")
    .Get<EmailChannelOptions>() ?? new EmailChannelOptions();

if (emailOptions.Enabled)
    builder.Services.AddEmailChannel(emailOptions);

InMemory database (dev / tests)

If you do not need durable persistence at all, prefer AddLumarinNotifySimple(...) instead of this override.

builder.Services.AddDbContext<LumarinNotifyDbContext>(opts =>
    opts.UseInMemoryDatabase("LumarinNotify_Dev"));

builder.Services.AddScoped<INotificationRepository, NotificationRepository>();
builder.Services.AddScoped<ITemplateRepository, TemplateRepository>();
builder.Services.AddScoped<IDeliveryRepository, DeliveryRepository>();
builder.Services.AddScoped<IDeviceRepository, DeviceRepository>();
builder.Services.AddScoped<IDeadLetterRepository, DeadLetterRepository>();
builder.Services.AddScoped<IPreferenceRepository, PreferenceRepository>();
builder.Services.AddScoped<ITemplateAuditRepository, TemplateAuditRepository>();

No migrations needed. All data resets on restart.

Docker Compose

# API-only
docker compose --profile api up --build

# API + frontend
docker compose --profile frontend up --build

Copy .env.example as the starting point for runtime parameters.

Next Steps

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 (4)

Showing the top 4 NuGet packages that depend on Lumarin.Notify.EntityFrameworkCore.PostgreSQL:

Package Downloads
Lumarin.Notify.Outbox.EntityFrameworkCore.PostgreSQL

Optional PostgreSQL provider package for Lumarin.Notify outbox EF Core persistence.

Lumarin.Notify.Scheduling.EntityFrameworkCore.PostgreSQL

Preview PostgreSQL provider package for Lumarin.Notify scheduling EF Core persistence.

Lumarin.Notify.Admin.EntityFrameworkCore.PostgreSQL

Optional PostgreSQL provider package for Lumarin.Notify admin EF Core persistence.

Lumarin.Notify.Hosting.Embedded.PostgreSQL

Optional embedded-hosting convenience profile for Lumarin.Notify on PostgreSQL

GitHub repositories

This package is not used by any popular GitHub repositories.

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