Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suppressing Automatic Replies #938

Closed
ghost opened this issue Jul 5, 2023 · 15 comments
Closed

Suppressing Automatic Replies #938

ghost opened this issue Jul 5, 2023 · 15 comments
Labels
question A question about how to do something

Comments

@ghost
Copy link

ghost commented Jul 5, 2023

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");
@Sicos1977
Copy link

Sicos1977 commented Jul 5, 2023

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.

@ghost
Copy link
Author

ghost commented Jul 5, 2023

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.

@Sicos1977
Copy link

Sicos1977 commented Jul 5, 2023

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.

@jstedfast
Copy link
Owner

@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.

@jstedfast jstedfast added the question A question about how to do something label Jul 5, 2023
@Sicos1977
Copy link

@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 :-)

@jstedfast
Copy link
Owner

jstedfast commented Jul 6, 2023

@mayka-mack

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

Thanks.

@ghost
Copy link
Author

ghost commented Jul 6, 2023

@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)

@jstedfast
Copy link
Owner

@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.

@jstedfast
Copy link
Owner

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.

@jstedfast
Copy link
Owner

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

@ghost
Copy link
Author

ghost commented Jul 20, 2023

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.

@jstedfast
Copy link
Owner

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

@jstedfast
Copy link
Owner

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

@rklec
Copy link
Contributor

rklec commented Oct 2, 2024

rklec added a commit to rklec/MimeKit that referenced this issue Oct 2, 2024
…uto-Submitted

This was totally confusing for me why there are two header IDs in MimeKit with the quite very same header. I even suspected a bug or that it is just a legacy API-compatibility thing (one being deprecated, though not stated?).

After some research it turns out, the header is actually defined.
At least for the modern RFC3834 header the keywords are actually also defined by IANA: https://www.iana.org/assignments/auto-submitted-keywords/auto-submitted-keywords.xml (But you don't do mapping to an enum, so introducing this would change the API drastically and be a different/bigger task, so I thought for the code this may not be relevant.)

So to clarify this and ease searching around the web and decrease potential confusion for other developers, IMHO, it is a good idea to just directly point out the basic difference in the (code) doc, directly where you are using it.

For background, I am at jstedfast#938 (comment) here detecting auto-reply mails for which this header is very important.
@rklec