rgl / MailBounceDetector

Detects whether a MailKit email Message is a bounce message
MIT License
28 stars 12 forks source link

Provide enum for Action field #21

Open rklec opened 1 month ago

rklec commented 1 month ago

The Action currently is a string. For a programmer, an enum would maybe be more helpful.

https://www.rfc-editor.org/rfc/rfc3464#section-2.3.3 defines the values of it. As this s an exclusive (aka terminal) list, it cannot contain more/other entries and it must be contained in a DSN.

So AFAIK it should be safe to parse in an enum?

You can then C# doc it to explain what the status means.

E.g. like this:

/// <summary>
/// An enum containing the DSN (delivery status notification) action field values, as per <em>RFC 3464</em>.
/// </summary>
/// <remarks><see href="https://www.rfc-editor.org/rfc/rfc3464#section-2.3.3"/></remarks>
public enum DsnAction
{
    /// <summary>
    /// "indicates that the message could not be delivered to the
    /// recipient.  The Reporting MTA has abandoned any attempts
    /// to deliver the message to this recipient.  No further
    /// notifications should be expected."
    /// </summary>
    Failed,

    /// <summary>
    /// "indicates that the Reporting MTA has so far been unable
    /// to deliver or relay the message, but it will continue to
    /// attempt to do so.  Additional notification messages may
    /// be issued as the message is further delayed or
    /// successfully delivered, or if delivery attempts are later
    /// abandoned."
    /// </summary>
    Delayed,

    /// <summary>
    /// "indicates that the message was successfully delivered to
    /// the recipient address specified by the sender, which
    /// includes "delivery" to a mailing list exploder.  It does
    /// not indicate that the message has been read.  This is a
    /// terminal state and no further DSN for this recipient
    /// should be expected."
    /// </summary>
    Delivered,

    /// <summary>
    /// "indicates that the message has been relayed or gatewayed
    /// into an environment that does not accept responsibility
    /// for generating DSNs upon successful delivery.  This
    /// action-value SHOULD NOT be used unless the sender has
    /// requested notification of successful delivery for this
    /// recipient."
    /// </summary>
    Relayed,

    /// <summary>
    /// "indicates that the message has been successfully
    /// delivered to the recipient address as specified by the
    /// sender, and forwarded by the Reporting-MTA beyond that
    /// destination to multiple additional recipient addresses.
    /// An action-value of "expanded" differs from "delivered" in
    /// that "expanded" is not a terminal state.  Further
    /// "failed" and/or "delayed" notifications may be provided."
    /// </summary>
    Expanded,
}

Care must just be taken to parse it case-insensitively, as the RFC says:

The action-value may be spelled in any combination of upper and lower case characters.

Parsing could e.g. be done in a class like this:

public class DsnActionLookup
{
    private readonly Dictionary<string, DsnAction> actions = new(StringComparer.OrdinalIgnoreCase)
    {
        { "failed", DsnAction.Failed },
        { "delayed", DsnAction.Delayed },
        { "delivered", DsnAction.Delivered },
        { "relayed", DsnAction.Relayed },
        { "expanded", DsnAction.Expanded }
    };

    /// <summary>
    /// Tries to get the <see cref="DsnAction"/> corresponding to the given action string.
    /// </summary>
    /// <param name="action">The action string to compare.</param>
    /// <param name="dsnAction">The resulting <see cref="DsnAction"/> if found.</param>
    /// <returns><c>true</c> if the action string matched; otherwise, <c>false</c>.</returns>
    public bool TryGetDsnAction(string action, out DsnAction dsnAction)
    {
        return actions.TryGetValue(action, out dsnAction);
    }

    /// <summary>
    /// Indexer to retrieve the <see cref="DsnAction"/> corresponding to the given action string.
    /// </summary>
    /// <param name="action">The action string to compare.</param>
    /// <exception cref="KeyNotFoundException">Thrown when the action string does not exist in the lookup.</exception>
    /// <returns>The <see cref="DsnAction"/> corresponding to the provided action string.</returns>
    public DsnAction this[string action] => actions[action];
}