From e53fabcda2824c140594d330e741220929701bae Mon Sep 17 00:00:00 2001 From: TekH Date: Wed, 5 Aug 2026 10:46:43 +0200 Subject: [PATCH] Refactor email account configuration for multi-account support Refactored `EmailAccountDto` to represent a single account with immutable properties and added an `Id` field. Introduced `EmailAccountsOptions` to manage multiple accounts and bound it to the `EmailAccounts` configuration section. Updated `DependencyInjection` to register `EmailAccountsOptions` and removed the old single-account binding. Refactored `LimilabsEmailService` to use `EmailAccountsOptions` and select the appropriate account dynamically. Replaced the `EmailAccount` section in `appsettings.Secrets.json` with a new `EmailAccounts` section supporting multiple accounts. Added a package reference for `Microsoft.Extensions.Options. ConfigurationExtensions` to support the options pattern. --- .../Common/Dtos/EmailAccountDto.cs | 22 +++++++++----- .../Common/Options/EmailAccountsOptions.cs | 17 +++++++++++ .../DependencyInjection.cs | 7 ++++- ...alData.MessagingService.Application.csproj | 1 + .../Services/LimilabsEmailService.cs | 29 ++++++++++--------- .../Program.cs | 4 --- .../appsettings.Secrets.json | 21 +++++++++----- 7 files changed, 67 insertions(+), 34 deletions(-) create mode 100644 src/core/DigitalData.MessagingService.Application/Common/Options/EmailAccountsOptions.cs diff --git a/src/core/DigitalData.MessagingService.Application/Common/Dtos/EmailAccountDto.cs b/src/core/DigitalData.MessagingService.Application/Common/Dtos/EmailAccountDto.cs index 7229922..d2c3ebb 100644 --- a/src/core/DigitalData.MessagingService.Application/Common/Dtos/EmailAccountDto.cs +++ b/src/core/DigitalData.MessagingService.Application/Common/Dtos/EmailAccountDto.cs @@ -1,21 +1,27 @@ namespace DigitalData.MessagingService.Application.Common.Dtos; /// -/// DTO for EmailAccount query results. +/// DTO for a single email account configuration. /// public class EmailAccountDto { - public string Username { get; set; } = null!; + /// + /// Logical name to identify this account (e.g. "default", "support"). + /// + public int Id { get; init; } - public string Password { get; set; } = null!; + public required string Username { get; init; } - public bool PasswordEncrypted { get; set; } = false; + public required string Password { get; init; } - public string SmtpServer { get; set; } = null!; + public bool PasswordEncrypted { get; init; } = false; - public int SmtpPort { get; set; } + public required string SmtpServer { get; init; } - public bool SmtpUseSsl { get; set; } + public int SmtpPort { get; init; } - public bool UseOAuth2 { get; set; } + public bool SmtpUseSsl { get; init; } + + public bool UseOAuth2 { get; init; } } + diff --git a/src/core/DigitalData.MessagingService.Application/Common/Options/EmailAccountsOptions.cs b/src/core/DigitalData.MessagingService.Application/Common/Options/EmailAccountsOptions.cs new file mode 100644 index 0000000..79f13b9 --- /dev/null +++ b/src/core/DigitalData.MessagingService.Application/Common/Options/EmailAccountsOptions.cs @@ -0,0 +1,17 @@ +using DigitalData.MessagingService.Application.Common.Dtos; + +namespace DigitalData.MessagingService.Application.Common.Options; + +/// +/// Wrapper options class that holds a list of entries +/// bound from the EmailAccounts configuration section. +/// +public class EmailAccountsOptions +{ + public const string SectionName = "EmailAccounts"; + + /// + /// The list of configured email accounts. + /// + public required IEnumerable Accounts { get; init; } = []; +} diff --git a/src/core/DigitalData.MessagingService.Application/DependencyInjection.cs b/src/core/DigitalData.MessagingService.Application/DependencyInjection.cs index ae3a9ee..a925d39 100644 --- a/src/core/DigitalData.MessagingService.Application/DependencyInjection.cs +++ b/src/core/DigitalData.MessagingService.Application/DependencyInjection.cs @@ -1,7 +1,9 @@ -using System.Reflection; +using DigitalData.MessagingService.Application.Common.Options; using FluentValidation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System.Reflection; namespace DigitalData.MessagingService.Application; @@ -35,6 +37,9 @@ public static class DependencyInjection // FluentValidation - Register all validators services.AddValidatorsFromAssembly(assembly); + // Register EmailAccounts configuration (IOptions) + services.Configure(configuration.GetSection(EmailAccountsOptions.SectionName)); + return services; } } diff --git a/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj b/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj index 5c3da41..e774d62 100644 --- a/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj +++ b/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj @@ -17,6 +17,7 @@ + diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs index 0268553..fd1685b 100644 --- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs +++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs @@ -6,20 +6,21 @@ using Limilabs.Client.SMTP; using Limilabs.Mail; using Limilabs.Mail.Headers; using Microsoft.Extensions.Options; +using DigitalData.MessagingService.Application.Common.Options; namespace DigitalData.MessagingService.Infrastructure.Services; /// /// Email service using Limilabs Mail.dll for SMTP operations (send-only). /// Commercial-grade library with superior Exchange support. -/// SMTP configuration is injected via IOptions<EmailAccountDto> from appsettings.json. +/// SMTP configuration is injected via IOptions<EmailAccountsOptions> from appsettings.json. +/// Uses the first account in the list whose equals "default", +/// or falls back to the first account if none is named "default". /// public class LimilabsEmailService( IEncryptionService encryptionService, - IOptions smtpConfig) : IEmailService + IOptions smtpConfig) : IEmailService { - private readonly EmailAccountDto _smtpAccount = smtpConfig.Value; - // Register encoding provider for Limilabs (requires windows-1252 and other code pages) static LimilabsEmailService() { @@ -28,14 +29,16 @@ public class LimilabsEmailService( public async Task SendEmailAsync(string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default) { + var smtpAccount = smtpConfig.Value.Accounts.First(); + using var smtp = new Smtp(); try { - await ConnectAndAuthenticateSmtpAsync(smtp); + await ConnectAndAuthenticateSmtpAsync(smtp, smtpAccount); var builder = new MailBuilder(); - builder.From.Add(new MailBox(_smtpAccount.Username)); + builder.From.Add(new MailBox(smtpAccount.Username)); builder.To.Add(new MailBox(to)); builder.Subject = subject; @@ -74,26 +77,26 @@ public class LimilabsEmailService( // --- Private Helper Methods --- - private async Task ConnectAndAuthenticateSmtpAsync(Smtp smtp) + private async Task ConnectAndAuthenticateSmtpAsync(Smtp smtp, EmailAccountDto smtpAccount) { - if (_smtpAccount.SmtpUseSsl) + if (smtpAccount.SmtpUseSsl) { - smtp.ConnectSSL(_smtpAccount.SmtpServer, _smtpAccount.SmtpPort); + smtp.ConnectSSL(smtpAccount.SmtpServer, smtpAccount.SmtpPort); } else { - smtp.Connect(_smtpAccount.SmtpServer, _smtpAccount.SmtpPort); + smtp.Connect(smtpAccount.SmtpServer, smtpAccount.SmtpPort); } - if (_smtpAccount.UseOAuth2) + if (smtpAccount.UseOAuth2) { throw new NotSupportedException("OAuth2 is not configured for this SMTP account. UseOAuth2 must be false."); } else { - var password = _smtpAccount.PasswordEncrypted ? encryptionService.Decrypt(_smtpAccount.Password) : _smtpAccount.Password; + var password = smtpAccount.PasswordEncrypted ? encryptionService.Decrypt(smtpAccount.Password) : smtpAccount.Password; - smtp.Login(_smtpAccount.Username, password); + smtp.Login(smtpAccount.Username, password); } await Task.CompletedTask; // For async consistency diff --git a/src/presentation/DigitalData.MessagingService.API/Program.cs b/src/presentation/DigitalData.MessagingService.API/Program.cs index 1fd24e1..07f59dc 100644 --- a/src/presentation/DigitalData.MessagingService.API/Program.cs +++ b/src/presentation/DigitalData.MessagingService.API/Program.cs @@ -67,10 +67,6 @@ try // Register Infrastructure layer (RabbitMQ, Repositories, etc.) builder.Services.AddInfrastructure(builder.Configuration); - // Register EmailAccount configuration (IOptions) - builder.Services.Configure( - builder.Configuration.GetSection("EmailAccount")); - builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); diff --git a/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json b/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json index 7d519ff..195654e 100644 --- a/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json +++ b/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json @@ -15,13 +15,18 @@ "DlqExchangeName": "emailprofiler.emails.dlq", "DlqRoutingKey": "email.outbox.dlq" }, - "EmailAccount": { - "Username": "test-flow@digitaldata.works", - "Password": "ddemail108", - "PasswordEncrypted": false, - "SmtpServer": "kundencenter.triplew.de", - "SmtpPort": 465, - "SmtpUseSsl": true, - "UseOAuth2": false + "EmailAccounts": { + "Accounts": [ + { + "Name": "1", + "Username": "test-flow@digitaldata.works", + "Password": "ddemail108", + "PasswordEncrypted": false, + "SmtpServer": "kundencenter.triplew.de", + "SmtpPort": 465, + "SmtpUseSsl": true, + "UseOAuth2": false + } + ] } }