< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Email.MailgunSender
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Email/MailgunSender.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 56
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage
100%
Covered methods: 2
Fully covered methods: 2
Total methods: 2
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
DeliverAsync()100%22100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Email/MailgunSender.cs

#LineLine coverage
 1using ArturRios.Heimdall.Shared.Security;
 2using ArturRios.Messaging.Email;
 3
 4namespace ArturRios.Heimdall.WebApi.Email;
 5
 6/// <summary>
 7///     Shared delivery path for the transactional emails this API sends through
 8///     <see cref="ArturRios.Messaging" />'s Mailgun service. Subclasses supply the subject and body;
 9///     this class owns the one rule both of them must obey.
 10/// </summary>
 11/// <remarks>
 12///     <para>
 13///         <b>A send failure is logged, never propagated.</b> Both callers sit on endpoints that
 14///         must answer identically whether or not the address belongs to anyone — UC-12's AF-12a
 15///         most explicitly. Letting a Mailgun outage, a bad API key, or a dropped connection surface
 16///         as a 500 would turn "the email was delivered" into an observable difference, and hand an
 17///         anonymous caller the account-enumeration oracle the specification is built to deny them.
 18///     </para>
 19///     <para>
 20///         The token survives the failure: it is already persisted by the time delivery is
 21///         attempted, so a caller can request another email, and UC-15 can resend the verification
 22///         one.
 23///     </para>
 24/// </remarks>
 2425public abstract class MailgunSender(IEmailService emailService, ILogger logger)
 26{
 27    /// <summary>What this sender delivers, for log messages. E.g. "password reset".</summary>
 28    protected abstract string Purpose { get; }
 29
 30    protected async Task DeliverAsync(string email, string subject, string body)
 31    {
 32        try
 33        {
 2434            var result = await emailService.SendEmailAsync(email, subject, body);
 35
 2136            if (!result.Success)
 37            {
 138                logger.LogError(
 139                    "Mailgun refused the {Purpose} email for {EmailRef}: {Errors}",
 140                    Purpose, LogSafeEmail.Reference(email), string.Join(" | ", result.Errors));
 41
 142                return;
 43            }
 44
 2045            logger.LogInformation("Sent the {Purpose} email to {EmailRef}", Purpose, LogSafeEmail.Reference(email));
 2046        }
 347        catch (Exception exception)
 48        {
 49            // Reaching Mailgun is I/O: it can time out, refuse the connection, or fail DNS. None of
 50            // that is the caller's business, and none of it may change the response they get.
 351            logger.LogError(
 352                exception, "Could not deliver the {Purpose} email to {EmailRef}",
 353                Purpose, LogSafeEmail.Reference(email));
 354        }
 2455    }
 56}