Support multiple email recipients in email-sending flow
Updated the `IEmailService` interface and related components to support multiple recipients in the `SendEmailAsync` method. - Replaced `Recipient` with `Recipients` in `SendEmailCommand`, `OutgoingEmailEvent`, and `EmailsController`. - Updated `SendEmailCommandValidator` to validate a collection of recipients, ensuring at least one valid email address. - Modified `LimilabsEmailService` to handle multiple recipients by iterating over the collection and adding each to the email. - Adjusted `OutgoingEmailConsumer` to process and log multiple recipients. - Updated logging and response structures to reflect the changes. These changes enable the system to handle emails with multiple recipients while maintaining proper validation and logging.
This commit is contained in:
@@ -14,5 +14,5 @@ public interface IEmailService
|
|||||||
/// Sends an email using the configured SMTP account.
|
/// Sends an email using the configured SMTP account.
|
||||||
/// SMTP credentials are configured in appsettings.json (EmailAccount section).
|
/// SMTP credentials are configured in appsettings.json (EmailAccount section).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task SendEmailAsync(EmailAccountDto from, string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default);
|
Task SendEmailAsync(EmailAccountDto from, IEnumerable<string> recipients, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public record SendEmailCommand : IRequest<OutgoingEmailEvent>
|
|||||||
public required GetSenderQuery Sender { get; init; }
|
public required GetSenderQuery Sender { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recipient email address
|
/// Recipient email addresses
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required string Recipient { get; init; }
|
public required IEnumerable<string> Recipients { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Email subject
|
/// Email subject
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ public class SendEmailCommandValidator : AbstractValidator<SendEmailCommand>
|
|||||||
{
|
{
|
||||||
public SendEmailCommandValidator()
|
public SendEmailCommandValidator()
|
||||||
{
|
{
|
||||||
RuleFor(x => x.Recipient)
|
RuleFor(x => x.Recipients)
|
||||||
.NotEmpty()
|
.NotEmpty()
|
||||||
.WithMessage("Recipient is required")
|
.WithMessage("Recipients are required")
|
||||||
.MaximumLength(200)
|
.Must(x => x.Any())
|
||||||
.WithMessage("Recipient must not exceed 200 characters")
|
.WithMessage("At least one recipient is required")
|
||||||
.EmailAddress()
|
.ForEach(recipient => recipient
|
||||||
.WithMessage("Recipient must be a valid email address");
|
.NotEmpty()
|
||||||
|
.WithMessage("Recipient email must not be empty")
|
||||||
|
.EmailAddress()
|
||||||
|
.WithMessage("Invalid email address format"));
|
||||||
|
|
||||||
RuleFor(x => x.Subject)
|
RuleFor(x => x.Subject)
|
||||||
.NotEmpty()
|
.NotEmpty()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public record OutgoingEmailEvent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recipient email address
|
/// Recipient email address
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Recipient { get; set; } = null!;
|
public IEnumerable<string> Recipients { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Email subject
|
/// Email subject
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public sealed class OutgoingEmailConsumer : IAsyncDisposable
|
|||||||
// Send email via SMTP (SMTP config is injected in IEmailService via IOptions)
|
// Send email via SMTP (SMTP config is injected in IEmailService via IOptions)
|
||||||
await EmailService.SendEmailAsync(
|
await EmailService.SendEmailAsync(
|
||||||
oMailEvent.Sender,
|
oMailEvent.Sender,
|
||||||
oMailEvent.Recipient,
|
oMailEvent.Recipients,
|
||||||
oMailEvent.Subject,
|
oMailEvent.Subject,
|
||||||
oMailEvent.Body,
|
oMailEvent.Body,
|
||||||
isHtml: oMailEvent.IsHtml);
|
isHtml: oMailEvent.IsHtml);
|
||||||
@@ -65,7 +65,7 @@ public sealed class OutgoingEmailConsumer : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger?.LogError(ex, "Failed to process email [To={To}, Subject={Subject}] message: DeliveryTag={DeliveryTag}. Moving to DLQ (NO retry).", oMailEvent?.Recipient, oMailEvent?.Subject, args.DeliveryTag);
|
logger?.LogError(ex, "Failed to process email [To={To}, Subject={Subject}] message: DeliveryTag={DeliveryTag}. Moving to DLQ (NO retry).", oMailEvent?.Recipients, oMailEvent?.Subject, args.DeliveryTag);
|
||||||
|
|
||||||
// TODO: Error Reporting Strategy
|
// TODO: Error Reporting Strategy
|
||||||
// Option 1: Separate RabbitMQ Queue (emailprofiler.errors)
|
// Option 1: Separate RabbitMQ Queue (emailprofiler.errors)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class LimilabsEmailService(
|
|||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendEmailAsync(EmailAccountDto from, string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default)
|
public async Task SendEmailAsync(EmailAccountDto from, IEnumerable<string> recipients, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using var smtp = new Smtp();
|
using var smtp = new Smtp();
|
||||||
|
|
||||||
@@ -37,7 +37,10 @@ public class LimilabsEmailService(
|
|||||||
|
|
||||||
var builder = new MailBuilder();
|
var builder = new MailBuilder();
|
||||||
builder.From.Add(new MailBox(from.Username));
|
builder.From.Add(new MailBox(from.Username));
|
||||||
builder.To.Add(new MailBox(to));
|
|
||||||
|
foreach (var recipient in recipients)
|
||||||
|
builder.To.Add(new MailBox(recipient));
|
||||||
|
|
||||||
builder.Subject = subject;
|
builder.Subject = subject;
|
||||||
|
|
||||||
if (isHtml)
|
if (isHtml)
|
||||||
|
|||||||
@@ -27,10 +27,7 @@ public class EmailsController(IMediator mediator) : ControllerBase
|
|||||||
|
|
||||||
return Accepted(new
|
return Accepted(new
|
||||||
{
|
{
|
||||||
CommandId = outgoingEmailEvent.Id,
|
outgoingEmailEvent.Id,
|
||||||
To = outgoingEmailEvent.Recipient,
|
|
||||||
outgoingEmailEvent.Subject,
|
|
||||||
outgoingEmailEvent.QueuedAt
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user