Lumarin.Notify.EntityFrameworkCore.PostgreSQL
0.8.0-preview.114
See the version list below for details.
dotnet add package Lumarin.Notify.EntityFrameworkCore.PostgreSQL --version 0.8.0-preview.114
NuGet\Install-Package Lumarin.Notify.EntityFrameworkCore.PostgreSQL -Version 0.8.0-preview.114
<PackageReference Include="Lumarin.Notify.EntityFrameworkCore.PostgreSQL" Version="0.8.0-preview.114" />
<PackageVersion Include="Lumarin.Notify.EntityFrameworkCore.PostgreSQL" Version="0.8.0-preview.114" />
<PackageReference Include="Lumarin.Notify.EntityFrameworkCore.PostgreSQL" />
paket add Lumarin.Notify.EntityFrameworkCore.PostgreSQL --version 0.8.0-preview.114
#r "nuget: Lumarin.Notify.EntityFrameworkCore.PostgreSQL, 0.8.0-preview.114"
#:package Lumarin.Notify.EntityFrameworkCore.PostgreSQL@0.8.0-preview.114
#addin nuget:?package=Lumarin.Notify.EntityFrameworkCore.PostgreSQL&version=0.8.0-preview.114&prerelease
#tool nuget:?package=Lumarin.Notify.EntityFrameworkCore.PostgreSQL&version=0.8.0-preview.114&prerelease
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
Signed webhook receipts can also carry additive feedback metadata such as FeedbackKind, provider-native RawStatus / RawCode, ExternalEventId, IdempotencyKey, and a push DeviceId. When those fields are present, they are included in the webhook receipt signature. Duplicate receipts with the same normalized correlation data are ignored, and invalid push-device feedback only deactivates the specific supplied DeviceId.
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:
AddLumarinNotifyAspNetCoreregistersDevelopmentOnlyLumarinNotifyAuthorizationPolicyby default, which grants all callers access to template admin and tracking endpoints. Register a customILumarinNotifyAuthorizationPolicybefore 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
- Getting Started guide — zero to first notification in 10 minutes
- ASP.NET Core Hosting — HTTP endpoints, identity resolution, authorization
- API Reference — full interface and DI reference
- Channel guides — per-channel setup and credential requirements
- SignalR realtime guide — WebSocket client integration
- Docker Compose guide — production container configuration
| 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.EntityFrameworkCore (>= 0.8.0-preview.114)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.1)
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 |