From 47534ce67121d56bd27950cdff0e66e2eb411cdb Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Mon, 4 May 2026 21:14:22 +0200 Subject: [PATCH] Added email support --- .../LiteCharms.Features.csproj | 13 ++- .../Handlers/SendEmailCommandHandler.cs | 60 ++++++++++++++ .../Utilities/Commands/SendEmailCommand.cs | 79 +++++++++++++++++++ .../Configuraton/Email/Account.cs | 8 ++ .../Configuraton/Email/SmtpSettings.cs | 12 +++ LiteCharms.Models/LiteCharms.Models.csproj | 2 +- 6 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 LiteCharms.Features/Utilities/Commands/Handlers/SendEmailCommandHandler.cs create mode 100644 LiteCharms.Features/Utilities/Commands/SendEmailCommand.cs create mode 100644 LiteCharms.Models/Configuraton/Email/Account.cs create mode 100644 LiteCharms.Models/Configuraton/Email/SmtpSettings.cs diff --git a/LiteCharms.Features/LiteCharms.Features.csproj b/LiteCharms.Features/LiteCharms.Features.csproj index 7b79a5c..6852b8a 100644 --- a/LiteCharms.Features/LiteCharms.Features.csproj +++ b/LiteCharms.Features/LiteCharms.Features.csproj @@ -11,7 +11,7 @@ LiteCharms.Features - 1.0.9 + 1.0.11 Khwezi Mngoma Lite Charms (PTY) Ltd Feature components for Lite Charms applications. @@ -27,6 +27,16 @@ + + + + + + + + + + @@ -43,6 +53,7 @@ + diff --git a/LiteCharms.Features/Utilities/Commands/Handlers/SendEmailCommandHandler.cs b/LiteCharms.Features/Utilities/Commands/Handlers/SendEmailCommandHandler.cs new file mode 100644 index 0000000..0300585 --- /dev/null +++ b/LiteCharms.Features/Utilities/Commands/Handlers/SendEmailCommandHandler.cs @@ -0,0 +1,60 @@ +using LiteCharms.Models.Configuraton.Email; + +namespace LiteCharms.Features.Utilities.Commands.Handlers; + +public class SendEmailCommandHandler(IOptions smtpOptions) : IRequestHandler +{ + public async ValueTask Handle(SendEmailCommand request, CancellationToken cancellationToken) + { + try + { + var settings = smtpOptions.Value; + + if(settings == null) + return Result.Fail(new Error("SMTP settings are not configured.")); + + if(settings.Credentials == null) + return Result.Fail(new Error("SMTP credentials are not configured.")); + + if(string.IsNullOrWhiteSpace(settings.Credentials.Username) || string.IsNullOrWhiteSpace(settings.Credentials.Password)) + return Result.Fail(new Error("SMTP credentials are incomplete.")); + + if(string.IsNullOrWhiteSpace(settings.Host) || settings.Port == 0) + return Result.Fail(new Error("SMTP host and port must be configured.")); + + var message = new MimeMessage(); + message.From.Add(new MailboxAddress(request.SenderName, request.From!)); + message.To.Add(new MailboxAddress(request.RecipientName, request.To!)); + message.Subject = request.Subject!; + + var bodyBuilder = new BodyBuilder(); + + if(request.Attachment?.Length > 0 && !string.IsNullOrEmpty(request.AttachmentFileName)) + bodyBuilder.Attachments.Add(request.AttachmentFileName!, request.Attachment!, cancellationToken); + + if (!request.IsHtml) bodyBuilder.TextBody = request.Message; + if (request.IsHtml) bodyBuilder.HtmlBody = request.Message; + + message.Body = bodyBuilder.ToMessageBody(); + + using var client = new SmtpClient(); + + await client.ConnectAsync(settings.Host!, settings.Port, settings.UseSsl, cancellationToken); + await client.AuthenticateAsync(settings.Credentials!.Username!, settings.Credentials.Password!, cancellationToken); + + var response = await client.SendAsync(message, cancellationToken); + + bool emailSent = response.Contains("OK", StringComparison.InvariantCultureIgnoreCase); + + await client.DisconnectAsync(true, cancellationToken); + + return emailSent + ? Result.Ok() + : Result.Fail(new Error("Failed to send email. SMTP response: " + response)); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/Utilities/Commands/SendEmailCommand.cs b/LiteCharms.Features/Utilities/Commands/SendEmailCommand.cs new file mode 100644 index 0000000..64d53e5 --- /dev/null +++ b/LiteCharms.Features/Utilities/Commands/SendEmailCommand.cs @@ -0,0 +1,79 @@ +namespace LiteCharms.Features.Utilities.Commands; + +public class SendEmailCommand : IRequest +{ + public string? From { get; set; } + + public string? SenderName { get; set; } + + public string? To { get; set; } + + public string? RecipientName { get; set; } + + public string? Subject { get; set; } + + public string? Message { get; set; } + + public bool IsHtml { get; set; } + + public Stream? Attachment { get; set; } + + public string? AttachmentFileName { get; set; } + + private SendEmailCommand(string from, string senderName, string to, string recipientName, string subject, string message, bool isHtml = false, Stream? attachment = null, string? attachmentFileName = null) + { + From = from; + To = to; + Subject = subject; + Message = message; + IsHtml = isHtml; + Attachment = attachment; + AttachmentFileName = attachmentFileName; + SenderName = senderName; + RecipientName = recipientName; + } + + public static SendEmailCommand Create(string from, string senderName, string to, string recipientName, string subject, string message, bool isHtml = false, Stream? attachment = null, string? attachmentFileName = null) + { + if (string.IsNullOrWhiteSpace(from)) + throw new ArgumentException("From address cannot be null or empty.", nameof(from)); + + if (string.IsNullOrWhiteSpace(senderName)) + throw new ArgumentException("Sender name cannot be null or empty.", nameof(senderName)); + + if (!string.IsNullOrWhiteSpace(senderName) && senderName.Length > 255) + throw new ArgumentException("Sender name cannot exceed 255 characters.", nameof(senderName)); + + if (string.IsNullOrWhiteSpace(to)) + throw new ArgumentException("To address cannot be null or empty.", nameof(to)); + + if (string.IsNullOrWhiteSpace(recipientName)) + throw new ArgumentException("Recipient name cannot be null or empty.", nameof(recipientName)); + + if (!string.IsNullOrWhiteSpace(recipientName) && recipientName.Length > 255) + throw new ArgumentException("Recipient name cannot exceed 255 characters.", nameof(recipientName)); + + if (string.IsNullOrWhiteSpace(subject)) + throw new ArgumentException("Subject cannot be null or empty.", nameof(subject)); + + if (subject.Length > 2048) + throw new ArgumentException("Subject cannot exceed 2048 characters.", nameof(subject)); + + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentException("Message cannot be null or empty.", nameof(message)); + + if (message.Length > 10485760) + throw new ArgumentException("Message cannot exceed 10 MB.", nameof(message)); + + if (attachment != null && string.IsNullOrWhiteSpace(attachmentFileName)) + throw new ArgumentException("Attachment file name must be provided when an attachment is included.", nameof(attachmentFileName)); + + if (attachment is not null && attachment.Length > 10485760) + throw new ArgumentException("Attachment cannot exceed 10 MB.", nameof(attachment)); + + if (!string.IsNullOrWhiteSpace(attachmentFileName) && attachmentFileName.Length > 255) + throw new ArgumentException("Attachment file name cannot exceed 255 characters.", nameof(attachmentFileName)); + + return new(from, senderName, to, recipientName, subject, message, isHtml, attachment, attachmentFileName); + } +} diff --git a/LiteCharms.Models/Configuraton/Email/Account.cs b/LiteCharms.Models/Configuraton/Email/Account.cs new file mode 100644 index 0000000..96424ce --- /dev/null +++ b/LiteCharms.Models/Configuraton/Email/Account.cs @@ -0,0 +1,8 @@ +namespace LiteCharms.Models.Configuraton.Email; + +public class Account +{ + public string? Username { get; set; } + + public string? Password { get; set; } +} diff --git a/LiteCharms.Models/Configuraton/Email/SmtpSettings.cs b/LiteCharms.Models/Configuraton/Email/SmtpSettings.cs new file mode 100644 index 0000000..c44fbbe --- /dev/null +++ b/LiteCharms.Models/Configuraton/Email/SmtpSettings.cs @@ -0,0 +1,12 @@ +namespace LiteCharms.Models.Configuraton.Email; + +public class SmtpSettings +{ + public Account? Credentials { get; set; } + + public int Port { get; set; } + + public string? Host { get; set; } + + public bool UseSsl { get; set; } +} diff --git a/LiteCharms.Models/LiteCharms.Models.csproj b/LiteCharms.Models/LiteCharms.Models.csproj index 87bdef2..f890e79 100644 --- a/LiteCharms.Models/LiteCharms.Models.csproj +++ b/LiteCharms.Models/LiteCharms.Models.csproj @@ -24,7 +24,7 @@ - +