Sending email with rich text and embedded image using Python - python

I am learning how to use Python for automating emails, and am facing issues in creating a "clean" email.
Problem: I have a Google doc which has a certain text, followed by an image, followed by some text. I wish to use this as the content of an email, which I wish to send using Python
Approach: Based on what I read on SO and blogs, I used MIMEMultipart and broke my original doc into 3 parts: text, image, text and I downloaded the html versions for both the text files. Thereafter, I used the following code:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from string import Template
me = "myemail#gmail.com"
you = "youremail#gmail.com"
msg = MIMEMultipart()
msg['From'] = me
msg['To'] = you
msg['Subject'] = 'This is a test'
html1 = open('test_doc_part1.html')
part1 = MIMEText(html1.read(), 'html')
msg.attach(part1)
html1.close()
image_text = MIMEText('<img src="cid:image1">', 'html')
msg.attach(image_text)
fp = open('test_image.jpg', 'rb')
image = MIMEImage(fp.read())
fp.close()
image.add_header('Content-ID', '<image1>') # Define the image's ID as referenced in the HTML body above
msg.attach(image)
html2 = open('test_doc_part2.html')
part2 = MIMEText(html2.read(), 'html')
msg.attach(part2)
html2.close()
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(me, "password")
text = msg.as_string()
# Do text substitutions here
server.sendmail(from_addr= me, to_addrs= you, msg=text)
server.quit()
Issue: Depending on the text sources, the text of my email is either hidden under the image, or all/some of it is underlined/garbled. I'm using gmail and viewing this email in Google Chrome. The original content of the received email can be viewed here. Can someone point out where I'm incorrect?

Related

Text not showing when sending html gmail using python

Im trying to send gmail email using python. The email includes plain text and an html image to be shown in the email. However when i try sending the email, the text is not showing (only image is shown).
Below is the code:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
host='smtp.gmail.com'
port=587
username='sender#gmail.com'
password='mypassword'
from_email=username
to_list=['recipient#gmail.com']
email_conn=smtplib.SMTP(host,port)
email_conn.ehlo()
email_conn.starttls()
email_conn.login(username,password)
msg=MIMEMultipart('Alternative')
temp=MIMEMultipart('Alternative2')
msg['Subject']='Hello'
msg['From']=username
txt='Welcome home'
part1=MIMEText(txt,'plain')
msgText = MIMEText('<img src="cid:image1">', 'html')
temp.attach(msgText)
fp = open('/home/user/Pictures/image.jpg', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()
# Define the image's ID as referenced above
msgImage.add_header('Content-ID', '<image1>')
temp.attach(msgImage)
msg.attach(part1)
msg.attach(temp)
email_conn.sendmail(from_email,to_list,msg.as_string())
email_conn.quit()
The immediate error is that you are creating a invalid MIME part with type multipart/Alternative2. You seem to be confusing the type (which should be one out of a limited set of IANA-approved labels) with a unique identifier.
More fundamentally, you seem to be following some obsolete email guideline. The proper way to create a new message in Python 3.6+ is to use the (no longer very) new EmailMessage API.
Also, you will want to restructure your code so that the message creation is not mixed with the message sending. In the following, I have simply removed all the smtplib code; this also makes it easy for you to troubleshoot locally with print(msg.as_string()) instead of sending the message.
from email.message import EmailMessage
from email.utils import make_msgid
username = 'sender#gmail.com'
to_list = ['recipient#gmail.com']
msg = EmailMessage()
msg['Subject'] = 'Hello'
msg['From'] = username
# Need recipient!
msg['To'] = ', '.join(to_list)
msg.set_content('Welcome home')
image_id = make_msgid()
# Notice closing slash at the end of <img ... />
msg.add_alternative('<img src="%s" />' % image_id.strip('<>'), subtype='html')
with open('/home/user/Pictures/image.jpg', 'rb') as fp:
msg.get_payload()[1].add_related(
fp.read(), 'image', 'jpeg', cid=image_id)
This rather closely follows the "asparagus" example from the email examples in the documentation.
You would then go on to create an SMTP session and smtp.send_message(msg) rather than take the detour to separately and explicitly convert the message to a string you can pass to the legacy sendmail method; this is one of the many improvements in the new API.

Use EMailMessage to to send email with embedded image and multipart text & html read from file

I want to send a multi-part email (text and html) using the Python 3 EMailMessage class, which also has an embedded image. I have been able to get it working when using the MIME classes, but not with the EMailMessage class. The HTML mail shows up but there is no embedded image, with it being added as an attachment instead. Example code is shown below (there are also lines commented out from my various attempts):
# Send an HTML email with an embedded image and a plain text message for
# email clients that don't want to display the HTML.
#Import smtplib for the actual sending function
import smtplib, ssl, mimetypes, imghdr
# Import the email modules we'll need
from email.message import EmailMessage
from email.headerregistry import Address
from email.mime.image import MIMEImage
from email.utils import make_msgid
STR_FROM_DISPLAY_NAME = "From Display Name"
TEXTFILE = "example.txt"
HTMLFILE = "example.html"
IMGFILE = "logo.png"
STR_FROM = "from#example.com"
STR_TO = "to#example.com"
STR_SUBJECT = f"The contents of {TEXTFILE}"
msg = EmailMessage()
msg["Subject"] = STR_SUBJECT
# STR_FROM == the sender's email address
# STR_TO == the recipient's email address
msg["From"] = STR_FROM
msg["To"] = STR_TO
msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
# Open the plain text file whose name is in TEXTFILE for reading.
with open(TEXTFILE) as fp:
# Create a text/plain message
msg.set_content(fp.read())
# Add the html version. This converts the message into a multipart/alternative
# container, with the original text message as the first part and the new html
# message as the second part.
image_cid = make_msgid()[1:-1] # strip <>
with open(HTMLFILE) as fp:
msg.add_alternative(fp.read(), subtype="html")
#.format(cid=cid), subtype="html")
#maintype, subtype = mimetypes.guess_type(IMGFILE)[0].split('/', 1)
# Now add the related image to the html part.
#attachments={"image1": IMGFILE}
#for png_cid in attachments:
from email.mime.image import MIMEImage
with open(IMGFILE, "rb") as fp:
image_type = imghdr.what(fp.name)
print("image_type = ", image_type)
msgImage = MIMEImage(fp.read())
# Define the image's ID as referenced above
msgImage.add_header("Content-ID", "<image_cid>")
msgImage.add_header('X-Attachment-Id', IMGFILE)
msgImage.add_header("Content-Disposition", f"inline; filename={IMGFILE}")
# msg.attach(msgImage)
# Make the EmailMessage's payload `mixed`
#msg.get_payload()[1].make_mixed() # getting the second part because it is the HTML alternative
#msg.get_payload()[1].attach(msgImage)
msg.get_payload()[1].add_related(msgImage, "image", image_type, fp.name, cid=image_cid) #.format(cid=cid))
# logo = MIMEImage(img.read())
# logo.add_header("Content-ID", f"cid")
# logo.add_header('X-Attachment-Id', IMGFILE)
# logo['Content-Disposition'] = f"inline; filename=" IMGFILE
# msg.attach(logo)
# Make the EmailMessage's payload `mixed`
# msg.get_payload()[1].make_mixed() # getting the second part because it is the HTML alternative
# msg.get_payload()[1].attach(logo)
port = 465 # For starttls
smtp_server = "smtp.mail.server.com"
password = "Password"
# Create secure connection with server and send email
context = ssl.create_default_context()
# Send the message via our own SMTP server.
with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
server.login(STR_FROM, password)
server.send_message(msg)
Below is the line from the HTML file where I specify the image:
<img class="image_fix" src="cid:{image_cid}" alt="Logo" title="Logo" width="637" height="213" />
There are lots of examples where the HTML is specified in the python code, but I haven't been able to find any where the HTML is loaded from a file where an image is embedded.
I also want to attach a word .docx file to the email, and I have sort of managed this with MIME classes, but the attachment did not show the size when viewed in the e-mail client. I used the line:
MIMEBase("application", "octet-stream").
with it encoded as base64. I think that there is a "word"/"docx" MIME type, but again not sure how to do it.
Any ideas ?

Excel email comes in a different format unable to open Python pandas

The goal is to send an email with excel attachment.
I found an example online but not written for Excel format.
It sends attachment but not like typical excel spreadsheet, so I am unable to open it.
Is it something I can modify in a code in order to receive .xlsx file?
# libraries to be imported
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
fromaddr = "From#gmail.com"
toaddr = "To#gmail.com"
# instance of MIMEMultipart
msg = MIMEMultipart()
# storing the senders email address
msg['From'] = fromaddr
# storing the receivers email address
msg['To'] = toaddr
# storing the subject
msg['Subject'] = "Sending Attachement"
# string to store the body of the mail
body = "Hello, This is Oleg and my attached file"
# attach the body with the msg instance
msg.attach(MIMEText(body, 'plain'))
# open the file to be sent
filename = "FileName"
attachment = open("C:\\Mylocation\\FileName.xlsx", "rb")
# instance of MIMEBase and named as p
p = MIMEBase('application', 'octet-stream')
# To change the payload into encoded form
p.set_payload((attachment).read())
# encode into base64
encoders.encode_base64(p)
p.add_header('Content-Disposition', "attachment; filename= %s" % filename)
# attach the instance 'p' to instance 'msg'
msg.attach(p)
# creates SMTP session
s = smtplib.SMTP('smtp.gmail.com', 587)
# start TLS for security
s.starttls()
# Authentication
s.login(fromaddr, "password")
# Converts the Multipart msg into a string
text = msg.as_string()
# sending the mail
s.sendmail(fromaddr, toaddr, text)
# terminating the session
s.quit()
There is just a small error in your code! change your filename variable to "FileName.xlsx" instead of just "FileName"
I noticed that your file didn't have an extension, and since your filename variable didn't have an extension - that is how I quickly came to this conclusion. The documentation for add_header() seems to use the file extensions as well.

HTML tables not opening in Outlook (pd.to_html)

I am using pd.to_html for sending my pandas dataframe over email, the emails are opening fine in Gmail browser with all the tables in the email body. But they are not opening in Outlook all the tables are going as an HTML attachment in outlook.
Using the following code for converting my dataframe to HTML.
df_1 = df_1.to_html(index=False,header= True,border=4,justify = 'left',col_space =9)
from IPython.core.display import display, HTML
display(HTML(df_1))
This is how I am sending the Email:-
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
email_user = 'xyz#x.com'
email_password = '*********'
email_send = 'xyz#x.com'
subject = 'ABCDEF'
msg = MIMEMultipart()
msg['From'] = email_user
msg['To'] = email_send
msg['Subject'] = subject
body_1 = """ Hey All,
Please find the data:
"""
msg.attach(MIMEText(body_1,'plain'))
msg.attach(MIMEText(df_1,'html'))
text = msg.as_string()
server = smtplib.SMTP('smtp.gmail.com',587)
server.starttls()
server.login(email_user,email_password)
server.sendmail(email_user,email_send.split(','),text)
server.quit()
There are no error's email is opening in Gmail properly. But in outlook tables are coming as an attachment.
You can make a single attachment as html.That worked for me.
df_1 = df_1.to_html(index=False,header= True,border=4,justify = 'left',col_space =9)
mailBody= """<p> Hi All, <br><br>
Please find the data: </p> <br><br>
""" + df_1 + """<br> Thanks. """
msg.attach(MIMEText(mailBody,'html'))

Send HTML file contents via python email script

Looking for a way to send the contents of a HTML file that is generated once a day using this script below. Running into road blocks getting it to work. I can sent HTML and see it, just not sure how to print out the contents of the file and send it.
File Format is export_MM-DD-YY.html
Id rather have it display the contents of the HTML in the email not the HTML file.
#!/usr/bin/env python3
import smtplib
import config
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
filename = 'name_of_file'
# Read a file and encode it into base64 format
fo = open(filename, "rb")
filecontent = fo.read()
encodedcontent = base64.b64encode(filecontent) # base64
filename = os.path.basename(filename)
# me == my email address
# you == recipient's email address
me = "From#example.com"
you = "TO#example.com"
subject = 'Test Subject v5'
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = me
msg['To'] = you
# Create the body of the message (a plain-text and an HTML version).
text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttps://www.python.org"
html = """\
**INSERT HTML FILE CONTENTS HERE...**
"""
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)
# Send the message via local SMTP server.
server = smtplib.SMTP('smtp.gmail.com:587')
server.starttls()
server.login(config.EMAIL_ADDRESS, config.PASSWORD)
server.sendmail(me,you,msg.as_string())
server.quit()
So I think I got it working but im sure theres more code here than is needed (If anyone sees anything I can clean up?)
#!/usr/bin/env python3
import smtplib
import os
import config
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
raport_file = open('export.html','rb')
alert_msg = MIMEText(raport_file.read(),"html", "utf-8")
# me == my email address
# you == recipient's email address
me = "From#example.com"
you = "TO#example.com"
subject = 'Test Subject v5'
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = me
msg['To'] = you
# Create the body of the message (a plain-text and an HTML version).
text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttps://www.python.org"
html = """\
"""
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)
# Send the message via local SMTP server.
server = smtplib.SMTP('smtp.gmail.com:587')
server.starttls()
server.login(config.EMAIL_ADDRESS, config.PASSWORD)
server.sendmail(me,you,alert_msg.as_string())
server.quit()
You're really close. Three changes:
Don't open the html file in binary mode. Read the file directly into the html string
report_file = open('export.html')
html = report_file.read()
remove the subsequent assignment to html var
html = """\
"""
send the msg object as constructed
server.sendmail(me, you, msg.as_string())
That worked for me. Also keep in mind that by default gmail settings may not allow your script to send mail. If so you'll need to update your settings to allow "insecure" (meaning non-Google) apps to send mail.

Categories

Resources