diff --git a/src/core/DigitalData.MessagingService.Application/Common/Dtos/EmailAccountDto.cs b/src/core/DigitalData.MessagingService.Application/Common/Dtos/EmailAccountDto.cs
deleted file mode 100644
index d2c3ebb..0000000
--- a/src/core/DigitalData.MessagingService.Application/Common/Dtos/EmailAccountDto.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace DigitalData.MessagingService.Application.Common.Dtos;
-
-///
-/// DTO for a single email account configuration.
-///
-public class EmailAccountDto
-{
- ///
- /// Logical name to identify this account (e.g. "default", "support").
- ///
- public int Id { get; init; }
-
- public required string Username { get; init; }
-
- public required string Password { get; init; }
-
- public bool PasswordEncrypted { get; init; } = false;
-
- public required string SmtpServer { get; init; }
-
- public int SmtpPort { get; init; }
-
- public bool SmtpUseSsl { get; init; }
-
- public bool UseOAuth2 { get; init; }
-}
-
diff --git a/src/core/DigitalData.MessagingService.Application/Common/Interfaces/IEmailService.cs b/src/core/DigitalData.MessagingService.Application/Common/Interfaces/IEmailService.cs
index c7639c5..7c6c1a8 100644
--- a/src/core/DigitalData.MessagingService.Application/Common/Interfaces/IEmailService.cs
+++ b/src/core/DigitalData.MessagingService.Application/Common/Interfaces/IEmailService.cs
@@ -1,3 +1,5 @@
+using DigitalData.MessagingService.Application.Common.Dtos;
+
namespace DigitalData.MessagingService.Application.Common.Interfaces;
///
@@ -12,5 +14,5 @@ public interface IEmailService
/// Sends an email using the configured SMTP account.
/// SMTP credentials are configured in appsettings.json (EmailAccount section).
///
- Task SendEmailAsync(string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default);
+ Task SendEmailAsync(EmailAccountDto from, string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default);
}
diff --git a/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs b/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs
index 70659d7..fdb0c3b 100644
--- a/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs
+++ b/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs
@@ -12,8 +12,10 @@ public class EmailMappingProfile : Profile
public EmailMappingProfile()
{
// SendEmailCommand -> OutgoingEmailEvent
+ // Sender is resolved via MediatR in the handler and set separately after mapping.
CreateMap()
.ForMember(dest => dest.Id, opt => opt.MapFrom(_ => Guid.NewGuid()))
- .ForMember(dest => dest.QueuedAt, opt => opt.MapFrom(_ => DateTime.Now));
+ .ForMember(dest => dest.QueuedAt, opt => opt.MapFrom(_ => DateTime.Now))
+ .ForMember(dest => dest.Sender, opt => opt.Ignore());
}
}
diff --git a/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj b/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj
index e774d62..b06f179 100644
--- a/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj
+++ b/src/core/DigitalData.MessagingService.Application/DigitalData.MessagingService.Application.csproj
@@ -20,8 +20,4 @@
-
-
-
-
diff --git a/src/core/DigitalData.MessagingService.Application/EmailAccount/Queries/GetSenderQuery.cs b/src/core/DigitalData.MessagingService.Application/EmailAccount/Queries/GetSenderQuery.cs
new file mode 100644
index 0000000..aaf760c
--- /dev/null
+++ b/src/core/DigitalData.MessagingService.Application/EmailAccount/Queries/GetSenderQuery.cs
@@ -0,0 +1,39 @@
+using DigitalData.MessagingService.Application.Common.Dtos;
+using DigitalData.MessagingService.Application.Common.Options;
+using MediatR;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace DigitalData.MessagingService.Application.EmailAccount.Queries;
+
+public record GetSenderQuery : IRequest
+{
+ public int? Id { get; init; }
+
+ public string? Username { get; init; }
+}
+
+///
+///
+///
+///
+///
+public class GetSenderQueryHandler(IOptions Options, ILogger Logger) : IRequestHandler
+{
+ public Task Handle(GetSenderQuery request, CancellationToken cancellationToken)
+ {
+ var accounts = request.Id is not null
+ ? Options.Value.Accounts.Where(a => a.Id == request.Id)
+ : Options.Value.Accounts.Where(a => a.Username == request.Username);
+
+ if(accounts.Count() > 1)
+ {
+ Logger.LogWarning(
+ "Multiple email accounts found for the given criteria ({Criteria}). Returning the first one.",
+ request.Id is not null ? $"Id: {request.Id}" : $"Username: {request.Username}"
+ );
+ }
+
+ return Task.FromResult(accounts.FirstOrDefault());
+ }
+}
\ No newline at end of file
diff --git a/src/core/DigitalData.MessagingService.Application/EmailAccount/Validators/GetSenderQueryValidator.cs b/src/core/DigitalData.MessagingService.Application/EmailAccount/Validators/GetSenderQueryValidator.cs
new file mode 100644
index 0000000..b26ce32
--- /dev/null
+++ b/src/core/DigitalData.MessagingService.Application/EmailAccount/Validators/GetSenderQueryValidator.cs
@@ -0,0 +1,27 @@
+using DigitalData.MessagingService.Application.EmailAccount.Queries;
+using FluentValidation;
+
+namespace DigitalData.MessagingService.Application.EmailAccount.Validators;
+
+///
+/// Validator for .
+/// Either or must be provided, but not both.
+///
+public class GetSenderQueryValidator : AbstractValidator
+{
+ public GetSenderQueryValidator()
+ {
+ RuleFor(x => x)
+ .Must(x => (x.Id is not null) ^ (x.Username is not null))
+ .WithMessage("Either Id or Username must be provided, but not both.");
+
+ When(x => x.Username is not null, () =>
+ {
+ RuleFor(x => x.Username)
+ .NotEmpty()
+ .WithMessage("Username must not be empty.")
+ .MaximumLength(200)
+ .WithMessage("Username must not exceed 200 characters.");
+ });
+ }
+}
diff --git a/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs b/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs
index 3db5d89..c27fc1f 100644
--- a/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs
+++ b/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs
@@ -1,4 +1,7 @@
using AutoMapper;
+using DigitalData.MessagingService.Application.Common.Dtos;
+using DigitalData.MessagingService.Application.EmailAccount.Queries;
+using DigitalData.MessagingService.Domain.Exceptions;
using DigitalData.MessagingService.Publisher.Abstraction;
using MediatR;
@@ -9,6 +12,8 @@ namespace DigitalData.MessagingService.Application.EmailSending.Commands;
///
public record SendEmailCommand : IRequest
{
+ public required GetSenderQuery Sender { get; init; }
+
///
/// Recipient email address
///
@@ -32,13 +37,20 @@ public record SendEmailCommand : IRequest
///
/// Handler for SendEmailCommand
-/// Creates EmailOutbox entity via AutoMapper and enqueues to RabbitMQ
+/// Resolves the sender account via MediatR, maps to OutgoingEmailEvent and enqueues to RabbitMQ
///
-public class SendEmailCommandHandler(IOutgoingEmailPublisher Publisher, IMapper Mapper) : IRequestHandler
+public class SendEmailCommandHandler(
+ISender Sender,
+IOutgoingEmailPublisher Publisher,
+IMapper Mapper) : IRequestHandler
{
public async Task Handle(SendEmailCommand request, CancellationToken cancellationToken)
{
- var outgoingEmailEvent = Mapper.Map(request);
+ var senderAccount = await Sender.Send(request.Sender, cancellationToken)
+ ?? throw new NotFoundException(
+ $"No email account found for the given sender criteria (Id: {request.Sender.Id}, Username: {request.Sender.Username}).");
+
+ var outgoingEmailEvent = Mapper.Map(request) with { Sender = senderAccount };
// Enqueue to RabbitMQ
await Publisher.EnqueueAsync(outgoingEmailEvent, cancellationToken);
diff --git a/src/core/DigitalData.MessagingService.Publisher.Abstraction/EmailAccountDto.cs b/src/core/DigitalData.MessagingService.Publisher.Abstraction/EmailAccountDto.cs
new file mode 100644
index 0000000..c27bd8a
--- /dev/null
+++ b/src/core/DigitalData.MessagingService.Publisher.Abstraction/EmailAccountDto.cs
@@ -0,0 +1,38 @@
+namespace DigitalData.MessagingService.Application.Common.Dtos;
+
+///
+/// DTO for a single email account configuration.
+///
+public class EmailAccountDto
+{
+ ///
+ /// Logical name to identify this account (e.g. "default", "support").
+ ///
+ public int Id { get; set; }
+
+#if NET
+ public required string Username { get; set; }
+#else
+ public string Username { get; set; } = null!;
+#endif
+
+#if NET
+ public required string Password { get; set; }
+#else
+ public string Password { get; set; } = null!;
+#endif
+
+ public bool PasswordEncrypted { get; set; } = false;
+
+#if NET
+ public required string SmtpServer { get; set; }
+#else
+ public string SmtpServer { get; set; } = null!;
+#endif
+
+ public int SmtpPort { get; set; }
+
+ public bool SmtpUseSsl { get; set; }
+
+ public bool UseOAuth2 { get; set; }
+}
\ No newline at end of file
diff --git a/src/core/DigitalData.MessagingService.Publisher.Abstraction/OutgoingEmailEvent.cs b/src/core/DigitalData.MessagingService.Publisher.Abstraction/OutgoingEmailEvent.cs
index 5250208..a7e338e 100644
--- a/src/core/DigitalData.MessagingService.Publisher.Abstraction/OutgoingEmailEvent.cs
+++ b/src/core/DigitalData.MessagingService.Publisher.Abstraction/OutgoingEmailEvent.cs
@@ -1,9 +1,13 @@
-namespace DigitalData.MessagingService.Publisher.Abstraction;
+using DigitalData.MessagingService.Application.Common.Dtos;
+
+namespace DigitalData.MessagingService.Publisher.Abstraction;
public record OutgoingEmailEvent
{
public Guid Id { get; set; }
+ public EmailAccountDto Sender { get; set; } = null!;
+
///
/// Recipient email address
///
diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs
index 80da0ee..b558c58 100644
--- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs
+++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs
@@ -48,6 +48,7 @@ public sealed class OutgoingEmailConsumer : IAsyncDisposable
{
// Send email via SMTP (SMTP config is injected in IEmailService via IOptions)
await EmailService.SendEmailAsync(
+ oMailEvent.Sender,
oMailEvent.Recipient,
oMailEvent.Subject,
oMailEvent.Body,
diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs
index 44e8c86..94d3f33 100644
--- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs
+++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs
@@ -19,8 +19,7 @@ namespace DigitalData.MessagingService.Infrastructure.Services;
/// or falls back to the first account if none is named "default".
///
public class LimilabsEmailService(
- IEncryptionService encryptionService,
- IOptions smtpConfig) : IEmailService
+ IEncryptionService encryptionService) : IEmailService
{
// Register encoding provider for Limilabs (requires windows-1252 and other code pages)
static LimilabsEmailService()
@@ -28,18 +27,16 @@ public class LimilabsEmailService(
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
- public async Task SendEmailAsync(string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default)
+ public async Task SendEmailAsync(EmailAccountDto from, 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, smtpAccount);
+ await ConnectAndAuthenticateSmtpAsync(smtp, from);
var builder = new MailBuilder();
- builder.From.Add(new MailBox(smtpAccount.Username));
+ builder.From.Add(new MailBox(from.Username));
builder.To.Add(new MailBox(to));
builder.Subject = subject;
diff --git a/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json b/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json
index 195654e..9f3529d 100644
--- a/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json
+++ b/src/presentation/DigitalData.MessagingService.API/appsettings.Secrets.json
@@ -18,7 +18,7 @@
"EmailAccounts": {
"Accounts": [
{
- "Name": "1",
+ "Id": "1",
"Username": "test-flow@digitaldata.works",
"Password": "ddemail108",
"PasswordEncrypted": false,