Notifications
The Curiosus.Notifications subsystem provides a flexible, extensible notification system that supports multiple channels (Email, SMS) with queued processing, reliability features, and post-processing capabilities.
Architecture Overview
The notification system follows a channel-based architecture where:
- Notificator (
INotificator) - Main service that routes notifications to appropriate channels - Notification Channels (
INotificationChannel) - Handle actual sending via specific providers (Email, SMS) - Notification Builders (
INotificationBuilder) - Convert metadata into channel-specific notifications - Post Processors - Handle actions after notification sending (logging, analytics, etc.)
flowchart TD
A[Client Code] --> B[INotificator]
B --> C[Notification Builders]
C --> D[Email Channel]
C --> E[SMS Channel]
D --> F[Email Provider]
E --> G[SMS Provider]
D --> H[Post Processors]
E --> H
Key Components
Notificator
The main service (Notificator) that:
- Accepts notification metadata (
INotificationMetadata) - Routes to appropriate builders based on notification type
- Manages multiple channels simultaneously
- Provides both synchronous (
NotifyAsync) and fire-and-forget (NotifyAndForgot) methods
Notification Channels
Background services that:
- Process notifications in a queue (one at a time per channel)
- Handle retries and error management
- Support graceful shutdown with pending notification handling
- Extend
NotificationChannelBase<T>for implementation
Notification Builders
Convert metadata into channel-specific notifications:
- Input:
INotificationMetadata(user-defined) - Output:
INotificationinstances for specific channels - Allow customization of notification content per channel
How to Configure
Basic Setup
services.AddCuriosusNotificator();
// Add Email channel
services.AddCuriosusEMailChannel();
services.AddCuriosusEMailSender(); // Configure email provider
// Add SMS channel
services.AddCuriosusSmsChannel();
services.AddCuriosusSmsSender(); // Configure SMS provider
// Add notification builders
services.AddCuriosusNotificationBuilder<MyEmailNotificationBuilder>();
services.AddCuriosusNotificationBuilder<MySmsNotificationBuilder>();
Custom Notification Metadata
public class UserWelcomeNotification : INotificationMetadata
{
public string UserEmail { get; set; }
public string UserName { get; set; }
public string PhoneNumber { get; set; }
}
Custom Notification Builders
public class UserWelcomeEmailBuilder : EmailNotificationBuilderBase<UserWelcomeNotification>
{
protected override async Task<IReadOnlyList<EmailNotification>> BuildNotificationsAsync(
UserWelcomeNotification metadata,
CancellationToken cancellationToken = default)
{
var notification = new EmailNotification(
metadata.UserEmail,
"Welcome to our platform!",
$"Hello {metadata.UserName}, welcome to our platform!",
isBodyHtml: true
);
return new[] { notification };
}
}
Reliability Features
Queued Processing
- Each channel processes notifications sequentially using
BlockingCollection<T> - Prevents overwhelming external services
- Provides backpressure handling
Error Handling
- Channels catch exceptions and convert to
NotificationException - Standardized error codes:
Auth,Communication,RateLimit,NoMoney,DeliveryError - Failed notifications bubble up to caller with detailed error information
Graceful Shutdown
- Channels implement
IHostedServicefor proper lifecycle management - Pending notifications are processed or cancelled during shutdown
TaskCompletionSourceensures calling code receives proper completion/cancellation
Post-Processing
- Email:
IEMailNotificationPostProcessor - SMS:
ISmsNotificationPostProcessor - Execute after successful/failed sending
- Common uses: logging, analytics, retry logic, user feedback
Processing Results
Synchronous Processing
try
{
await notificator.NotifyAsync(new UserWelcomeNotification
{
UserEmail = "user@example.com",
UserName = "John Doe",
PhoneNumber = "+1234567890"
});
// All channels completed successfully
}
catch (NotificationException ex)
{
// Handle notification-specific errors
switch (ex.ErrorCode)
{
case NotificationErrorCode.Auth:
// Handle authentication errors
break;
case NotificationErrorCode.RateLimit:
// Handle rate limiting
break;
// ... other error types
}
}
Fire-and-Forget Processing
// Does not wait for completion, errors are logged
notificator.NotifyAndForgot(new UserWelcomeNotification
{
UserEmail = "user@example.com",
UserName = "John Doe"
});
Result Handling
- Successful notifications complete without exceptions
- Failed notifications throw
NotificationExceptionwith specific error codes - Post-processors receive both notification and provider response for analysis
- Logging is integrated throughout the pipeline for monitoring and debugging