Replaced the `EmailAccountDto` class with the `EmailAccount` class across the codebase to consolidate the `EmailAccount` entity into the domain layer. Updated namespaces, method signatures, property types, and test cases to reflect this change. Moved `EmailAccount` from `DigitalData.MessagingService.Application.Common.Dto` to `DigitalData.MessagingService.Domain.Entities`. Updated XML documentation and removed redundant project file entries. Adjusted namespaces for related queries, validators, and commands to align with the new structure. These changes improve separation of concerns and align with domain-driven design principles.
140 lines
5.0 KiB
C#
140 lines
5.0 KiB
C#
using System.Text;
|
|
using DigitalData.MessagingService.Application.Common.Interfaces;
|
|
using DigitalData.MessagingService.Domain.Exceptions;
|
|
using Limilabs.Client.SMTP;
|
|
using Limilabs.Mail;
|
|
using Limilabs.Mail.Headers;
|
|
using DigitalData.MessagingService.Infrastructure.Services.Extensions;
|
|
using DigitalData.MessagingService.Application.Common.Dto;
|
|
using DigitalData.MessagingService.Domain.Entities;
|
|
|
|
namespace DigitalData.MessagingService.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<EmailAccountsOptions> from appsettings.json.
|
|
/// Uses the first account in the list whose <see cref="EmailAccount.Name"/> equals <c>"default"</c>,
|
|
/// or falls back to the first account if none is named "default".
|
|
/// </summary>
|
|
public class LimilabsEmailService() : IEmailService
|
|
{
|
|
// Register encoding provider for Limilabs (requires windows-1252 and other code pages)
|
|
static LimilabsEmailService()
|
|
{
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
}
|
|
|
|
public async Task SendEmailAsync(EmailContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
using var smtp = new Smtp();
|
|
ISendMessageResult? result = null;
|
|
try
|
|
{
|
|
await ConnectAndAuthenticateSmtpAsync(smtp, context.Sender);
|
|
|
|
var builder = new MailBuilder();
|
|
builder.From.Add(new MailBox(context.Sender.Username));
|
|
|
|
foreach (var recipient in context.Recipients)
|
|
builder.To.Add(new MailBox(recipient));
|
|
|
|
builder.Subject = context.Subject;
|
|
|
|
if (context.IsHtml)
|
|
builder.Html = context.Body;
|
|
else
|
|
builder.Text = context.Body;
|
|
|
|
AddAttachments(builder, context.Attachments);
|
|
|
|
var mail = builder.Create();
|
|
|
|
result = await smtp.SendMessageAsync(mail, cancellationToken);
|
|
|
|
if (result.Status != SendMessageStatus.Success)
|
|
{
|
|
throw new InvalidOperationException($"Failed to send email. Status: {result.Status}. {ErrorMessageBuilder(result)}");
|
|
}
|
|
|
|
await smtp.CloseAsync(cancellationToken);
|
|
}
|
|
catch (Limilabs.Client.ServerException ex)
|
|
{
|
|
await smtp.CloseSafelyAsync();
|
|
throw new AuthenticationFailedException($"SMTP authentication failed. Check credentials or OAuth2 configuration. {ErrorMessageBuilder(result)}", ex);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await smtp.CloseSafelyAsync();
|
|
throw new InvalidOperationException($"Failed to send email via SMTP server. {ErrorMessageBuilder(result)}", ex);
|
|
}
|
|
}
|
|
|
|
private static async Task ConnectAndAuthenticateSmtpAsync(Smtp smtp, EmailAccount smtpAccount)
|
|
{
|
|
if (smtpAccount.SmtpUseSsl)
|
|
{
|
|
await smtp.ConnectSSLAsync(smtpAccount.SmtpServer, smtpAccount.SmtpPort);
|
|
}
|
|
else
|
|
{
|
|
await smtp.ConnectAsync(smtpAccount.SmtpServer, smtpAccount.SmtpPort);
|
|
}
|
|
|
|
if (smtpAccount.UseOAuth2)
|
|
{
|
|
throw new NotSupportedException("OAuth2 is not configured for this SMTP account. UseOAuth2 must be false.");
|
|
}
|
|
else
|
|
{
|
|
await smtp.LoginAsync(smtpAccount.Username, smtpAccount.Password);
|
|
}
|
|
}
|
|
|
|
private static string ErrorMessageBuilder(ISendMessageResult? result = null)
|
|
{
|
|
if(result is null || result.GeneralErrors.Count == 0)
|
|
return string.Empty;
|
|
else if(result.GeneralErrors.Count == 1)
|
|
return $"Error: {result.GeneralErrors.FirstOrDefault()}";
|
|
|
|
var message = new StringBuilder("Errors:\n");
|
|
|
|
foreach (var error in result.GeneralErrors)
|
|
{
|
|
message.AppendLine($" • {error}");
|
|
}
|
|
|
|
return message.ToString();
|
|
}
|
|
|
|
private static void AddAttachments(MailBuilder builder, IEnumerable<EmailAttachmentDto> attachments)
|
|
{
|
|
foreach (var attachment in attachments)
|
|
{
|
|
if (attachment.IsInline)
|
|
{
|
|
var visual = builder.AddVisual(attachment.Content);
|
|
visual.FileName = attachment.FileName;
|
|
visual.ContentId = string.IsNullOrWhiteSpace(attachment.ContentId)
|
|
? attachment.FileName
|
|
: attachment.ContentId;
|
|
|
|
if (!string.IsNullOrWhiteSpace(attachment.ContentType))
|
|
visual.ContentType = ContentType.Parse(attachment.ContentType);
|
|
}
|
|
else
|
|
{
|
|
var part = builder.AddAttachment(attachment.Content);
|
|
part.FileName = attachment.FileName;
|
|
|
|
if (!string.IsNullOrWhiteSpace(attachment.ContentType))
|
|
part.ContentType = ContentType.Parse(attachment.ContentType);
|
|
|
|
if (!string.IsNullOrWhiteSpace(attachment.ContentId))
|
|
part.ContentId = attachment.ContentId;
|
|
}
|
|
}
|
|
}
|
|
} |