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