mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Move Infrastructure.Email into Features.Emails (#288)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using Features.Emails.Templates.Rendering;
|
||||
using Infrastructure.Configuration;
|
||||
using Infrastructure.Email;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Features.Emails.Services;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Features.Emails.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for sending emails.
|
||||
/// </summary>
|
||||
public interface IEmailProvider
|
||||
{
|
||||
/// <summary>Sends an email to a single recipient.</summary>
|
||||
/// <param name="to">The recipient's email address.</param>
|
||||
/// <param name="subject">The email subject line.</param>
|
||||
/// <param name="body">The email body content.</param>
|
||||
/// <param name="isHtml">Whether <paramref name="body"/> is HTML; when <see langword="false"/> it is sent as plain text.</param>
|
||||
Task SendAsync(string to, string subject, string body, bool isHtml = true);
|
||||
|
||||
/// <summary>Sends an email to multiple recipients.</summary>
|
||||
/// <param name="to">The recipients' email addresses.</param>
|
||||
/// <param name="subject">The email subject line.</param>
|
||||
/// <param name="body">The email body content.</param>
|
||||
/// <param name="isHtml">Whether <paramref name="body"/> is HTML; when <see langword="false"/> it is sent as plain text.</param>
|
||||
Task SendAsync(IEnumerable<string> to, string subject, string body, bool isHtml = true);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Infrastructure.Configuration;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MimeKit;
|
||||
|
||||
namespace Features.Emails.Services;
|
||||
|
||||
/// <summary>
|
||||
/// SMTP email service implementation using MailKit.
|
||||
/// Configured via application configuration (backed by environment variables).
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpEmailProvider" /> class, reading SMTP configuration
|
||||
/// (<c>SMTP_HOST</c>, <c>SMTP_PORT</c>, <c>SMTP_USERNAME</c>, <c>SMTP_PASSWORD</c>, <c>SMTP_USE_SSL</c>,
|
||||
/// <c>SMTP_FROM_EMAIL</c>, <c>SMTP_FROM_NAME</c>) from <paramref name="configuration" />.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when <c>SMTP_HOST</c>, <c>SMTP_PORT</c>, or <c>SMTP_FROM_EMAIL</c> is not set,
|
||||
/// or when <c>SMTP_PORT</c> is not a valid integer.
|
||||
/// </exception>
|
||||
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";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SendAsync(string to, string subject, string body, bool isHtml = true)
|
||||
{
|
||||
await SendAsync([to], subject, body, isHtml);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// Connects using StartTls when SSL is enabled (or no encryption otherwise), authenticates
|
||||
/// if credentials were configured, sends the message, and disconnects.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when connecting, authenticating, or sending via SMTP fails, wrapping the original exception.
|
||||
/// </exception>
|
||||
public async Task SendAsync(
|
||||
IEnumerable<string> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user