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 @@
-
+