Sending a PDF attachment with a HTML+plain_text email in Python - python

I'm trying to send a PDF attachment with an email body that summarises the contents of the PDF file. The email body is in both HTML and plaintext.
I'm using the following code to build the email email message object:
#Part A
logging.debug(" Building standard email with HTML and Plain Text")
msg = MIMEMultipart("alternative")
msg.attach(MIMEText(email_obj.attachments["plain_text"], "plain", _charset="utf-8"))
msg.attach(MIMEText(email_obj.attachments["html_text"], "html", _charset="utf-8"))
#Part B
logging.debug(" Adding PDF report")
pdf_part = MIMEApplication(base64.decodestring(email_obj.attachments["pdf_report"]), "pdf")
pdf_part.add_header('Content-Disposition', 'attachment', filename="pdf_report.pdf")
logging.debug(" Attaching PDF report")
msg.attach(pdf_part)
My problem is that my email body disappears if I attach the PDF. If I comment out the code that attaches the PDF (Part B), the email body appears.
Unless I'm mistaken, it looks as though my PDF attachment is overwriting the email body.

Such a message should have a more complex structure. The message itself contains two "top-level" MIME parts and has content-type "multipart/mixed". The first of these MIME part has type of "multipart/alternative" with two subparts, one for plain text and another for HTML. The second of the main parts is PDF attachment
pdfAttachment = MIMEApplication(pdf, _subtype = "pdf")
pdfAttachment.add_header('content-disposition', 'attachment', filename = ('utf-8', '', 'payment.pdf'))
text = MIMEMultipart('alternative')
text.attach(MIMEText("Some plain text", "plain", _charset="utf-8"))
text.attach(MIMEText("<html><head>Some HTML text</head><body><h1>Some HTML Text</h1> Another line of text</body></html>", "html", _charset="utf-8"))
message = MIMEMultipart('mixed')
message.attach(text)
message.attach(pdfAttachment)
message['Subject'] = 'Test multipart message'
f = open("message.msg", "wb")
f.write(bytes(message.as_string(), 'utf-8'))
f.close()
You may try to open a "source view" of a message in your favorite mail program (mail user agent) and see yourself (Ctrl-U in Thunderbird)

Related

smtplib multiple attachments failed to be received

I wrote a code that create a PDF, and I wanted to send it with another file (still a .pdf) with a python code based on SMTPLIB library.
You can see str(names[i]) value for the receiver email, since is taken from a table and also the sending-process is managed with a for cycle, were the name of the just-created pdf is depending on the str(names[i]) value.
I'm trying to manage the following code, considering a two factor authentication in order to send the automated email via python, from a gmail-based email:
sender_email = "sender#gmail.com"
receiver_email = str(names[i])
password = input("Authentication code: ")
subject = "Title"
body = """Hi,
This is the body of the email
"""
attachments = ['file1'+str(names[i])+'.pdf', 'file2.pdf'] # list of attachments
# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email # For mass emails
# Add body to email
message.attach(MIMEText(body, "plain"))
if 'attachments' in globals() and len('attachments') > 0:
for filename in attachments:
f = filename
part = MIMEBase('application', "octet-stream")
part.set_payload( open(f,"rb").read() )
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
message.attach(part)
# Add header as key/value pair to attachment part
part.add_header("Content-Disposition",f"attachment; filename= {attachments}",)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, text)
Everything works just fine: PDF is created, the mail is sent and received but.... the attachments are not ok in non-gmail emails.
What I find in the attachment list in a Outlook mail are files (with no extention) called ['file1'+str(names[i])+'.pdf', 'file2.pdf'], and trying with different receivers gives the same result.
It seems like non-gmail servers do not load files in the proper manner, while the gmail server recognizes the overall process
I thought about writing a "multiserver" object in the last with condition, but I don't know how to do it.
Thank you all!
In simplified code you do this:
# block#1 The following is simplified ---
for filename in attachments:
part = ... # construct part, include file content
part.add_header('Content-Disposition', ...)
message.attach(part)
# block#2: The following is original code ----
# Add header as key/value pair to attachment part
part.add_header("Content-Disposition",f"attachment; filename= {attachments}",)
# Add attachment to message and convert message to string
message.attach(part)
Thus, you
in block#1 you first create a part for each file
in block#2 you then take the last part from block#1 and add another content-disposition header, with including the name list as name
in block#2 you then attach this part again to this message
Obviously, block#1 is all you need and block#2 just messes up everything. Remove it.

How to send a complete email using smtplib python

I am trying to send an email using Python smtplib.
My objective is to include the below info in email
Attachment file #works fine
Paste the contents of a table in message body #works fine
Write a few lines about the table (as text) in message body # not works. instead stores as an attachment
So, I tried the below code
message = MIMEMultipart()
message['Subject'] = 'For your review - files'
message['From'] = 'user2#org.com'
message['To'] = 'user1#org.com'
# code to paste table contents in outlook message window - works fine
body_content = output # this has the pretty table - html table
message.attach(MIMEText(body_content, "html"))
# code to paste the written text in outlook message window - not works. instead of writing the text in outlook body,it stores as an attachment
written_text = """\
Hi,
How are you?"""
message.attach(MIMEText(written_text, "plain"))
# code to attach an csv file to a outlook email - works fine
with open(filename, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
message.attach(part)
msg_body = message.as_string()
server = SMTP('internal.org.com', 2089)
server.sendmail(message['From'], message['To'], msg_body)
print("mail sent successfully")
server.quit()
The problem in my code is that it creates a text file (containing the message "Hi, How are you") and sends as an attachment?
But I want "Hi, How are you" as a text message in the main Outlook message window.
The immediate problem is that many email clients assume that text body parts after the first are attachments. You can experiment with adding an explicit Content-Disposition: inline to the part(s) you want rendered as part of the main message, but is there a reason these need to be separate body parts in the first place? Combining the text fragments into a single body part would perhaps make more sense here.
More fundamentally, your email code was written for an older Python version. 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 this code and start over with modern code from the Python email examples documentation.
from email.message import EmailMessage
message = EmailMessage()
message['Subject'] = 'For your review - files'
message['From'] = 'user2#org.com'
message['To'] = 'user1#org.com'
message.set_content(output, subtype="html")
written_text = """\
Hi,
How are you?"""
message.add_attachment(
written_text, subtype="plain",
disposition="inline")
with open(filename, "rb") as attachment:
message.add_attachment(
attachment.read(),
maintype="application", subtype="octet-stream",
filename=filename)
with SMTP('internal.org.com', 2089) as server:
server.send_message(message)
print("mail sent successfully")
server.quit()
If the final attachment is really a CSV file, specifying it as application/octet-stream is a bit misleading; the proper MIME type would be text/csv (see also What MIME type should I use for CSV?)

EML file as attachment is not downloading using IMAP in Python?

I am using IMAP library in python to read an email inbox which is working file and i am downloading all my attachment successfully but when any .eml file is coming as attachment i got an error, pease help me how to download an eml file coming as attachment.
In-order to download an attachment such as .png from an email, the payload needs to be decoded using: part.get_payload(decode=True).decode(). However, from the documentation:
If the message is a multipart and the decode flag is True, then None is returned.
The error you are seeing is caused because a .eml file is a multipart message. The parts consist of message/rfc822 at the top level which holds all the email's details. Beneath will be single part messages such as text/html which holds the email's text etc...
To download this text into an .html or .txt file you need to .walk() through the parts of the .eml file - like you are doing on the original email to download the .eml attachment.
Here is a snippet of my code:
if msg.is_multipart():
for part in msg.walk():
# extract content type of email
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
if "attachment" in content_disposition:
if content_type == "message/rfc822":
# walk through the .eml attachment parts:
for eml_part in part.walk():
# find the content type of each part:
content_type = eml_part.get_content_type()
if content_type == "text/html": # this type is not multipart
body = eml_part.get_payload(decode=True).decode() # get_payload() can be decoded
# can do what you need with the decoded body.
# in this case extract text and save to .txt or .html
else: .....
Maybe you need to use EML Parser?
You can find the manual for eml-parser here.
You can use it:
def _read(self):
"""Reads all emails and get attachments.
Returns:
Attachments.
"""
self.mail.list()
self.mail.select(self.select)
self.mail.uid('search', None, 'ALL')
self.uids = self.data[0].split()
self.content_length = len(self.uids)
self.attachments = []
for uid in self.uids:
self.result, self.email_data = self.mail.uid(
'fetch', uid, '(RFC822)')
self.raw_email = self.email_data[0][1]
self.raw_email_string = self.raw_email.decode('utf-8')
self.parsed_email = email.message_from_bytes(self.raw_email)
for part in self.parsed_email.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get_content_type() not in ['text/html', 'text/plain']:
self.attachments.append({
'name':
part.get_filename(),
'content_type':
part.get_content_type(),
'bytes':
part.get_payload(decode=True)
})
self.result = {'attachments': self.attachments}
return self.result
Try to use my high level imap lib:
https://github.com/ikvk/imap_tools
from imap_tools import MailBox, MailMessage
# get .eml files attached to email messages from INBOX
with MailBox('imap.mail.com').login('test#mail.com', 'password', 'INBOX') as mailbox:
for message in mailbox.fetch():
for att in message.attachments:
if '.eml' in att.filename:
print(att.filename, len(att.payload))
Also you can parse .eml in place - see lib examples:
https://github.com/ikvk/imap_tools/blob/master/examples/parse_eml_attachments.py

Send Excel file via Gmail API but attachment is corrupted

I was trying to get a python program send an attachment via Gmail.
I used the sample code found:
Sending email via gmail & python
The problem is when I sent Excel files like .xls, .xlsx, .xlsm to myself, I cannot open the attachments as they were corrupted, even though the original files are fine. But sending .csv works fine. The entire process does not pop up any warnings or error.
Question is: did oauth2.0 or Gmail or MIME mess up the attachment for some formats? And how can I tell the program upload and send attachment without modifying it?
Had similar issue.
The error is probably in encoding file into bytes.
My gmail still sends corrupted file, when I do want to send xlsx, but I managed to get correct email with xls format.
This is my code:
def create_message_with_excel_attachment(sender, to, subject, message_text, file):
message = MIMEMultipart()
message['to'] = to
message['from'] = sender
message['subject'] = subject
msg = MIMEText(message_text)
message.attach(msg)
part = MIMEBase('application', "vnd.ms-excel")
part.set_payload(open(file, "rb").read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment', filename=file)
message.attach(part)
raw = base64.urlsafe_b64encode(message.as_bytes())
raw = raw.decode()
return {'raw': raw}
def send_message(service, user_id, message):
message = (service.users().messages().send(userId=user_id, body=message).execute())
try:
print ('Message Id: %s' % message['id'])
return message
except:
print ('An error occurred:')

Python - Send HTML-formatted email via Outlook 2007/2010 and win32com

Is there a way to send HTML-formatted email using Python's win32com.client (which utilizes Outlook 2007/2010). The format I'm using now looks like this:
import win32com.client
olMailItem = 0x0
obj = win32com.client.Dispatch("Outlook.Application")
newMail = obj.CreateItem(olMailItem)
newMail.Subject = "the subject"
newMail.Body = "body text"
newMail.To = "recipient#example.com"
attachment1 = "c:\\mypic.jpg"
newMail.Attachments.Add(attachment1)
newMail.Send()
This will send an email using Outlook, sent from the currently authenticated user, to the specified recipient, with a subject, content, and attached image.
I want to be able to send an inline image, which can be achieved using an "Embedded" attachment, or simply to link to and image using HTML, or embed an image using HTML and a Base64-encoded image.
HTML is my preferred approach, but any HTML I add to the body is formatted and encoded as plain text (e.g. < becomes <). Is there a way to tell Outlook the body content is HTML and should be parsed as such?
This is the way to make the body in html format
newMail.HTMLBody = htmltext

Categories

Resources