0

I am writing an email system for my application. Just a simple email with an attachment (pdf file) that needs to be sent upon completing a step.

On every client that I can test (Geary on linux, Outlook on mobile, Outlook on web, our own email system using roundcube) my attachments comes through perfectly and we have no problems.

Except for the Outlook client on windows, our mails just get received without the attachment.

I've tried changing my attachment to .png , .jpg or even .docx and with all of the filetypes it is just the same problem as above.

I'm using this code:

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase

from email import encoders
import base64

FROM = "sender@mail.com"
data = request.data['data']
base64_string = data['file']
base64_string = base64_string.split(',')[1]
attachment_data = base64.b64decode(base64_string)

PASS = ""
SERVER = 'mail.ourhost.be'

TO = "receiver@mail.com"

msg = MIMEMultipart('alternative')
msg['Subject'] = "subject"
msg['From'] = FROM
msg['To'] = TO

html = f"""\
    <html style="font-family: Heebo;">
            my email content
    </html>
    """ 

part2 = MIMEText(html, 'html')
msg.attach(part2)


# Add the decoded attachment data to the email
part = MIMEBase('application', 'pdf')
part.set_payload((attachment_data))
encoders.encode_base64(part)
filename = f'name.pdf'
part.add_header('Content-Disposition', "attachment; filename= %s" % filename)
msg.attach(part)

Is there anything particular that I need to watch out for in Outlook on windows?

Eugene Astafiev
  • 47,483
  • 3
  • 24
  • 45
Yorbjörn
  • 356
  • 3
  • 21

1 Answers1

0

I also was having the problem, that part of my contacts did not receive the attachment.

My research

Doing some research I found this SO post: SMTPlib Attachments not received

By comparing the code snippets the only relevant difference that I could discern was, that in the question...

msg = MIMEMultipart('alternative')

... was used, while in the working response MIMEMultipart was called without arguments:

msg = MIMEMultipart()

According to the docs (https://docs.python.org/3/library/email.mime.html) this will default to _subtype='mixed'.

class email.mime.multipart.MIMEMultipart(_subtype='mixed', boundary=None, _subparts=None, *, policy=compat32, **_params)

According to Mail multipart/alternative vs multipart/mixed the type to chose from depends on the contents of your message.


Solution?

In your example you did not use an alternative plain text, so maybe "mixed" (the default) would then be the correct _subtype for MIMEMultipart?

msg = MIMEMultipart()

Please let me know whether this solves the issue for you.

Best regards!

P.S. Here my script. In reality I read in the parameters from a toml config file, as it is problematic to hardcode login information into a script. My example also has some basic logging and some functionality is split into different functions.

import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import make_msgid, formatdate
from pathlib import Path
import logging

# configure basic logging
logging.basicConfig(
    filename="send_mail.log",
    filemode="a",
    format="%(name)s - %(levelname)s - %(message)s",
)

def send_mail(sender_email, receiver_email, pdf_pathstring, email_plain_text="Add your message here."):
    """This is a wrapper function to send emails"""
    message = create_message(sender_email, receiver_email, pdf_pathstring, email_plain_text)
    send_smtp(message, sender_email, receiver_email)


def create_message(sender_email, receiver_email, pdf_pathstring, email_plain_text):
    """This function assembles the different parts of the email, so that it can then be sent by send_smtp"""
    # create a MIMEMultipart object
    message = MIMEMultipart()
    message["Subject"] = "Example_subject"
    message["From"] = sender_email
    message["To"] = receiver_email
    message["Message-ID"] = make_msgid()
    message["Date"] = formatdate()

    # Turn the email_plain_text into plain MIMEText objects and attach to message
    plaintext_part = MIMEText(email_plain_text, "plain")
    message.attach(plaintext_part)

    # Read in pdf
    pdf_path = Path(pdf_pathstring)
    filename = pdf_path.name

    # Open PDF file in binary mode
    with open(pdf_path, "rb") as attachment:
        # Add file as application/octet-stream
        # Email client can usually download this automatically as attachment
        attachment_part = MIMEBase("application", "octet-stream")
        attachment_part.set_payload(attachment.read())

    # Encode file in ASCII characters to send by email
    encoders.encode_base64(attachment_part)

    # Add header as key/value pair to attachment part
    attachment_part.add_header(
        "Content-Disposition",
        f"attachment; filename= {filename}",
    )

    # Add attachment_part to MIMEMultipart message
    message.attach(attachment_part)

    return message


def send_smtp(message, sender_email, receiver_email):
    """This function creates a smtp_object and sends the mail including the attachments in the message object"""
    sender_email = sender_email
    password = "************"

    smtp_object = smtplib.SMTP_SSL(exampleserver.ch, 465)
    smtp_object.ehlo()  # listen for server response
    smtp_object.login(sender_email, password)
    try:
        smtp_object.sendmail(sender_email, receiver_email, message.as_string())
        logging.warning(f"Message sent to {receiver_email}")
        print(f"Message sent to {receiver_email}")
    except:
        logging.exception(f"Couldn't sent mail to {receiver_email} because of an exception:")
        print(f"Couldn't sent mail to {receiver_email} because of an exception.")
    smtp_object.quit()  # terminate the connection
Ranudar
  • 35
  • 7