NotifyNet 1.0.0

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

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:

  1. Tenant-specific template (TenantId matches)
  2. 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

  • TenantId on NotificationModel → selects tenant-specific template + provider config
  • TenantId = 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: DirectContactNotifyNet_UsersIReceiverResolver


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_Logs with Status = Failed and ErrorMessage

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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