Question

Here is my code:

FROM = ''
TO = ''
SMTPSERVER = ''
MYEMAILPASSWORD = ""

import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.mime.base import MIMEBase
from email import encoders


def physicallySend(listOfAttachments):
    msg = MIMEMultipart('alternative')
    msg['Subject'] = "Testing"
    msg['From'] = FROM
    msg['To'] = TO

    textPart = MIMEText("Hello. I should be able to see this body.", 'plain')
    msg.attach(textPart)
    for attachmentFilename in listOfAttachments:
        fp = open(attachmentFilename, 'rb')
        file1 = MIMEBase('application','csv')
        file1.set_payload(fp.read())
        fp.close()
        encoders.encode_base64(file1)
        file1.add_header('Content-Disposition','attachment;filename=%s' % attachmentFilename)
        msg.attach(file1)

    server = smtplib.SMTP(SMTPSERVER)
    server.ehlo()
    server.starttls()
    server.login(FROM, MYEMAILPASSWORD)
    server.sendmail(FROM, to, msg.as_string())
    server.quit()
    return True


physicallySend(["testfile.csv"])

While I can see the text body fine on Gmail and Outlook, however on my iPhone (6.1.3) I only see the attachment, and not the body.

Was it helpful?

Solution

I found my solution in this comment: How do I send attachments using SMTP?

My first line should have been

msg = MIMEMultipart('mixed')

rather than 'alternative'.

OTHER TIPS

For ones who seek the full solution to the question, that will work as intended on all email clients including iOS default email app.

When to use multipart/mixed

from the RFC

The primary subtype for multipart, "mixed", is intended for use when the body parts are independent and intended to be displayed serially. Any multipart subtypes that an implementation does not recognize should be treated as being of subtype "mixed".

so multipart/mixed type should be used when all of the parts of the message (text/html, text/plain, image/png etc.) are equally important and all should be displayed to the user

this means that if you attach text/html to the message and also text/plain as a fallback to html, both of them will be shown to the user one over the other which doesn't make any sense

When to use multipart/alternative

from the RFC

In particular, each of the parts is an "alternative" version of the same information. User agents should recognize that the content of the various parts are interchangeable. The user agent should either choose the "best" type based on the user's environment and preferences, or offer the user the available alternatives. In general, choosing the best type means displaying only the LAST part that can be displayed. This may be used, for example, to send mail in a fancy text format in such a way that it can easily be displayed anywhere

This means that whatever you attach to the multipart/alternative message can be treated as the same value but in different forms.

Why is that important?

if after the textual parts, you attach an image (image/png, image/jpeg) to the multipart/alternative message it can be treated as equally important as the message itself thus the only thing that the user will see is the image - without the text/html nor text/plain parts.
This is not true for most of nowadays clients - they are smart enough to know what you mean, but the one that still does this is iOS default Mail app - you don't want to confuse those users.

Whats the correct way to do it then?

After the explanation this will make much more sense now

# keeps the textual and attachment parts separately
root_msg = MIMEMultipart('mixed')  

root_msg['Subject'] = email.subject
root_msg['From'] = self._format_from_header()
root_msg['To'] = self._format_addrs(recipients=email.recipients)

alter_msg = MIMEMultipart('alternative')

plain_text = MIMEText('some text', 'plain', 'UTF-8')
html = MIMEText('<strong>some text</strong>', 'html')  

alter_msg.attach(plain_text)
alter_msg.attach(html)

# now the alternative message (textual)
# is just a part of the root mixed message
root_msg.attach(alter_msg)

img_path = '...'
filename = os.path.basename(img_path)
with open(img_path, 'rb') as f:
    attachment = MIMEImage(f.read(), filename=filename)          
    attachment.add_header('Content-Disposition', 'attachment', filename=filename)
    # now the images (attachments) will be shown alongside 
    # either plain or html message and not instead of them
    root_msg.attach(attachment)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top