NotifyNet 1.0.0
dotnet add package NotifyNet --version 1.0.0
NuGet\Install-Package NotifyNet -Version 1.0.0
<PackageReference Include="NotifyNet" Version="1.0.0" />
<PackageVersion Include="NotifyNet" Version="1.0.0" />
<PackageReference Include="NotifyNet" />
paket add NotifyNet --version 1.0.0
#r "nuget: NotifyNet, 1.0.0"
#:package NotifyNet@1.0.0
#addin nuget:?package=NotifyNet&version=1.0.0
#tool nuget:?package=NotifyNet&version=1.0.0
NotifyNet
Provider-agnostic, multi-tenant notification library for ASP.NET Core.
Supports Email (SMTP / SendGrid), SMS (TextLk), and In-App notifications — Push (FCM) coming soon.
Packages
| Package | Purpose |
|---|---|
NotifyNet |
Core — interfaces, models, pipeline |
NotifyNet.EFCore |
SQL Server storage via EF Core |
NotifyNet.Smtp |
Email via SMTP (MailKit) |
NotifyNet.SendGrid |
Email via SendGrid |
NotifyNet.TextLk |
SMS via Text.lk |
NotifyNet.Hangfire |
Delayed + scheduled jobs via Hangfire |
In-app notifications are built into
NotifyNet.EFCore— no extra package needed.
Install what you need:
dotnet add package NotifyNet.EFCore
dotnet add package NotifyNet.Smtp # OR NotifyNet.SendGrid
dotnet add package NotifyNet.TextLk # SMS
dotnet add package NotifyNet.Hangfire # only if you need Delay/Scheduled mode
Quick Start
1. Register in DI
// Program.cs / DependencyInjection.cs
services.AddNotifyNet(opt =>
{
// opt.UseTestMode("dev@example.com"); // redirects all emails in dev
// opt.UseUnsubscribe("https://app.com/unsubscribe", "your-secret-key");
});
services.UseEFCore(connectionString);
services.UseSmtp(cfg =>
{
cfg.Host = "smtp.gmail.com";
cfg.Port = 587;
cfg.Username = "you@gmail.com";
cfg.Password = "app-password";
cfg.FromEmail = "you@gmail.com";
cfg.FromName = "My App";
cfg.UseSsl = false;
});
// Only if using Delay / Scheduled mode:
services.UseHangfire(connectionString);
2. Apply migrations (one-time setup)
// Program.cs — call before app.Run()
app.ApplyNotifyNetMigrations();
// If using Hangfire dashboard:
app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = [] });
This creates 7 tables in your database:
NotifyNet_Templates, NotifyNet_ChannelStrategies, NotifyNet_TenantConfigs,
NotifyNet_Logs, NotifyNet_Subscriptions, NotifyNet_Users, NotifyNet_UserContacts
3. Create a channel strategy
Before sending, tell NotifyNet which channels are active for your notification type:
POST /api/admin/notifynet/strategies
{
"notificationType": 1,
"channel": 1, // 1 = Email
"priority": 1,
"tenantId": null, // null = global
"isEnabled": true
}
4. Create a template
POST /api/admin/notifynet/templates
{
"notificationType": 1,
"channel": 1,
"tenantId": null,
"name": "Welcome Email",
"subject": "Hello {{ name }}!",
"body": "<p>Welcome, {{ name }}. Your booking is confirmed.</p>",
"isActive": true
}
Templates use Fluid (Liquid) syntax.
5. Send a notification
// Inject INotificationService
await notificationService.SendAsync([new NotificationModel
{
NotificationType = 1,
ReceiverId = "user-123", // resolves email from NotifyNet_Users
// OR
DirectContact = new DirectContact { Emails = ["user@example.com"] },
Payload = new { name = "Arun", bookingDate = "Jan 10" },
TenantId = "business-abc", // null for global/single-tenant
Mode = NotificationMode.Immediate,
}]);
Notification Modes
| Mode | Usage |
|---|---|
Immediate |
Send right away (default) |
Delay |
Send after X minutes — requires NotifyNet.Hangfire |
Scheduled |
Send at a specific UTC datetime — requires NotifyNet.Hangfire |
// Delay
new NotificationModel
{
Mode = NotificationMode.Delay,
Delay = TimeSpan.FromMinutes(30),
...
}
// Scheduled — specific UTC time
new NotificationModel
{
Mode = NotificationMode.Scheduled,
ScheduledAt = DateTimeOffset.UtcNow.AddHours(2),
...
}
Cancel a scheduled notification
// Inject INotifyNetScheduledJobService
var pending = await scheduledJobService.GetPendingAsync();
// Find the job by NotificationType / ReceiverId, then:
await scheduledJobService.CancelAsync(jobId);
User Management (Built-in)
Store users and their contacts so ReceiverId resolution works automatically.
No need to implement IReceiverResolver.
// Inject INotifyNetUserService
await userService.AddAsync(new AddUserRequest
{
UserId = "user-123",
DisplayName = "Arun"
});
await userService.AddContactAsync("user-123", new AddContactRequest
{
ContactType = 1, // 1=Email, 2=Phone, 3=FcmToken
Value = "arun@example.com",
IsPrimary = true
});
UserId is globally unique — no TenantId on users. The same user can belong to multiple tenants via notifications.
Template System
Templates use Fluid (Liquid) syntax. All keys from Payload are available as variables.
Subject: Hello {{ name }}!
Body:
<p>Hi {{ name }},</p>
<p>Your booking on {{ date }} at {{ time }} is confirmed.</p>
{% if unsubscribe_url %}
<p><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
{% endif %}
{{ unsubscribe_url }} is auto-injected when UseUnsubscribe() is configured and ReceiverId is set.
Template resolution order:
- Tenant-specific template (
TenantIdmatches) - Global template (
TenantId = null)
Idempotency
Prevent duplicate sends (e.g. from retries):
new NotificationModel
{
IdempotencyKey = $"booking-confirm-{bookingId}",
...
}
If a notification with the same key + channel was already sent successfully, it is silently skipped.
Test Mode
Redirects all outgoing emails to a single inbox in development:
services.AddNotifyNet(opt => opt.UseTestMode("dev@example.com"));
The original recipient is recorded in NotifyNet_Logs.Note.
Subscription / Opt-Out
// Inject INotifyNetSubscriptionService
// Opt out of a specific notification type
await subscriptionService.OptOutAsync("user-123", tenantId: null, notificationType: 1, channel: null);
// Opt back in
await subscriptionService.OptInAsync("user-123", tenantId: null, notificationType: 1, channel: null);
Opt-out is checked automatically before sending. Opted-out notifications are logged and skipped.
Delivery Logs
// Inject INotifyNetLogService
var logs = await logService.GetAsync(new LogQuery
{
TenantId = "business-abc",
NotificationType = 1,
Status = (int)NotificationStatus.Failed
});
Multi-Tenant
TenantIdonNotificationModel→ selects tenant-specific template + provider configTenantId = null→ uses global template + default appsettings config- Auth/OTP scenarios that don't belong to a tenant: pass
TenantId = null
Per-tenant SMTP/SendGrid credentials:
POST /api/admin/notifynet/configs
{
"tenantId": "business-abc",
"channel": 1,
"configJson": "{\"Host\":\"smtp.custom.com\",\"Port\":587,...}"
}
Custom Receiver Resolver (Optional)
If you don't use NotifyNet_Users, implement IReceiverResolver:
public class MyResolver : IReceiverResolver
{
public async Task<NotificationReceiver> ResolveAsync(string receiverId, string? tenantId)
{
var user = await _db.Users.FindAsync(receiverId);
return new NotificationReceiver { Emails = [user.Email] };
}
}
// Register:
services.AddScoped<IReceiverResolver, MyResolver>();
Resolution priority: DirectContact → NotifyNet_Users → IReceiverResolver
Retry Behaviour
- Immediate mode: 3 in-process retries (2s, 4s, 6s backoff)
- After 3 failures: re-queues to Hangfire for a final retry in 5 minutes (if
UseHangfire()registered) - All attempts logged to
NotifyNet_LogswithStatus = FailedandErrorMessage
SMS (TextLk)
Register
services.UseTextLk(cfg =>
{
cfg.ApiKey = configuration["NotifyNet:TextLk:ApiKey"] ?? "";
cfg.SenderId = configuration["NotifyNet:TextLk:SenderId"] ?? "";
});
Add phone contact to user
await userService.AddContactAsync("user-123", new AddContactRequest
{
ContactType = 2, // 2 = Phone
Value = "+94771234567", // E.164 format
IsPrimary = true
});
Create SMS template (Channel = 2, plain text)
POST /api/admin/notifynet/templates
{
"notificationType": 1,
"channel": 2,
"tenantId": null,
"name": "Booking Reminder SMS",
"subject": null,
"body": "Hi {{ name }}, your booking on {{ date }} is confirmed.",
"isActive": true
}
Create SMS strategy
POST /api/admin/notifynet/strategies
{
"notificationType": 1,
"channel": 2,
"priority": 1,
"tenantId": null,
"isEnabled": true
}
Send SMS
await notificationService.SendAsync([new NotificationModel
{
NotificationType = 1,
ReceiverId = "user-123", // resolves phone from NotifyNet_UserContacts
// OR
DirectContact = new DirectContact { Phones = ["+94771234567"] },
Payload = new { name = "Arun", date = "Jan 10" },
Mode = NotificationMode.Immediate,
}]);
Both Email (channel=1) and SMS (channel=2) strategies can be active for the same NotificationType — the executor sends to all active channels.
Note:
UseTestMode()only redirects Email. SMS has no test mode — use a real number or provider-level sandbox for testing.
In-App Notifications (Channel = 4)
In-app notifications are stored in NotifyNet_InAppNotifications table. No external provider needed — UseEFCore() handles everything.
Create strategy + template
POST /api/admin/notifynet/strategies
{ "notificationType": 1, "channel": 4, "priority": 1, "tenantId": null, "isEnabled": true }
POST /api/admin/notifynet/templates
{ "notificationType": 1, "channel": 4, "tenantId": null, "name": "Booking Confirmed",
"subject": "Booking Confirmed!", "body": "Hi {{ name }}, your booking is confirmed.", "isActive": true }
Template subject → stored as Title. body → stored as Body.
Send in-app notification
await notificationService.SendAsync([new NotificationModel
{
NotificationType = 1,
ReceiverId = "user-123", // required for in-app
Payload = new { name = "Arun" },
Mode = NotificationMode.Immediate,
}]);
ReceiverId is required — in-app notifications are always user-specific.
Read notifications (inject INotifyNetInAppService)
// All notifications (unread first)
var notifications = await inAppService.GetAsync("user-123", tenantId: null);
// Unread only
var unread = await inAppService.GetUnreadAsync("user-123", tenantId: null);
// Unread count (badge)
var count = await inAppService.GetUnreadCountAsync("user-123", tenantId: null);
// Mark one as read
await inAppService.MarkAsReadAsync(notificationId);
// Mark all as read
await inAppService.MarkAllAsReadAsync("user-123", tenantId: null);
// Delete
await inAppService.DeleteAsync(notificationId);
Build your own controller injecting INotifyNetInAppService — NotifyNet handles the DB layer.
Enums Reference
// NotificationChannel
Email = 1, Sms = 2, Push = 3
// NotificationMode
Immediate = 1, Delay = 2, Scheduled = 3
// NotificationPriority (maps to Hangfire queue)
Low = 1, Normal = 2, High = 3
// NotificationStatus (in logs)
Pending = 1, Sent = 2, Failed = 3
// ContactType (in NotifyNet_UserContacts)
Email = 1, Phone = 2, FcmToken = 3
| Product | Versions 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 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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on NotifyNet:
| Package | Downloads |
|---|---|
|
NotifyNet.TextLk
Text.lk SMS provider for NotifyNet. Plug in Text.lk as the SMS delivery channel. Supports per-tenant API keys with thread-safe HttpClient handling. |
|
|
NotifyNet.Hangfire
Hangfire scheduler for NotifyNet. Enables Delay and Scheduled notification modes backed by Hangfire with SQL Server storage. Includes job listing and cancellation support. |
|
|
NotifyNet.Smtp
SMTP email provider for NotifyNet using MailKit. Plug in SMTP (Gmail, Outlook, custom) as the email delivery channel. Supports per-tenant credentials and test mode. |
|
|
NotifyNet.EFCore
SQL Server / EF Core persistence for NotifyNet. Provides storage for templates, channel strategies, tenant configs, delivery logs, subscriptions, users, and in-app notifications. Includes the notification execution engine and EF migrations. |
|
|
NotifyNet.SendGrid
SendGrid email provider for NotifyNet. Plug in SendGrid as the email delivery channel with support for per-tenant API keys and test mode. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 232 | 6/23/2026 |