diff --git a/src/ScadaLink.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs b/src/ScadaLink.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs index 8928530..7fb8946 100644 --- a/src/ScadaLink.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs +++ b/src/ScadaLink.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs @@ -91,7 +91,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap // A malformed sender or recipient address cannot be fixed by retrying — // surface it as a permanent failure (mirrors NS-008). - var addressError = NotificationDeliveryService.ValidateAddresses( + var addressError = EmailAddressValidator.ValidateAddresses( smtpConfig.FromAddress, recipients); if (addressError != null) { diff --git a/src/ScadaLink.NotificationService/EmailAddressValidator.cs b/src/ScadaLink.NotificationService/EmailAddressValidator.cs new file mode 100644 index 0000000..6fb38e9 --- /dev/null +++ b/src/ScadaLink.NotificationService/EmailAddressValidator.cs @@ -0,0 +1,38 @@ +using MimeKit; +using ScadaLink.Commons.Entities.Notifications; + +namespace ScadaLink.NotificationService; + +/// +/// NS-008: Validates the sender and recipient email addresses before an SMTP +/// delivery is attempted, so a malformed address surfaces as a clean error +/// string rather than a ParseException escaping the delivery path. +/// +/// Public so the central Notification Outbox's EmailNotificationDeliveryAdapter +/// can share this exact pre-send validation rather than carry a divergent copy. +/// +/// +public static class EmailAddressValidator +{ + /// + /// Validates the sender and recipient email addresses, returning a + /// human-readable error string if any is malformed, or null if all parse. + /// + public static string? ValidateAddresses( + string fromAddress, IReadOnlyList recipients) + { + if (!MailboxAddress.TryParse(fromAddress, out _)) + { + return $"Invalid sender (from) email address: '{fromAddress}'"; + } + + var invalid = recipients + .Where(r => !MailboxAddress.TryParse(r.EmailAddress, out _)) + .Select(r => r.EmailAddress) + .ToList(); + + return invalid.Count > 0 + ? $"Invalid recipient email address(es): {string.Join(", ", invalid)}" + : null; + } +} diff --git a/src/ScadaLink.NotificationService/NotificationDeliveryService.cs b/src/ScadaLink.NotificationService/NotificationDeliveryService.cs index c760e97..2508641 100644 --- a/src/ScadaLink.NotificationService/NotificationDeliveryService.cs +++ b/src/ScadaLink.NotificationService/NotificationDeliveryService.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using MimeKit; using ScadaLink.Commons.Entities.Notifications; using ScadaLink.Commons.Interfaces.Repositories; using ScadaLink.Commons.Interfaces.Services; @@ -91,7 +90,7 @@ public class NotificationDeliveryService : INotificationDeliveryService, IDispos // malformed address previously caused MailboxAddress.Parse to throw a // ParseException that escaped SendAsync unhandled; it must instead produce a // clean NotificationResult the calling script can handle. - var addressError = ValidateAddresses(smtpConfig.FromAddress, recipients); + var addressError = EmailAddressValidator.ValidateAddresses(smtpConfig.FromAddress, recipients); if (addressError != null) { _logger.LogWarning("Notification to list {List} has invalid addresses: {Reason}", listName, addressError); @@ -225,7 +224,7 @@ public class NotificationDeliveryService : INotificationDeliveryService, IDispos } // NS-008: a malformed address cannot be fixed by retrying — park it. - var addressError = ValidateAddresses(smtpConfig.FromAddress, recipients); + var addressError = EmailAddressValidator.ValidateAddresses(smtpConfig.FromAddress, recipients); if (addressError != null) { _logger.LogError( @@ -344,33 +343,6 @@ public class NotificationDeliveryService : INotificationDeliveryService, IDispos GC.SuppressFinalize(this); } - /// - /// NS-008: Validates the sender and recipient email addresses, returning a - /// human-readable error string if any is malformed, or null if all parse. - /// - /// Public and shared: the central Notification Outbox's - /// EmailNotificationDeliveryAdapter applies the same pre-send address - /// validation, so both delivery paths use this single implementation. - /// - /// - public static string? ValidateAddresses( - string fromAddress, IReadOnlyList recipients) - { - if (!MailboxAddress.TryParse(fromAddress, out _)) - { - return $"Invalid sender (from) email address: '{fromAddress}'"; - } - - var invalid = recipients - .Where(r => !MailboxAddress.TryParse(r.EmailAddress, out _)) - .Select(r => r.EmailAddress) - .ToList(); - - return invalid.Count > 0 - ? $"Invalid recipient email address(es): {string.Join(", ", invalid)}" - : null; - } - /// /// Delivers an email via SMTP. Throws on failure (transient errors and /// propagate; the caller classifies them). diff --git a/tests/ScadaLink.NotificationService.Tests/SmtpErrorClassifierTests.cs b/tests/ScadaLink.NotificationService.Tests/SmtpErrorClassifierTests.cs index 442934d..8ffe947 100644 --- a/tests/ScadaLink.NotificationService.Tests/SmtpErrorClassifierTests.cs +++ b/tests/ScadaLink.NotificationService.Tests/SmtpErrorClassifierTests.cs @@ -1,7 +1,6 @@ using System.Net.Sockets; using MailKit; using MailKit.Net.Smtp; -using MailKit.Security; namespace ScadaLink.NotificationService.Tests;