11

I am trying to reply to an email using Python 3.4. The recipient of the email will be using Outlook (unfortunately) and it is important that Outlook recognizes the reply and displays the thread properly.

The code I currently have is:

def send_mail_multi(headers, text, msgHtml="", orig=None):
    """
    """

    msg = MIMEMultipart('mixed')
    # Create message container - the correct MIME type is multipart/alternative.
    body = MIMEMultipart('alternative')

    for k,v in headers.items():
        if isinstance(v, list):
            v = ', '.join(v)
        msg.add_header(k, v)

    # Attach parts into message container.
    # According to RFC 2046, the last part of a multipart message, in this case
    # the HTML message, is best and preferred.
    body.attach(MIMEText(text, 'plain'))
    if msgHtml != "": body.attach(MIMEText(msgHtml, 'html'))
    msg.attach(body)

    if orig is not None:
        msg.attach(MIMEMessage(get_reply_message(orig)))
        # Fix subject
        msg["Subject"] = "RE: "+orig["Subject"].replace("Re: ", "").replace("RE: ", "")
        msg['In-Reply-To'] = orig["Message-ID"]
        msg['References'] = orig["Message-ID"]+orig["References"].strip()
        msg['Thread-Topic'] = orig["Thread-Topic"]
        msg['Thread-Index'] = orig["Thread-Index"]
    
    send_it(msg['From'], msg['To'], msg)
  • The function get_reply_message is removing any attachments as in this answer.
  • send_it function sets the Message-ID header and uses the proper SMTP configuration. Then it calls smtplib.sendmail(fr, to, msg.as_string())
  • Outlook receives the email but does not recognize/display the thread. However, the thread seems like being an attachment to the message (probably caused by msg.attach(MIMEMessage(...))

Any ideas on how to do this? Have I missed any headers?

tripleee
  • 175,061
  • 34
  • 275
  • 318
urban
  • 5,392
  • 3
  • 19
  • 45
  • Caution, future visitors. The `email` module in the standard library was overhauled in Python 3.6 to be more logical, versatile, and succinct; new code should target the (no longer very) new `EmailMessage` API. Probably throw away any `MimeMultipart` code and start over with modern code from [the Python `email` examples documentation.](https://docs.python.org/3/library/email.examples.html) – tripleee Sep 22 '22 at 07:47
  • The general advice still holds; you want `In-Reply-To:` and `References:` for RFC compliance, and probably the undocumented Microsoft `Thread-xxx:` junk headers for Outlook, and wave a dead chicken at the crossroads at new moon. – tripleee Sep 22 '22 at 07:47

1 Answers1

13

Took me a while but the following seems working:

def send_mail_multi(headers, text, msgHtml="", orig=None):
    """
    """

    msg = MIMEMultipart('mixed')
    # Create message container - the correct MIME type is multipart/alternative.
    body = MIMEMultipart('alternative')

    for k,v in headers.items():
        if isinstance(v, list):
            v = ', '.join(v)
        msg.add_header(k, v)

    # Attach parts into message container.
    # According to RFC 2046, the last part of a multipart message, in this case
    # the HTML message, is best and preferred.
    if orig is not None:
        text, msgHtml2 = append_orig_text(text, msgHtml, orig, False)

        # Fix subject
        msg["Subject"] = "RE: "+orig["Subject"].replace("Re: ", "").replace("RE: ", "")
        msg['In-Reply-To'] = orig["Message-ID"]
        msg['References'] = orig["Message-ID"]#+orig["References"].strip()
        msg['Thread-Topic'] = orig["Thread-Topic"]
        msg['Thread-Index'] = orig["Thread-Index"]

    body.attach(MIMEText(text, 'plain'))
    if msgHtml != "": 
        body.attach(MIMEText(msgHtml2, 'html'))
    msg.attach(body)

    send_it(msg)


def append_orig_text(text, html, orig, google=False):
    """
    Append each part of the orig message into 2 new variables
    (html and text) and return them. Also, remove any 
    attachments. If google=True then the reply will be prefixed
    with ">". The last is not tested with html messages...
    """
    newhtml = ""
    newtext = ""

    for part in orig.walk():
        if (part.get('Content-Disposition')
            and part.get('Content-Disposition').startswith("attachment")):

            part.set_type("text/plain")
            part.set_payload("Attachment removed: %s (%s, %d bytes)"
                        %(part.get_filename(), 
                        part.get_content_type(), 
                        len(part.get_payload(decode=True))))
            del part["Content-Disposition"]
            del part["Content-Transfer-Encoding"]

        if part.get_content_type().startswith("text/plain"):
            newtext += "\n"
            newtext += part.get_payload(decode=False)
            if google:
                newtext = newtext.replace("\n","\n> ")

        elif part.get_content_type().startswith("text/html"):
            newhtml += "\n"
            newhtml += part.get_payload(decode=True).decode("utf-8")
            if google:
                newhtml = newhtml.replace("\n", "\n> ")

    if newhtml == "":
        newhtml = newtext.replace('\n', '<br/>')

    return (text+'\n\n'+newtext, html+'<br/>'+newhtml)

The code needs a little bit tiding up but as is Outlook displays it correctly (with Next/Previous options). There was no need to create From, Send, To, Subject headers by hand, appending the content worked.

Hope this saves someone else's time

urban
  • 5,392
  • 3
  • 19
  • 45
  • Just a small correction: If you want outlook to display the nice (blueish) email header, you have to build it (inside a

    ). This applies only to HTML emails...

    – urban Nov 27 '15 at 11:22
  • Can you explain what the difference is between your original code and this? – Brōtsyorfuzthrāx Sep 04 '18 at 09:01
  • 1
    It has been a while but from what I can figure out (again :) ) `append_orig_text` seems to be doing the difference. Instead of appending the original message, it isolates all the text/plain parts of the original into a new MIMEText which is appended. I wish I could provide more info but its been a while and I have nowhere to test nowadays... – urban Sep 04 '18 at 09:22