Module Messaging
Module d'envoi multicanal - email SMTP, SMS AWS SNS, notifications push et templates MJML
Module Messaging
Le module Messaging fournit une infrastructure d'envoi de messages multicanal (email, SMS, push) avec persistance du suivi d'envoi, templates MJML pour les emails et nettoyage automatique des anciens messages.
Architecture du module
Contracts (interface publique)
Messages
Les messages sont definis dans Messaging.Contracts.Messages/ :
// Email
public sealed record EmailMessage(
string To,
string Subject,
string TemplateName,
IReadOnlyDictionary<string, string> Variables,
string? ReplyTo = null) : IMessage;
// SMS
public sealed record SmsMessage(
string PhoneNumber,
string TemplateName,
IReadOnlyDictionary<string, string> Variables) : IMessage;
// Push
public sealed record PushMessage(
Guid UserId,
string Title,
string Body,
IReadOnlyDictionary<string, string>? Data = null) : IMessage;
Commandes
Les commandes sont des wrappers autour de ICommand de BuildingBlocks via l'interface IMessagingCommand :
public sealed record SendEmailCommand(EmailMessage Message) : IMessagingCommand;
public sealed record SendSmsCommand(SmsMessage Message) : IMessagingCommand;
public sealed record SendPushCommand(PushMessage Message) : IMessagingCommand;
Evenements
MessageSentEvent: emis apres un envoi reussiMessageFailedEvent: emis apres un echec d'envoi
Entites
SentMessage
Enregistrement de chaque message envoye pour le suivi et l'audit :
| Propriete | Type | Description |
|---|---|---|
Id | Guid | Identifiant unique |
Channel | string | Canal d'envoi (email, sms, push) |
Recipient | string | Destinataire (email, telephone, userId) |
TemplateName | string | Nom du template utilise |
Subject | string | Sujet du message |
Status | MessageStatus | Statut courant |
ErrorMessage | string? | Message d'erreur en cas d'echec |
Attempts | int | Nombre de tentatives d'envoi |
CreatedAt | DateTime | Date de creation |
SentAt | DateTime? | Date d'envoi effectif |
CorrelationId | string? | Identifiant de correlation |
MessageStatus (Enum)
internal enum MessageStatus
{
Pending, // En attente d'envoi
Sent, // Envoye avec succes
Failed, // Echec d'envoi
Bounced // Rebond (email non delivre)
}
MessagingPersistenceBehavior
Ce behavior MediatR est specifique au module Messaging. Il s'active pour toute commande implementant IMessagingCommand :
- Avant l'envoi : cree un
SentMessageavec le statutPendinget le persiste - Pendant l'envoi : execute le handler reel (SMTP, SNS, push)
- Apres l'envoi : met a jour le statut en
Sentavec la date d'envoi - En cas d'echec : met a jour le statut en
Failedavec le message d'erreur - Toujours : persiste le resultat final (meme si la persistance echoue, un warning est logue)
Le pattern garantit que chaque tentative d'envoi est tracee, qu'elle reussisse ou echoue.
Services internes
IEmailSender / SmtpEmailSender
Envoi d'emails via SMTP avec MailKit :
internal interface IEmailSender
{
Task SendAsync(string to, string subject, string htmlBody, string? replyTo, CancellationToken cancellationToken);
}
Configuration via SmtpOptions :
Host: serveur SMTPPort: port SMTP (587 par defaut)Username/Password: authentificationFromEmail/FromName: expediteurUseSsl: activer TLS/SSL
Un SmtpHealthCheck est enregistre pour verifier la connectivite SMTP.
ISmsSender / SnsSmsSender
Envoi de SMS via AWS Simple Notification Service :
internal interface ISmsSender
{
Task SendAsync(string phoneNumber, string body, CancellationToken cancellationToken);
}
Configuration via SnsOptions :
Region: region AWS (ex:eu-north-1)AccessKeyId/SecretAccessKey: credentials AWS (optionnel si IAM role)
Si aucune region SNS n'est configuree, un SmsServiceStub est enregistre a la place.
IPushSender / PushServiceStub
Interface pour les notifications push :
internal interface IPushSender
{
Task SendAsync(Guid userId, string title, string body, IReadOnlyDictionary<string, string>? data, CancellationToken cancellationToken);
}
Actuellement implemente par un stub (PushServiceStub). L'implementation reelle est a venir.
ITemplateRenderer / MjmlTemplateRenderer
Rendu de templates email avec MJML :
internal interface ITemplateRenderer
{
string Render(string templateName, IReadOnlyDictionary<string, string> variables);
}
L'implementation MjmlTemplateRenderer utilise la librairie Mjml.Net pour convertir les templates MJML en HTML responsive.
Handlers
SendEmailCommandHandler
- Rend le template MJML en HTML via
ITemplateRenderer - Envoie l'email via
IEmailSender
SendSmsCommandHandler
- Rend le template en texte via
ITemplateRenderer - Envoie le SMS via
ISmsSender
SendPushCommandHandler
- Envoie la notification push via
IPushSender
Job de nettoyage
MessageCleanupJob est un job Hangfire qui supprime les messages de plus de 90 jours :
internal sealed class MessageCleanupJob(MessagingContext messagingContext, ILogger<MessageCleanupJob> logger)
{
internal const int RetentionDays = 90;
public async Task ExecuteAsync(CancellationToken cancellationToken)
{
DateTime cutoff = DateTime.UtcNow.AddDays(-RetentionDays);
int deletedCount = await messagingContext.SentMessages
.Where(message => message.CreatedAt < cutoff)
.ExecuteDeleteAsync(cancellationToken);
}
}
DbContext
internal sealed class MessagingContext : AppDbContextBase
{
public DbSet<SentMessage> SentMessages => Set<SentMessage>();
protected override void OnModelCreating(ModelBuilder builder)
{
builder.HasDefaultSchema("messaging");
builder.ApplyConfigurationsFromAssembly(typeof(MessagingContext).Assembly);
builder.ToSnakeCaseTables();
}
}
Schema : messaging, convention snake_case pour les tables et colonnes.
Enregistrement du module
extension(WebApplicationBuilder builder)
{
public WebApplicationBuilder AddInfrastructure()
{
builder.AddCustomDbContext<MessagingContext>("Messaging");
builder.Services.AddDatabaseMigration<MessagingContext>(2);
builder.Services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssemblies(typeof(MessagingRoot).Assembly));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>),
typeof(MessagingPersistenceBehavior<,>));
// SmtpOptions, IEmailSender, ISmsSender, IPushSender, ITemplateRenderer
// SmtpHealthCheck
}
}
Developpement local
En developpement avec Aspire, Mailpit est utilise comme serveur SMTP local :
- SMTP sur le port
1025 - Interface web de visualisation sur le port
8025 - Configuration injectee automatiquement via les variables d'environnement Aspire