using Email.Data.Services; using EmailApi.Models.Requests; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Options; using MimeKit; using EmailApi.Models.Settings; using Models.Settings; namespace Api.Services; /// /// Wraps an HTML body fragment in the branded HTML shell and sends the resulting email via SMTP using MailKit. /// Attaches files from the shared file-storage volume when an attachment path is provided. /// public sealed class SmtpEmailDispatcher { private readonly SmtpSettings _smtp; private readonly FileStorageSettings _fileStorage; private readonly IEmailTemplateService _templates; private readonly ILogger _log; private readonly string _environmentName; public SmtpEmailDispatcher( IOptions smtp, IOptions fileStorage, IEmailTemplateService templates, ILogger log) { _smtp = smtp.Value; _fileStorage = fileStorage.Value; _templates = templates; _log = log; _environmentName = Environment.GetEnvironmentVariable("APP_ENVIRONMENT_NAME") ?? "Development"; } /// /// Builds a from , wraps the body in the HTML shell, /// optionally attaches a file, and sends via the configured SMTP server. /// Logs a warning and returns without throwing when the SMTP host is not configured. /// /// Email payload containing recipients, subject, HTML body, and optional attachment path. /// Cancellation token. public async Task SendAsync(SendEmailRequest req, CancellationToken ct) { if (string.IsNullOrWhiteSpace(_smtp.Host)) { _log.LogWarning("SMTP host not configured — email skipped (to: {To})", string.Join(", ", req.To)); return; } var msg = new MimeMessage(); msg.From.Add(MailboxAddress.Parse(_smtp.Username)); foreach (var to in req.To) msg.To.Add(MailboxAddress.Parse(to)); if (!string.IsNullOrWhiteSpace(req.ReplyTo)) msg.ReplyTo.Add(MailboxAddress.Parse(req.ReplyTo)); msg.Subject = $"[{_environmentName}] {req.Subject}".Trim(); var shellStart = _templates.Get("email.html-shell.start", "*"); var shellEnd = _templates.Get("email.html-shell.end", "*"); var builder = new BodyBuilder { HtmlBody = shellStart + req.HtmlBody + shellEnd }; if (!string.IsNullOrWhiteSpace(req.AttachmentPath)) { var fullPath = Path.Combine(_fileStorage.Path, req.AttachmentPath); if (File.Exists(fullPath)) { builder.Attachments.Add(fullPath); _log.LogDebug("Attachment added: {Path}", fullPath); } else { _log.LogWarning("Attachment not found, skipping: {Path}", fullPath); } } msg.Body = builder.ToMessageBody(); _log.LogInformation("Sending email to {Recipients} subject {Subject}", string.Join(", ", req.To), req.Subject); using var client = new SmtpClient(); var tls = _smtp.UseStartTls ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto; await client.ConnectAsync(_smtp.Host, _smtp.Port, tls, ct); if (!string.IsNullOrWhiteSpace(_smtp.Username)) await client.AuthenticateAsync(_smtp.Username, _smtp.Password, ct); await client.SendAsync(msg, ct); await client.DisconnectAsync(true, ct); _log.LogInformation("Email sent successfully to {Recipients}", string.Join(", ", req.To)); } }