using Configuration; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Configuration; using MimeKit; namespace Features.Emails.Services; /// /// SMTP email service implementation using MailKit. /// Configured via application configuration (backed by environment variables). /// public class SmtpEmailProvider : IEmailProvider { private readonly string _fromEmail; private readonly string _fromName; private readonly string _host; private readonly string? _password; private readonly int _port; private readonly string? _username; private readonly bool _useSsl; /// /// Initializes a new instance of the class, reading SMTP configuration /// (SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_USE_SSL, /// SMTP_FROM_EMAIL, SMTP_FROM_NAME) from . /// /// /// Thrown when SMTP_HOST, SMTP_PORT, or SMTP_FROM_EMAIL is not set, /// or when SMTP_PORT is not a valid integer. /// public SmtpEmailProvider(IConfiguration configuration) { _host = ConfigurationHelpers.GetKeyOrThrow(configuration, ConfigurationKeys.SmtpHost); _port = ConfigurationHelpers.GetIntKeyOrThrow(configuration, ConfigurationKeys.SmtpPort); _username = configuration[ConfigurationKeys.SmtpUsername]; _password = configuration[ConfigurationKeys.SmtpPassword]; string useSslString = configuration[ConfigurationKeys.SmtpUseSsl] ?? "true"; _useSsl = bool.Parse(useSslString); _fromEmail = ConfigurationHelpers.GetKeyOrThrow( configuration, ConfigurationKeys.SmtpFromEmail ); _fromName = configuration[ConfigurationKeys.SmtpFromName] ?? "The Biergarten"; } /// public async Task SendAsync(string to, string subject, string body, bool isHtml = true) { await SendAsync([to], subject, body, isHtml); } /// /// /// Connects using StartTls when SSL is enabled (or no encryption otherwise), authenticates /// if credentials were configured, sends the message, and disconnects. /// /// /// Thrown when connecting, authenticating, or sending via SMTP fails, wrapping the original exception. /// public async Task SendAsync( IEnumerable to, string subject, string body, bool isHtml = true ) { MimeMessage message = new(); message.From.Add(new MailboxAddress(_fromName, _fromEmail)); foreach (string recipient in to) message.To.Add(MailboxAddress.Parse(recipient)); message.Subject = subject; BodyBuilder bodyBuilder = new(); if (isHtml) bodyBuilder.HtmlBody = body; else bodyBuilder.TextBody = body; message.Body = bodyBuilder.ToMessageBody(); using SmtpClient client = new(); try { SecureSocketOptions secureSocketOptions = _useSsl ? SecureSocketOptions.StartTls : SecureSocketOptions.None; await client.ConnectAsync(_host, _port, secureSocketOptions); if (!string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password)) await client.AuthenticateAsync(_username, _password); await client.SendAsync(message); await client.DisconnectAsync(true); } catch (Exception ex) { throw new InvalidOperationException($"Failed to send email: {ex.Message}", ex); } } }