This question is really a continuation of this answer
https://stackoverflow.com/a/49098251/19308674. I'm trying to add multiple embedded images (not just one) to the email content.
I want to do it in a way that I loop through a list of images, in addition, there will be different text next to each image. Something like this for example as you can see in Weather Next 10 days I want to loop through images from a folder and next to each image there will be some different text as in the example.
from email.message import EmailMessage
from email.utils import make_msgid
import mimetypes
msg = EmailMessage()
# generic email headers
msg['Subject'] = 'Hello there'
msg['From'] = 'ABCD <abcd#example.com>'
msg['To'] = 'PQRS <pqrs#example.org>'
# set the plain text body
msg.set_content('This is a plain text body.')
# now create a Content-ID for the image
image_cid = make_msgid(domain='example.com')
# if `domain` argument isn't provided, it will
# use your computer's name
# set an alternative html body
msg.add_alternative("""\
<html>
<body>
<p>This is an HTML body.<br>
It also has an image.
</p>
<img src="cid:{image_cid}">
</body>
</html>
""".format(image_cid=image_cid[1:-1]), subtype='html')
# image_cid looks like <long.random.number#example.com>
# to use it as the img src, we don't need `<` or `>`
# so we use [1:-1] to strip them off
# now open the image and attach it to the email
with open('path/to/image.jpg', 'rb') as img:
# know the Content-Type of the image
maintype, subtype = mimetypes.guess_type(img.name)[0].split('/')
# attach it
msg.get_payload()[1].add_related(img.read(),
maintype=maintype,
subtype=subtype,
cid=image_cid)
# the message is ready now
# you can write it to a file
# or send it using smtplib
If I'm able to guess what you are trying to ask, the solution is simply to generate a unique cid for each image.
from email.message import EmailMessage
from email.utils import make_msgid
# import mimetypes
msg = EmailMessage()
msg["Subject"] = "Hello there"
msg["From"] = "ABCD <abcd#example.com>"
msg["To"] = "PQRS <pqrs#example.org>"
# create a Content-ID for each image
image_cid = [make_msgid(domain="example.com")[1:-1],
make_msgid(domain="example.com")[1:-1],
make_msgid(domain="example.com")[1:-1]]
msg.set_content("""\
<html>
<body>
<p>This is an HTML body.<br>
It also has three images.
</p>
<img src="cid:{image_cid[0]}"><br/>
<img src="cid:{image_cid[1]}"><br/>
<img src="cid:{image_cid[2]}">
</body>
</html>
""".format(image_cid=image_cid), subtype='html')
for idx, imgtup in enumerate([
("path/to/first.jpg", "jpeg"),
("file/name/of/second.png", "png"),
("path/to/third.gif", "gif")]):
imgfile, imgtype = imgtup
with open(imgfile, "rb") as img:
msg.add_related(
img.read(),
maintype="image",
subtype=imgtype,
cid=f"<{image_cid[idx]}>")
# The message is ready now.
# You can write it to a file
# or send it using smtplib
Kudos for using the modern EmailMessage API; we still see way too many questions which blindly copy/paste the old API from Python <= 3.5 with MIMEMultipart etc etc.
I took out the mimetypes image format quessing logic in favor of spelling out the type of each image in the code. If you need Python to guess, you know how to do that, but for a small static list of images, it seems to make more sense to just specify each, and avoid the overhead as well as the unlikely but still not impossible problem of having the heuristics guess wrong.
I'm guessing your images will all use the same format, and so you could actually simply hardcode subtype="png" or whatever.
It should hopefully be obvious how to add more per-image information into the loop over image tuples, though if your needs go beyond the trivial, you'll probably want to encapsulate the image and its various attributes into a simple class.
Your message apparently makes no sense for a recipient who cannot access the HTML part, so I took out the bogus text/plain part you had. You were effectively sending a different message entirely to recipients whose preference is to view plain text over HTML; if that was genuinely your intent, please stop it. If you are unable to provide the same information in the plain text version as in the HTML version, at least don't make it look to those recipients like you had nothing of importance to say in the first place.
Tangentially, please don't fake email addresses of domains you don't own. You will end up tipping off the spammers and have them trying to send unsolicited messages to an innocent third party. Always use IANA-reserved domains like example.com, example.org etc which are guaranteed to never exist in reality. I edited your question to fix this.
Related
I have a message in HTML format. I need to reply to this email, keeping all the pictures from the previous email.
I have all the pictures from the email saved, and now I need to add them back. How to do it?
exchabgelib has instructions for this:
from exchangelib import HTMLBody
message = Message()
logo_filename = 'logo.png'
with open(logo_filename, 'rb') as f:
my_logo = FileAttachment(
name=logo_filename, content=f.read(), is_inline=True,
content_id=logo_filename
)
message.attach(my_logo)
# Most email systems
message.body = HTMLBody(
'<html><body>Hello logo: <img src="cid:%s"></body></html>' % logo_filename
)
If the new email must also be in HTML format, then attach the images one by one and add an <img> tag for each image you have attached. Otherwise, just attach the images and compose the email in plain text.
I'm sending emails using smtplib and I'm generating an email signature that has embedded images. I used https://www.base64-image.de/ to convert several images to base64 strings, and I'm using those encoded strings like this, as part of a MIMEMultipart('mixed') message:
body = """\
<html>
...
<img src="%s" width = "32" height = "32">
<img src="%s" width = "32" height = "32">
<img src="%s" width = "32" height = "32">
...
</html>""" % (encoded_string1, encoded_string2, encoded_string3)
Example of what the string variables look like:
encoded_string1 = 'data:image/png;base64,iVBORw...'
Everything in the email is sent and formatted correctly. The images are in the correct places and are the correct size. But the images in the email are blank. There is just a border around where the image should be.
I can't figure out why this is happening. https://codebeautify.org/base64-to-image-converter can decode base64 strings to an image, and I've tested my base64 strings to make sure they're correct, which they are.
If I use a website where the images are saved as the src in the img tag, it shows up correctly in the email. I don't want to do this as I don't have control over the site that hosts the images, and I don't want to create my own site to host the images if I can avoid it.
Does anyone know why they're showing up as blank in the email once it's sent? Thanks!
May be problem with client-side support
Send a base64 image in HTML email
I am generating html files using elementtree.ElementTree.dump on an Element. The files look ok in all browsers, and the underlying code within the files looks fine (no unclosed brackets or anything).
When I send an email to Outlook 2010 via smtplib, I am seeing weird formatting issues. These issues will be 100% repeatable, so the issue is logical. Here is an example:
<table b="" order="1">
That is from the source code of a HTML email I sent myself. It is correctly written as:
<table border="1">
within the original source code.
If in Outlook I write a HTML email using the original HTML as source, it correctly formats. (New email-attach html file->insert as text)
Is the issue going to be Outlook or Python? The function I used for reading the html file and sending is below.
def email_Report(mailOptions):
reportName = time.strftime("%Y%m%d.%H%M") + ".html"
ElementTree(mailOptions['report']).write("/home/%s/%s" %(mailOptions['username'],reportName))
#Set sender and receiver to the user building the report.
mailaddr = '%s#acme.com' %(mailOptions['username'])
#Access the report file. Added binary in case we ever use code on Windows
filename = "/home/%s/%s" % (mailOptions['username'], reportName)
open_file = open(filename, 'rb')
emsg = MIMEText(open_file.read(), 'html')
open_file.close()
emsg['Subject'] = "Report for %s generated by %s %s" % (mailOptions['zone'], mailOptions['username'], time.strftime("%d%m%Y-%H%M"))
emsg['To'] = mailaddr
emsg['From'] = mailaddr
#Hostname can be a parameter to SMTP method if localhost isn't listening
sc = smtplib.SMTP()
sc.connect()
sc.sendmail(mailaddr, mailaddr, emsg.as_string())
sc.close()
return
The HTML is extremely simple. No CSS, no title or head tags etc. Just html->body->table->tr->th->(newrow)->td->td etc. Could I have overlooked something like encoding/escaping? Do I have to use mime multipart? I am using Python 2.4.3 and can't use any module that didn't come stock.
Are you sure you're not running into the 990 character limit for mail servers as per
workaround for the 990 character limitation for email mailservers
I am emailing the content of a text file "gerrit.txt" # http://pastie.org/8289257 in outlook using the below code,
however after the email is sent when I look at the source code( #http://pastie.org/8289379) of the email in outlook ,i see unnecessary
exclamation markds(!)'s in the code which is messing up the output, can anyone provide inputs on why is it so and how to avoid this ?
from email.mime.text import MIMEText
from smtplib import SMTP
def email (body,subject):
msg = MIMEText("%s" % body, 'html')
msg['Content-Type'] = "text/html; charset=UTF8"
msg['Subject'] = subject
s = SMTP('localhost',25)
s.sendmail('userid#company.com', ['userid2#company.com'],msg=msg.as_string())
def main ():
# open gerrit.txt and read the content into body
with open('gerrit.txt', 'r') as f:
body = f.read()
subject = "test email"
email(body,subject)
print "Done"
if __name__ == '__main__':
main()
Some info available here: http://bugs.python.org/issue6327
Note that mailservers have a 990-character limit on each line
contained within an email message. If an email message is sent that
contains lines longer than 990-characters, those lines will be
subdivided by additional line ending characters, which can cause
corruption in the email message, particularly for HTML content. To
prevent this from occurring, add your own line-ending characters at
appropriate locations within the email message to ensure that no lines
are longer than 990 characters.
I think you must split your html to some lines. You can use textwrap.wrap method.
adding a '\n' in between my html string , some random 20 characters before "!" was appearing solved my problem
I also faced the same issue, Its because outlook doesn't support line more than 990 characters it starts giving below issues.
Nested tables
Color change of column heading
Adding unwanted ! marks .
Here is solution for the same.
if you are adding for single line you can add
"line[:40]" + \r\n + "line[40:]".
If you are forming a table then you can put the same in loop like
"<td>" + line[j][:40]+"\r\n"+line[j][40:] + "</td>"
In my case the html is being constructed outside of the python script and is passed in as an argument. I added line breaks after each html tag within the python script which resolved my issue:
import re
result_html = re.sub(">", ">\n", html_body)
My bad. Postmark does not support inline images apparently. Solved by changing smtp-mail provider.
I'm trying to send e-mails with TurboMail using pylons.
Everything works fine, except for using embedded images in html-content. It seems that the Content-ID header for each image is being lost somewhere along the way.
This is my code:
def sendMail(to,subject,html_content,plain_content,images):
from turbomail import Message as Mail
mail = Mail(to=to,subject=subject)
mail.plain = plain_content
mail.rich = html_content
for cid,path in images.iteritems():
mail.embed(path,cid)
mail.send()
In my tests the html content is:
<html>
<header/>
<body>
<h1>Send images using TurboMail</h1>
<img src="cid:img0" />
</body>
</html>
And the images dict:
{"img0":"path/to/img0"}
When you pass in both a filename and a cid, TurboMail ignores the cid and uses the basename of the file instead. I suspect your filenames have extensions and your cids do not:
{"img0":"path/to/img0.png"}
If so, the images are embedded with a cid of img0.png instead.
You could pass in an open image file instead; TurboMail will then not ignore the name:
def sendMail(to,subject,html_content,plain_content,images):
from turbomail import Message as Mail
mail = Mail(to=to,subject=subject)
mail.plain = plain_content
mail.rich = html_content
for cid,path in images.iteritems():
mail.embed(open(path, 'rb'), cid)
mail.send()
I'd use marrow.mailer instead; it's the new name for the same package but the .embed method has been made a little saner in it's handling of embedded images and cids.
an earlier revision of this answer had marrow and TurboMail confused, referring to the marrow .embed signature instead.
Apparently, Postmarkapp does not support inline images.