1

I am using python to generate and send email messages. I want the messages to include both

  • embedded images (this post shows how)
  • attachments

Currently, the code below does all of this, except that the attachments are not 100% showing up. In Outlook, there is no way to see them. In Gmail, when you see the mail in your inbox it seems to have no attachment, but when you open the mail, there is the attachment at the bottom.

How can I get the attachments to show up consistently?

Following is my current code. I'll also show below that a comparison between the header seem from my gmail inbox of an email sent with this, vs one where everything works as expected.

import smtplib
from os import getcwd, path
from config import mailer_settings

from email.message import EmailMessage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from email.utils import formatdate
from mimetypes import guess_type


def send_mail(send_from, send_to, subject, message, message_html="", file_loc: str = None, locs=[], cids=[],
              use_tls=True):
    """Compose and send email with provided info and attachments.

    Args:
        send_from (str): from name
        send_to (list[str]): to name(s)
        subject (str): message title
        message (str): message body
        message_html: message body with html
        file_loc (list[str]): path of file to be attached to email
        locs: list[str]: list of file locations for embedded images
        cids: list: list of generated (email.utils.make_msgid) cids for embedded images
        use_tls (bool): use TLS mode
    """

    # --- Method 1: can't get embedded pictures to work ---
    # msg = MIMEMultipart('alternative')
    # part1 = MIMEText(message, 'plain')
    # part2 = MIMEText(message_html, 'html')
    # msg.attach(part1)
    # msg.attach(part2)

    # --- Method 2 currently best ---
    msg = EmailMessage()
    msg.set_content(message)
    msg.add_alternative(message_html, subtype='html')

    # --- Method 3:
    # from (https://stackoverflow.com/questions/35925969/python-sent-mime-email-attachments-not-showing-up-in-mail-live)
    # not sure how to get it to work with embedded pictures, since MIMEImage is now deprecated ---
    # Create the root message and fill in the from, to, and subject headers
    b = False
    if b:
        msg = MIMEMultipart('related')
        msg.preamble = 'This is a multi-part message in MIME format.'

        # Encapsulate the plain and HTML versions of the message body in an
        # 'alternative' part, so message agents can decide which they want to display.
        msgAlternative = MIMEMultipart('alternative')
        msg.attach(msgAlternative)

        msgText = MIMEText('This is the alternative plain text message.')
        msgAlternative.attach(msgText)

        # We reference the image in the IMG SRC attribute by the ID we give it below
        msgText = MIMEText('<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>Nifty!', 'html')
        msgAlternative.attach(msgText)

        # This example assumes the image is in the current directory
        fp = open(getcwd() + '\\mail\\example_pdf.ong', 'rb')
        # :( MIMEImage is deprecated
        # msgImage = MIMEImage(fp.read())
        fp.close() 

        # Define the image's ID as referenced above
        # msgImage.add_header('Content-ID', '<image1>')
        # msg.attach(msgImage)

        # --- end Method 3 ---

    msg['From'] = send_from
    msg['To'] = send_to
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject

    # now open the image and attach it to the email
    for loc, cid in zip(locs, cids):
        with open(loc, 'rb') as img:

            # know the Content-Type of the image
            maintype, subtype = guess_type(img.name)[0].split('/')

            # attach it
            msg.get_payload()[1].add_related(img.read(), maintype=maintype, subtype=subtype, cid=cid)

    if file_loc:
        mimetype, encoding = guess_type(file_loc)
        mimetype = mimetype.split('/', 1)
        fp = open(file_loc, 'rb')
        attachment = MIMEBase(mimetype[0], mimetype[1])
        attachment.set_payload(fp.read())
        fp.close()
        encoders.encode_base64(attachment)
        attachment.add_header('Content-Disposition', 'attachment',
                              filename=path.basename(file_loc))
        msg.attach(attachment)

    server = mailer_settings["server"]
    port = mailer_settings["port"]
    username = mailer_settings["username"]
    password = mailer_settings["password"]

    smtp = smtplib.SMTP(server, port)
    if use_tls:
        smtp.starttls()
    smtp.login(username, password)
    smtp.sendmail(send_from, send_to, msg.as_string())
    print("message sent!")
    smtp.quit()

The Header in my emails:

--===============0780945104962264622==
Content-Type: application/pdf
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Ecoemballage - test_product_01a.pdf"


--===============0780945104962264622==--

Header from a Microsoft email that is showing it attachment like it should:

--=-FabH5jV6gYAblpCdlHoDfQ==
Content-Type: application/octet-stream; name=221885190882013.pdf
Content-Disposition: attachment; filename=221885190882013.pdf
Content-Transfer-Encoding: base64


--=-FabH5jV6gYAblpCdlHoDfQ==--
levraininjaneer
  • 1,157
  • 2
  • 18
  • 38

1 Answers1

0

I found the answer here.

The problem here is that you have directly attached a pre-built MIME part to a multipart/alternative message. It ends in an incorrect message that may be poorly processed by mail readers.

In the email.message interface, you should use the add_attachement method. It will handle the base64 encoding and will change the message into a multipart/mixed one:

To fix, I changed my code as follows:

if file_loc:
    fp = open(file_loc, 'rb')
    filename = path.basename(file_loc)
    # mimetype, encoding = guess_type(file_loc)
    # mimetype = mimetype.split('/', 1)
    # attachment = MIMEBase(mimetype[0], mimetype[1])
    # attachment.set_payload(fp.read())
    # fp.close()
    # encoders.encode_base64(attachment)
    # attachment.add_header('Content-Disposition', 'attachment',
    #                       filename=path.basename(file_loc))
    # msg.attach(attachment)
    msg.add_attachment(fp.read(), maintype='application',
                       subtype='octet-stream', filename=filename)
levraininjaneer
  • 1,157
  • 2
  • 18
  • 38