Email Notifications
The Email notification channel (EmailNotificationChannel) provides reliable email delivery using the Curiosus.EMail infrastructure with queued processing, post-processing capabilities, and comprehensive error handling.
How it works
Architecture
The email notification system consists of:
- EmailNotificationChannel - Background service that processes email notifications from a queue
- EmailNotification - Represents an email to be sent with subject, body, recipient, and optional parameters
- EmailNotificationBuilderBase - Abstract base class for building email notifications from metadata
- IEMailNotificationPostProcessor - Interface for post-processing after email sending
Processing Flow
sequenceDiagram
participant Client
participant Notificator
participant Builder
participant Channel
participant Provider
participant PostProcessor
Client->>Notificator: NotifyAsync(metadata)
Notificator->>Builder: BuildNotificationsAsync(metadata)
Builder->>Notificator: EmailNotification[]
Notificator->>Channel: SendNotificationAsync(notification)
Channel->>Provider: SendAsync(email, subject, body)
Provider->>Channel: Response
Channel->>PostProcessor: ProcessAsync(notification, result)
Channel->>Notificator: Task completion
Notificator->>Client: Result
Queue Processing
- Each
EmailNotificationChannelprocesses notifications sequentially - Uses
BlockingCollection<NotificationQueueItem<EmailNotification>>for thread-safe queuing - Provides
TaskCompletionSourcefor async completion tracking - Handles cancellation and shutdown gracefully
Email Structure
public class EmailNotification : INotification
{
public string ChannelType => "curiosus.notifications.email";
public string Email { get; } // Recipient email address
public string Subject { get; } // Email subject
public string Body { get; } // Email body content
public bool IsBodyHtml { get; } // Whether body is HTML
public IEMailExtraParams? ExtraParams { get; } // Provider-specific parameters
}
Available providers
The email notification channel uses the IEMailSender interface from Curiosus.EMail package, which supports multiple providers:
Mailgun
services.AddCuriosusMailgunSender(options =>
{
options.ApiKey = "your-api-key";
options.Domain = "your-domain.com";
options.FromEmail = "noreply@your-domain.com";
});
SendGrid
services.AddCuriosusSendGridSender(options =>
{
options.ApiKey = "your-api-key";
options.FromEmail = "noreply@your-domain.com";
options.FromName = "Your App Name";
});
SMTP
services.AddCuriosusSmtpSender(options =>
{
options.Host = "smtp.your-provider.com";
options.Port = 587;
options.Username = "your-username";
options.Password = "your-password";
options.EnableSsl = true;
});
In-Memory (Testing)
services.AddCuriosusInMemoryEMailSender();
How to add custom provider?
Step 1: Implement IEMailSender
public class CustomEmailSender : IEMailSender
{
public async Task<Response> SendAsync(
string email,
string subject,
string body,
bool isBodyHtml = false,
CancellationToken cancellationToken = default)
{
try
{
// Your custom email sending logic here
await SendEmailThroughCustomProvider(email, subject, body, isBodyHtml);
return Response.Successful();
}
catch (AuthenticationException ex)
{
return Response.Failed(new Error((int)EmailError.Auth, ex.Message));
}
catch (RateLimitException ex)
{
return Response.Failed(new Error((int)EmailError.RateLimit, ex.Message));
}
catch (Exception ex)
{
return Response.Failed(new Error((int)EmailError.Unknown, ex.Message));
}
}
public async Task<Response> SendAsync(
string email,
string subject,
string body,
bool isBodyHtml,
IEMailExtraParams extraParams,
CancellationToken cancellationToken = default)
{
// Handle extra parameters specific to your provider
var customParams = extraParams as CustomEmailExtraParams;
// Implementation with extra parameters
return await SendAsync(email, subject, body, isBodyHtml, cancellationToken);
}
}
Step 2: Create Custom Extra Parameters (Optional)
public class CustomEmailExtraParams : IEMailExtraParams
{
public string Priority { get; set; } = "normal";
public string Category { get; set; }
public Dictionary<string, string> Tags { get; set; } = new();
}
Step 3: Register in IoC
public static class IoCExtensions
{
public static IServiceCollection AddCustomEmailSender(
this IServiceCollection services,
Action<CustomEmailOptions> configure)
{
services.Configure(configure);
services.AddSingleton<IEMailSender, CustomEmailSender>();
return services;
}
}
Step 4: Configure and Use
// In Startup.cs or Program.cs
services.AddCustomEmailSender(options =>
{
options.ApiEndpoint = "https://api.customprovider.com/email";
options.ApiKey = "your-api-key";
});
services.AddCuriosusEMailChannel();
Step 5: Create Custom Builder (Optional)
public class CustomEmailNotificationBuilder : EmailNotificationBuilderBase<YourNotificationMetadata>
{
protected override async Task<IReadOnlyList<EmailNotification>> BuildNotificationsAsync(
YourNotificationMetadata metadata,
CancellationToken cancellationToken = default)
{
var extraParams = new CustomEmailExtraParams
{
Priority = "high",
Category = "user-notifications",
Tags = new Dictionary<string, string> { { "user-id", metadata.UserId } }
};
var notification = new EmailNotification(
metadata.RecipientEmail,
await GenerateSubject(metadata),
await GenerateBody(metadata),
isBodyHtml: true,
extraParams: extraParams
);
return new[] { notification };
}
}
Error Handling
The email channel maps provider errors to standardized notification error codes:
var notificationCode = errorCode switch
{
EmailError.Auth => NotificationErrorCode.Auth,
EmailError.Communication => NotificationErrorCode.Communication,
EmailError.RateLimit => NotificationErrorCode.RateLimit,
EmailError.NoMoney => NotificationErrorCode.NoMoney,
EmailError.IncorrectRequestData => NotificationErrorCode.IncorrectRequestData,
_ => NotificationErrorCode.Unknown,
};
Post-Processing
Implement IEMailNotificationPostProcessor for actions after email sending:
public class EmailAnalyticsProcessor : IEMailNotificationPostProcessor
{
public async Task ProcessAsync(
EmailNotification notification,
Response result,
CancellationToken cancellationToken = default)
{
// Log analytics, update database, send webhooks, etc.
await analyticsService.TrackEmailSent(new EmailAnalytics
{
Recipient = notification.Email,
Subject = notification.Subject,
Success = result.IsSuccess,
ErrorCode = result.Errors?.FirstOrDefault()?.Code,
SentAt = DateTime.UtcNow
});
}
}
// Register in IoC
services.AddEMailNotificationPostProcessor<EmailAnalyticsProcessor>();