feat(infrastructure): Add Limilabs email service and RabbitMQ email queue

- Add LimilabsEmailService for SMTP operations (IEmailService implementation)
- Add RabbitMqEmailQueue for production email queue (RabbitMQ-based)
- Update InMemoryEmailQueue for improved error handling
- Update IEmailQueue interface for RabbitMQ compatibility
- Update DependencyInjection.cs:
  * Switch IEmailService to LimilabsEmailService (Singleton)
  * Switch IEmailQueue to RabbitMqEmailQueue (Singleton)
  * Change IEncryptionService to Singleton (thread-safe)
  * Remove IDmsService registration
- Add required NuGet package references to .csproj

TODO: Add Limilabs.Mail NuGet package (commercial license required)
This commit is contained in:
2026-07-22 11:48:37 +02:00
parent dbd0d35ba3
commit 3f9bfc78a8
6 changed files with 402 additions and 22 deletions

View File

@@ -0,0 +1,103 @@
using DigitalData.EmailProfiler.Application.Common.Dtos;
using DigitalData.EmailProfiler.Application.Common.Interfaces;
using DigitalData.EmailProfiler.Domain.Exceptions;
using Limilabs.Client.SMTP;
using Limilabs.Mail;
using Limilabs.Mail.Headers;
using Microsoft.Extensions.Options;
namespace DigitalData.EmailProfiler.Infrastructure.Services;
/// <summary>
/// Email service using Limilabs Mail.dll for SMTP operations (send-only).
/// Commercial-grade library with superior Exchange support.
/// SMTP configuration is injected via IOptions&lt;EmailAccountDto&gt; from appsettings.json.
/// </summary>
public class LimilabsEmailService(
IEncryptionService encryptionService,
IOptions<EmailAccountDto> smtpConfig) : IEmailService
{
private readonly EmailAccountDto _smtpAccount = smtpConfig.Value;
public async Task SendEmailAsync(string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default)
{
using var smtp = new Smtp();
try
{
await ConnectAndAuthenticateSmtpAsync(smtp);
var builder = new MailBuilder();
builder.From.Add(new MailBox(_smtpAccount.Username));
builder.To.Add(new MailBox(to));
builder.Subject = subject;
if (isHtml)
{
builder.Html = body;
}
else
{
builder.Text = body;
}
var mail = builder.Create();
var result = smtp.SendMessage(mail);
if (result.Status != SendMessageStatus.Success)
{
throw new InvalidOperationException($"Failed to send email. Status: {result.Status}");
}
smtp.Close();
await Task.CompletedTask; // For async consistency
}
catch (Limilabs.Client.ServerException ex)
{
DisconnectSafely(smtp);
throw new AuthenticationFailedException("SMTP authentication failed. Check credentials or OAuth2 configuration.", ex);
}
catch (Exception ex)
{
DisconnectSafely(smtp);
throw new InvalidOperationException("Failed to send email via SMTP server.", ex);
}
}
// --- Private Helper Methods ---
private async Task ConnectAndAuthenticateSmtpAsync(Smtp smtp)
{
if (_smtpAccount.SmtpUseSsl)
{
smtp.ConnectSSL(_smtpAccount.SmtpServer, _smtpAccount.SmtpPort);
}
else
{
smtp.Connect(_smtpAccount.SmtpServer, _smtpAccount.SmtpPort);
}
if (_smtpAccount.UseOAuth2)
{
throw new NotSupportedException("OAuth2 is not configured for this SMTP account. UseOAuth2 must be false.");
}
else
{
var password = encryptionService.Decrypt(_smtpAccount.EncryptedPassword!);
smtp.Login(_smtpAccount.Username, password);
}
await Task.CompletedTask; // For async consistency
}
private static void DisconnectSafely(Smtp smtp)
{
try
{
if (smtp.Connected)
smtp.Close();
}
catch { /* Ignore disconnect errors */ }
}
}