From 54af1c88b6d905c9787137ccbf2b1a8f17b25b43 Mon Sep 17 00:00:00 2001 From: Mia Winter Date: Sun, 18 Feb 2024 15:06:31 +0100 Subject: [PATCH] Implemented multiple email providers --- Wave/Data/EmailConfiguration.cs | 9 +++++ Wave/Data/SmtpConfiguration.cs | 3 -- Wave/Program.cs | 40 ++++++++++++++++++----- Wave/Services/IEmailService.cs | 4 +-- Wave/Services/IScopedProcessingService.cs | 5 +++ Wave/Services/LiveEmailService.cs | 15 +++++---- Wave/Services/SmtpEmailSender.cs | 21 +++++++----- 7 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 Wave/Data/EmailConfiguration.cs create mode 100644 Wave/Services/IScopedProcessingService.cs diff --git a/Wave/Data/EmailConfiguration.cs b/Wave/Data/EmailConfiguration.cs new file mode 100644 index 0000000..2c4cce3 --- /dev/null +++ b/Wave/Data/EmailConfiguration.cs @@ -0,0 +1,9 @@ +namespace Wave.Data; + +public class EmailConfiguration { + public required string SenderEmail { get; init; } + public required string SenderName { get; init; } + public required string? ServiceEmail { get; init; } + + public Dictionary Smtp { get; init; } = new(StringComparer.InvariantCultureIgnoreCase); +} \ No newline at end of file diff --git a/Wave/Data/SmtpConfiguration.cs b/Wave/Data/SmtpConfiguration.cs index c72c45d..1b8f617 100644 --- a/Wave/Data/SmtpConfiguration.cs +++ b/Wave/Data/SmtpConfiguration.cs @@ -5,8 +5,5 @@ public class SmtpConfiguration { public required int Port { get; init; } public required string Username { get; init; } public required string Password { get; init; } - public required string SenderEmail { get; init; } - public required string SenderName { get; init; } - public required string ServiceEmail { get; init; } public bool Ssl { get; init; } = true; } \ No newline at end of file diff --git a/Wave/Program.cs b/Wave/Program.cs index e64bc2a..a31946e 100644 --- a/Wave/Program.cs +++ b/Wave/Program.cs @@ -121,16 +121,40 @@ builder.Services.AddCascadingValue("TitlePrefix", sf => (sf.GetService>()?.Value.AppName ?? "Wave") + " - "); -var smtpConfig = builder.Configuration.GetSection("Email:Smtp"); -if (smtpConfig.Exists()) { - builder.Services.Configure(smtpConfig); - builder.Services.AddKeyedScoped("live"); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped, SmtpEmailSender>(); +var emailConfig = builder.Configuration.GetSection("Email").Get(); +builder.Services.Configure(builder.Configuration.GetSection("Email")); +if (emailConfig?.Smtp.Count > 0) { + if (string.IsNullOrWhiteSpace(emailConfig.SenderEmail)) { + throw new ApplicationException( + "Email providers have been configured, but no SenderEmail. " + + "Please provider the sender email address used for email distribution."); + } + + foreach (var smtp in emailConfig.Smtp) { + builder.Services.AddKeyedScoped(smtp.Key.ToLower(), (provider, key) => + ActivatorUtilities.CreateInstance(provider, + provider.GetRequiredService>().Value.Smtp[(string)key])); + } + + if (emailConfig.Smtp.Keys.Any(k => k.Equals("live", StringComparison.CurrentCultureIgnoreCase))) { + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped, SmtpEmailSender>(); + } else { + builder.Services.AddSingleton, IdentityNoOpEmailSender>(); + logMessages.Add("No 'live' email provider configured."); + } + + if (emailConfig.Smtp.Keys.Any(k => k.Equals("bulk", StringComparison.CurrentCultureIgnoreCase))) { + builder.Services.AddScoped(); + } else if (builder.Configuration.GetSection(nameof(Features)).Get()?.EmailSubscriptions is not true) { + throw new ApplicationException( + "Email subscriptions have been enabled, but no 'bulk' email provider was configured. " + + "Disable email subscriptions or provide the mail provider for bulk sending"); + } } else { builder.Services.AddSingleton, IdentityNoOpEmailSender>(); - logMessages.Add("No Email provider configured."); + logMessages.Add("No email provider configured."); } builder.Services.AddScoped(); diff --git a/Wave/Services/IEmailService.cs b/Wave/Services/IEmailService.cs index cd4aceb..62381d1 100644 --- a/Wave/Services/IEmailService.cs +++ b/Wave/Services/IEmailService.cs @@ -1,8 +1,8 @@ namespace Wave.Services; public interface IEmailService : IAsyncDisposable { - ValueTask Connect(CancellationToken cancellation); - ValueTask Disconnect(CancellationToken cancellation); + ValueTask ConnectAsync(CancellationToken cancellation); + ValueTask DisconnectAsync(CancellationToken cancellation); ValueTask SendEmailAsync(IEmail email); } \ No newline at end of file diff --git a/Wave/Services/IScopedProcessingService.cs b/Wave/Services/IScopedProcessingService.cs new file mode 100644 index 0000000..73deafb --- /dev/null +++ b/Wave/Services/IScopedProcessingService.cs @@ -0,0 +1,5 @@ +namespace Wave.Services; + +public interface IScopedProcessingService { + ValueTask DoWork(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Wave/Services/LiveEmailService.cs b/Wave/Services/LiveEmailService.cs index 581a5a9..e0b9ef9 100644 --- a/Wave/Services/LiveEmailService.cs +++ b/Wave/Services/LiveEmailService.cs @@ -6,18 +6,19 @@ namespace Wave.Services; -public class LiveEmailService(ILogger logger, IOptions configuration) : IEmailService { +public class LiveEmailService(ILogger logger, IOptions emailConfiguration, SmtpConfiguration configuration) : IEmailService { private ILogger Logger { get; } = logger; - private SmtpConfiguration Configuration { get; } = configuration.Value; + private EmailConfiguration EmailConfiguration { get; } = emailConfiguration.Value; + private SmtpConfiguration Configuration { get; } = configuration; private SmtpClient? Client { get; set; } public async ValueTask DisposeAsync() { GC.SuppressFinalize(this); - await Disconnect(CancellationToken.None); + await DisconnectAsync(CancellationToken.None); } - public async ValueTask Connect(CancellationToken cancellation) { + public async ValueTask ConnectAsync(CancellationToken cancellation) { if (Client is not null) return; try { @@ -35,7 +36,7 @@ public class LiveEmailService(ILogger logger, IOptions logger, IOptions logger, IOptions"); + message.Headers.Add(HeaderId.ListId, $""); } message.Headers.Add(id, value); } diff --git a/Wave/Services/SmtpEmailSender.cs b/Wave/Services/SmtpEmailSender.cs index 83b9efa..ab25b7e 100644 --- a/Wave/Services/SmtpEmailSender.cs +++ b/Wave/Services/SmtpEmailSender.cs @@ -3,9 +3,10 @@ namespace Wave.Services; -public class SmtpEmailSender(EmailFactory email, [FromKeyedServices("live")]IEmailService emailService) : IEmailSender, IAdvancedEmailSender, IAsyncDisposable { +public class SmtpEmailSender(EmailFactory email, [FromKeyedServices("live")]IEmailService emailService, [FromKeyedServices("bulk")]IEmailService bulkEmailService) : IEmailSender, IAdvancedEmailSender, IAsyncDisposable { private EmailFactory Email { get; } = email; private IEmailService EmailService { get; } = emailService; + private IEmailService BulkEmailService { get; } = bulkEmailService; #region IEmailSenderAsync @@ -33,33 +34,35 @@ public class SmtpEmailSender(EmailFactory email, [FromKeyedServices("live")]IEma public async Task SendEmailAsync(string email, string? name, string subject, string htmlMessage) { - await EmailService.Connect(CancellationToken.None); + await EmailService.ConnectAsync(CancellationToken.None); await EmailService.SendEmailAsync(await Email.CreateDefaultEmail(email, name, subject, subject, htmlMessage)); - await EmailService.Disconnect(CancellationToken.None); + await EmailService.DisconnectAsync(CancellationToken.None); } public async Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml) { - await EmailService.Connect(CancellationToken.None); + await EmailService.ConnectAsync(CancellationToken.None); var email = await Email.CreateDefaultEmail(receiverMail, receiverName, subject, title, bodyHtml); await EmailService.SendEmailAsync(email); - await EmailService.Disconnect(CancellationToken.None); + await EmailService.DisconnectAsync(CancellationToken.None); } public async Task SendSubscribedMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml, string browserUrl = "", string subscribedRole = "-1") { var email = await Email.CreateSubscribedEmail(subscriber, browserUrl, subject, title, bodyHtml, subscribedRole); - await EmailService.Connect(CancellationToken.None); - await EmailService.SendEmailAsync(email); // TODO use bulk service + await BulkEmailService.ConnectAsync(CancellationToken.None); + await BulkEmailService.SendEmailAsync(email); } public async Task SendWelcomeMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml, IEnumerable articles) { var email = await Email.CreateWelcomeEmail(subscriber, articles, subject, title, bodyHtml); - await EmailService.Connect(CancellationToken.None); - await EmailService.SendEmailAsync(email); // TODO use bulk service + await BulkEmailService.ConnectAsync(CancellationToken.None); + await BulkEmailService.SendEmailAsync(email); } public async ValueTask DisposeAsync() { + GC.SuppressFinalize(this); await EmailService.DisposeAsync(); + await BulkEmailService.DisposeAsync(); } } \ No newline at end of file