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;