feat: consume transactional email notifications
Add contract validation, SMTP delivery results, terminal failure context, neutral development seeding, and local Docker setup. Ref: IT-1033
This commit is contained in:
+80
@@ -0,0 +1,80 @@
|
||||
namespace HrynCo.NotificationService.Worker.Services.EmailProcessing;
|
||||
|
||||
using System.Net.Mail;
|
||||
using System.Net.Sockets;
|
||||
|
||||
public static class NotificationDeliveryErrorFormatter
|
||||
{
|
||||
private const int MaximumErrorLength = 2000;
|
||||
private const string FallbackError = "Notification delivery failed after all retry attempts.";
|
||||
|
||||
public static string Format(Exception exception)
|
||||
{
|
||||
string technicalMessage = Normalize(exception.GetBaseException().Message);
|
||||
if (string.IsNullOrWhiteSpace(technicalMessage))
|
||||
{
|
||||
return FallbackError;
|
||||
}
|
||||
|
||||
string contextualMessage = CreateContextualMessage(exception, technicalMessage);
|
||||
return contextualMessage.Length <= MaximumErrorLength
|
||||
? contextualMessage
|
||||
: contextualMessage[..MaximumErrorLength];
|
||||
}
|
||||
|
||||
private static string CreateContextualMessage(Exception exception, string technicalMessage)
|
||||
{
|
||||
if (FindException<SmtpFailedRecipientException>(exception) is not null)
|
||||
{
|
||||
return "SMTP delivery failed after all retry attempts: " +
|
||||
"the SMTP server rejected the recipient address.";
|
||||
}
|
||||
|
||||
SocketException? socketException = FindException<SocketException>(exception);
|
||||
if (socketException is not null)
|
||||
{
|
||||
string reason = socketException.SocketErrorCode switch
|
||||
{
|
||||
SocketError.HostNotFound or SocketError.NoData =>
|
||||
"the configured SMTP server host could not be resolved",
|
||||
SocketError.ConnectionRefused =>
|
||||
"the configured SMTP server refused the connection",
|
||||
SocketError.TimedOut =>
|
||||
"the connection to the configured SMTP server timed out",
|
||||
_ when technicalMessage.Contains(
|
||||
"Name or service not known",
|
||||
StringComparison.OrdinalIgnoreCase) =>
|
||||
"the configured SMTP server host could not be resolved",
|
||||
_ => "the configured SMTP server could not be reached"
|
||||
};
|
||||
|
||||
return $"SMTP delivery failed after all retry attempts: {reason} ({technicalMessage}).";
|
||||
}
|
||||
|
||||
if (FindException<SmtpException>(exception) is not null)
|
||||
{
|
||||
return $"SMTP delivery failed after all retry attempts ({technicalMessage}).";
|
||||
}
|
||||
|
||||
return $"Notification delivery failed after all retry attempts ({technicalMessage}).";
|
||||
}
|
||||
|
||||
private static TException? FindException<TException>(Exception exception)
|
||||
where TException : Exception
|
||||
{
|
||||
for (Exception? current = exception; current is not null; current = current.InnerException)
|
||||
{
|
||||
if (current is TException typedException)
|
||||
{
|
||||
return typedException;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string Normalize(string message)
|
||||
{
|
||||
return message.ReplaceLineEndings(" ").Trim();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user