0

I am trying to send an email with images in the body of the email.

I am able to send an email with no images or with images as an attachment but I'm unable to send with the image displayed in the email.

Below is what I have done so far:

from email.encoders import encode_base64
from email.mime.base import MIMEBase
from PIL import Image
from io import BytesIO
from smtplib import SMTPException
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from smtplib import SMTP
import smtplib


msg = MIMEMultipart("related")
msg['Subject'] = subject #time
msg['From'] = from_addr
msg['To'] = to_addr

html_output = "your html here"

msg.attach(MIMEText(html_output, "html"))

#plots is a dictionary of images

for image_name, image_location in plots.items():
   img = Image.open(BytesIO(image_location))
   msg.attach(img)
        
        
smtplib.SMTP(host).sendmail(from_addr, to_addr, msg.as_string())

I get an error saying the below in Databricks

/usr/lib/python3.8/email/generator.py in _handle_multipart(self, msg)
    274             s = self._new_buffer()
    275             g = self.clone(s)
--> 276             g.flatten(part, unixfrom=False, linesep=self._NL)
    277             msgtexts.append(s.getvalue())
    278         # BAW: What about boundaries that are wrapped in double-quotes?

/usr/lib/python3.8/email/generator.py in flatten(self, msg, unixfrom, linesep)
    105         # they are processed by this code.
    106         old_gen_policy = self.policy
--> 107         old_msg_policy = msg.policy
    108         try:
    109             self.policy = policy

/databricks/python/lib/python3.8/site-packages/PIL/Image.py in __getattr__(self, name)
    539             )
    540             return self._category
--> 541         raise AttributeError(name)
    542 
    543     @property

AttributeError: policy

What am I doing incorrectly?

Mikee
  • 783
  • 1
  • 6
  • 18
  • It's not clear why you are using PIL or what the error with that is; but e.g. https://stackoverflow.com/questions/72777873/how-to-add-multiple-embedded-images-to-an-email-in-python demonstrates how to embed images without it. – tripleee Jul 15 '22 at 14:13
  • I guess the immediate problem is that you need to specify the type of the attachment when you add one, but see my answer for a more thorough refactoring. – tripleee Jul 16 '22 at 04:46

2 Answers2

0

I can't tell you what's wrong with your PIL call, but you should not need PIL here at all anyway. Simply attach the image binaries directly.

Incidentally, it looks like 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. The "asparagus" example shows how to link to an image from HTML, and the "family reunion" example shows how to attach a sequence of images. Here's an attempt to synthesize the two.

from email.message import EmailMessage
from email.utils import make_msgid
import smtplib


imgs = []

for image_name, image_location in plots.items():
    with open(image_location, "rb") as img:
        image_data = img.read()
    imgs.append({
        "name": image_name,
        "data": image_data,
        "cid": make_msgid().strip("<>")
    })

html = """\
<html><head><title>Plots</title></head>
 <body>
  <h1>Your Plots</h1>
  <p>Dear Victim,</p>
  <p>Here are today's plots.
   <ul>
"""
for img in imgs:
    html += f'    <li><img src="cid:{img["cid"]}" alt="{img["name"]}" /></li>\n'
html += """\
   </ul>
  </p>
  <p>Plot to take over the world!</p>
 </body>
</html>
"""

msg = EmailMessage()
msg['Subject'] = subject #time
msg['From'] = from_addr
msg['To'] = to_addr

msg.set_content(html, subtype="html")

for img in imgs:
    msg.add_related(
        img["data"], "image", "png",
        cid=f'<{img["cid"]}>')

with smtplib.SMTP(host) as s:
    s.send_message(msg)

You'll obviously need to adapt the HTML to your needs, and review some other assumptions in the code (your example had a couple of uninitialized variables which I simply copied, and I hardcoded my guess that your images will all be PNG ones).

The copying of data from plots to imgs is slightly unattractive; perhaps you could simply add a CID to the plots structure. I didn't want to wreck it in case you have other code which needs it to be in a particular way.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • To drive the conversation a bit further, I used a PIL because I was getting an error of "embedded null byte" and this assisted me to overcome it. In your answer posted, same error comes up. The error comes up at "with open(image_location, "rb") as img:" I accepted this because of the working solution you have posted at https://stackoverflow.com/questions/72777873/how-to-add-multiple-embedded-images-to-an-email-in-python/72841106?noredirect=1#comment128935527_72841106 and because it's likely a small fix from you. – Mikee Jul 16 '22 at 13:12
  • Where exactly do you get "embedded null byte"? – tripleee Jul 16 '22 at 13:15
  • The error comes up at "with open(image_location, "rb") as img:" – Mikee Jul 16 '22 at 13:20
  • Does `image_location` contain a null byte? Opening in binary mode (`"rb"`) should work fine with null bytes in the file data, but if the file _name_ contains a null, that's an error in the code which created this variable. – tripleee Jul 16 '22 at 13:23
  • Don't think so because I only have 2 keys and 2 values in the plots dictionary so I can quickly confirm there's no null byte in image_location – Mikee Jul 16 '22 at 13:31
-1

You can customize your email in the html body already importing your image in <img src="image.jpg">

Se7e
  • 11
  • 2