jstedfast/MimeKit

Suppressing Automatic Replies

mayka-mack opened this issue · 13 comments

If one wanted to suppress receiving automatic replies in response to a message sent via MailKit, would adding the X-Auto-Response-Suppress header to the MimeMessage be the correct way to go about this? Our organization uses Outlook 365, so the vast majority of our auto-replies will be coming from an Exchange server.

MimeMessage message = new();
message.Headers.Add("X-Auto-Response-Suppress", "DR, RN, NRN, OOF, AutoReply");

There is of course the more standard "Auto-Submitted: auto-generated" header, but I was unsure if this would also tell the mail server not to respond with another auto-generated email or auto-reply.

message.Headers.Add(HeaderId.AutoSubmitted, "auto-generated");

It is almost impossible to suppress auto replies on e-mails that you sent. You can set the ReplyTo field to something like noreply@yourcompany.com to let the receiving mailserver sent the reply to another e-mail address that just eats the reply. But even that is not 100% fool proof.

You can also make something that just tries to detect if an e-mail is an auto reply and then just deletes the e-mail but that is also not 100% fool proof because there is no real standard that says if an e-mail is an auto reply.

It is almost impossible to suppress auto replies on e-mails that you sent. You can set the ReplyTo field to something like noreply@yourcompany.com to let the receiving mailserver sent the reply to another e-mail address that just eats the reply. But even that is not 100% full proof.

You can also make something that just tries to detect if an e-mail is an auto reply and then just deletes the e-mail but that is also not 100% full proof because there is no real standard that says if an e-mail is an auto reply.

Indeed I feared that may be the case, though my initial research did lead me to RFC 3834, which notes the following:

Automatic responses SHOULD NOT be issued in response to any message which contains an Auto-Submitted header field), where that field has any value other than "no".

As these emails being sent out by MailKit are generated by an automated process, adding the Auto-Submitted header field with a value of auto-generated would not be inaccurate, though I am uncertain if Exchange complies with RFC 3834, which is the server from which the majority of my automatic replies would be coming from.

I cannot change the ReplyTo field, since I still want users to be able to reply directly to these emails if they need assistance. So it is only the automatic replies which I would like suppressed. Even if 100% reliable suppression of auto-replies on emails I've sent is not feasible, suppressing the vast majority of them would still be a great help.

I made this to try to detect auto replies. If you want you can use it because all the code in it comes mostly from the internet

You probably need to translate my Dutch (Netherlands) comments.

internal class AutoReplyDetector
{
    #region Consts
    private const string AutoSubmittedHeader = "Auto-submitted";
    private const string XAutoreplyHeader = "X-Autoreply";
    private const string XAutoRespond = "X-Autorespond";
    private const string PrecedenceHeader = "Precedence";
    // private const string XMailerHeader = "X-Mailer";
    private const string XAutoResponseSuppressHeader = "X-Auto-Response-Suppress";
    private const string ListIdHeader = "List-Id";
    private const string ListUnsubscribeHeader = "List-Unsubscribe";
    private const string FeedbackIdHeader = "Feedback-ID";
    private const string XmsFBLHeader = "X-MSFBL";
    private const string XLoopHeader = "X-Loop";
    #endregion

    #region Private enum XAutoResponseSuppressHeaderValue
    /// <summary>
    ///     Mogelijke waarden voor <see cref="XAutoResponseSuppressHeader"/>
    /// </summary>
    /// <remarks>
    ///     Specifies whether a client or server application will fore go
    ///     sending automated replies in response to this message.
    /// </remarks>
    private enum XAutoResponseSuppressHeaderValue
    {
        /// <summary>
        ///     Suppress delivery reports from transport.
        /// </summary>
        DR = 0x00000001,

        /// <summary>
        ///     Suppress non-delivery reports from transport.
        /// </summary>
        NDR = 0x00000002,

        /// <summary>
        ///     Suppress read notifications from receiving client.
        /// </summary>
        RN = 0x00000004,

        /// <summary>
        ///     Suppress non-read notifications from receiving client.
        /// </summary>
        NRN = 0x00000008,
        
        /// <summary>
        ///     Suppress Out of Office (OOF) notifications.
        /// </summary>
        OOF = 0x00000010,

        /// <summary>
        ///     Suppress auto-reply messages other than OOF notifications.
        /// </summary>
        AutoReply = 0x00000020
    }
    #endregion

    #region Detect
    /// <summary>
    ///     Detecteert of een e-mail een automatisch antwoord is
    /// </summary>
    internal static AutoReplyDetectorResult Detect(MimeMessage message)
    {
        var headers = message.Headers;

        if (headers.Contains(AutoSubmittedHeader))
        {
            var value = headers[AutoSubmittedHeader];
            if (value.ToLowerInvariant() != "no")
                return new AutoReplyDetectorResult(true, $"Found header '{AutoSubmittedHeader}' with value '{value}'");
        }
        else if (headers.Contains(XAutoreplyHeader))
        {
            var value = headers[XAutoreplyHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{XAutoreplyHeader}' with value '{value}'");
        }
        else if (headers.Contains(XAutoRespond))
        {
            var value = headers[XAutoRespond];
            return new AutoReplyDetectorResult(true, $"Found header '{XAutoRespond}' with value '{value}'");
        }
        else if (headers.Contains(PrecedenceHeader))
        {
            var value = headers[PrecedenceHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{PrecedenceHeader}' with value '{value}'");
        }
        //else if (headers.Contains(XMailerHeader))
        //{
        //    var value = headers[XMailerHeader];
        //    return new AutoReplyDetectorResult(true, $"Found header '{XMailerHeader}' with value '{value}'");
        //}
        else if (headers.Contains(XAutoResponseSuppressHeader))
        {
            var value = headers[XAutoResponseSuppressHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{XAutoResponseSuppressHeader}' with value '{value}'");
        }
        else if (headers.Contains(ListIdHeader))
        {
            var value = headers[ListIdHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{ListIdHeader}' with value '{value}', this is probably an e-mail from a mailing list");
        }
        else if (headers.Contains(ListUnsubscribeHeader))
        {
            var value = headers[ListUnsubscribeHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{ListUnsubscribeHeader}' with value '{value}', this is probably an e-mail from a mailing list");
        }
        else if (headers.Contains(FeedbackIdHeader))
        {
            var value = headers[FeedbackIdHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{FeedbackIdHeader}' with value '{value}', this is probably an e-mail from a mailing list");
        }
        else if (headers.Contains(XmsFBLHeader))
        {
            var value = headers[XmsFBLHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{XmsFBLHeader}' with value '{value}', this is probably an e-mail from a mailing list");
        }
        else if (headers.Contains(XLoopHeader))
        {
            var value = headers[XLoopHeader];
            return new AutoReplyDetectorResult(true, $"Found header '{XLoopHeader}' with value '{value}', this is probably an e-mail from a mailing list");
        }

        return new AutoReplyDetectorResult(false, string.Empty);
    }
    #endregion
}
/// <summary>
///     Wordt gebruikt om de status van <see cref="AutoReplyDetector.Detect"/> te retourneren
/// </summary>
[DataContract(Name = "autoreplydetectorresult", Namespace = "")]
public class AutoReplyDetectorResult
{
    #region Properties
    /// <summary>
    ///     Retourneert <c>true</c> wanneer de <see cref="Email"/> waarschijnlijk
    ///     een automatisch antwoord is
    /// </summary>
    /// <remarks>
    ///     Gebruik <see cref="Reason"/> om te achterhalen waarom we denken dat het
    ///     een automatisch antwoord is
    /// </remarks>
    [DataMember(Name = "primarystatus", EmitDefaultValue = false)]
    public bool IsAutoReply { get; private set; }

    /// <summary>
    ///     De reden waarom het een automatisch antwoord is
    /// </summary>
    public string Reason { get; private set; }
    #endregion

    #region Constructor
    /// <summary>
    ///     Maakt dit object en zet alle benodigde properties
    /// </summary>
    /// <param name="isAutoReply"></param>
    /// <param name="reason"></param>
    internal AutoReplyDetectorResult(bool isAutoReply, string reason)
    {
        IsAutoReply = isAutoReply;
        Reason = reason;
    }
    #endregion
}

And the rest that slips through you need to detect with just looking for specific words in the subject of the e-mail and then in the body.

@mayka-mack

Unfortunately you are venturing into uncharted territory (at least as far as I've explored).

I just fired off your question to some folks on the Office365 team that have helped answer questions for me before (seems like a few are OOF until 2023-07-07 so it may take a few days to get a response). Hopefully they'll have an answer, but if not, your best bet might be a try-and-see approach to see if setting those headers work or not.

Failing that, @Sicos1977 's suggestion on trying to detect auto-response emails and filtering them out (or auto-deleting them) might be the next best option.

I'll keep you updated if I get any responses from the Office365 team.

@mayka-mack

Unfortunately you are venturing into uncharted territory (at least as far as I've explored).

I just fired off your question to some folks on the Office365 team that have helped answer questions for me before (seems like a few are OOF until 2023-07-07 so it may take a few days to get a response). Hopefully they'll have an answer, but if not, your best bet might be a try-and-see approach to see if setting those headers work or not.

Failing that, @Sicos1977 's suggestion on trying to detect auto-response emails and filtering them out (or auto-deleting them) might be the next best option.

I'll keep you updated if I get any responses from the Office365 team.

I'm working with e-mail traffic for the last 20 years and detecting 100% of all auto replies is just impossible. The best solution I found is what I posted. Detecting something like a soft or hard bouncer is much much easier. Wish it would also be that easy for auto replies :-)

@mayka-mack

I am being asked which types of auto-replies specifically you are trying to suppress. Can you clarify?

Thanks.

@mayka-mack

I am being asked which types of auto-replies specifically you are trying to suppress. Can you clarify?

Thanks.

I'm Ideally trying to suppress all auto-replies except for non-delivery reports. But if it's all-or-nothing, it'd be okay to suppress non-delivery reports as well.

Auto-Replies To Suppress:

  • Delivery reports (DR)
  • Read notifications (RN)
  • Non-read notifications (NRN)
  • Out of Office notifications (OOF)
  • Other auto-reply messages (AutoReply)

@mayka-mack

If you send messages via SMTP, delivery reports shouldn't happen unless you opt-in to receive them via the SMTP DSN feature (assuming we are talking about the same thing?). Or, you might be able to use DeliveryStatusNotification.Never if the SMTP server supports DSN. See this code snippet for how to do this: http://mimekit.net/docs/html/M_MailKit_Net_Smtp_SmtpClient_GetDeliveryStatusNotifications.htm (just use Never instead of Failure).

Likewise, for Read notifications, they should be opt-in only if you set a Disposition-Notification-To header in the message.

If you are getting them anyway, let me know, and I can pass that along as well.

If you send messages via SMTP, delivery reports shouldn't happen unless you opt-in

Well, in theory. But come to think of it, I'm pretty sure I've gotten them sometimes and I never enable this. Only in the "Failed to deliver" cases, though.

Still trying to get answers from Exchange/Outlook folks...

Still trying to get answers from Exchange/Outlook folks...

I appreciate all your help on this. No worries if you're not able to get a response. An official answer would be nice, but I can definitely play around with the X-Auto-Response-Suppress header and see if it suppresses most of what I need it to.

My email kept getting passed around and no one seemed to have an answer.

I'm just going to close this but I wish I could have gotten you a conclusive answer :(