Trying to use an Outlook signature through Python (using 3.10.6
for this example), however the images within the signature are not being displayed. Just shows the typical broken image with "The picture can't be displayed."
Here is what I am using to create the draft email, where full_path_body_content
is the html signature text that's been previously modified to use absolute paths for all its image refs.
from win32com.client import Dispatch
from lxml import html
outlook = Dispatch("outlook.application")
mail = outlook.CreateItem(0)
mail.To = recipients
mail.CC = cc_recipients
mail.BCC = bcc_recipients
mail.Subject = subject
root = html.fromstring(full_path_body_content)
for img_tag in root.xpath("//img"):
src = os.path.abspath(img_tag.get("src"))
if not src.startswith("http"):
attachment = mail.Attachments.Add(src)
cid = str(os.path.basename(src)).split(".")[0]
attachment.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001F", cid)
img_tag.set("src", f"cid:{cid}")
modified_body_content = html.tostring(root, method="html", encoding="unicode")
mail.HTMLBody = modified_body_content
for attachment_path in attachments:
mail.Attachments.Add(attachment_path)
mail.Save()
So this works fine with creating the draft, adding my attachments, and displaying the text, but the images are all broken. If I simply remove the line attachment.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001F", cid)
then I do see the images actually attaching to my email draft in Outlook, and if I open the attachment I see the expected image just fine. So this means the part of adding the files from a path is correct, its just assigning the files CID to my body doesn't seem to be working.
I can confirm from debugging though that the modified_body_content
does reference the CID images in the body. i.e. Within modified_body_content
I can see that an img
will have src="cid:image002"
which should be correct.
During debugging I have also confirmed that the attachments do have the property properly assigned by checking [attachment.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001F") for attachment in mail.Attachments]
which gives the following output for my images: ['image002', 'image004', 'image006', 'image008', 'image010']
.
So given that the images should attach fine, the CID is assigned properly for each image, and the body content properly references the CID in the src
, I am at a bit of a loss.
Only other thing I can mention is that when I remove the .SetProperty
line the images are attached as actual attachments I can see, and if I save the draft as HTML I can see the images in the files folder it creates. However when I do set the CID property, the images are not shown (as expected) but when I save the draft as HTML I do see it creates the files with appropriate width and height for the images I expect to see, but they're all black and 0 bytes in size. So maybe it isn't attaching them correctly, or maybe that's just expected output from embedded images.
Edit: Using OutSpy I was able to confirm the CID and attachments were properly linked.
Email signature I was using had VML formatting that my desktop application was trying to use over the img tags, whereas the web version must prefer img over VML. Never heard of VML before so didn't think much of it as I was trying to debug this. Ended up using regex:
re.sub(r'<!--\[if gte vml.*?-->', "", html_body_content, flags=re.DOTALL)
along with lxml
for img_tag in root.xpath("//img"):
if 'v:shapes' in img_tag.attrib:
del img_tag.attrib['v:shapes']
Which removes all the v:shapes
attributes in my images and all of the if statements that try to display the VML. Embedded images now displaying perfectly.
Takeaway for anyone in similar position: Keep it simple. Test out a simple e-mail before complaining that a complex one doesn't work.