My goal is to add an image (from local .png file) to an Outlook MailItem using Python.
I have read answers to similar questions on SO (mostly using VBA), but the issue I have is that the mail looks fine (I can see the image) before I send it (and when I look in my Sent Items folder), but when it is received at the other end, the image is broken.
If I manually insert the image in a message (using Insert / Picture / From this Device from the Outlook menu) the email arrives fine, with the image displayed.
Here is the basic code I am using (based mostly on VBA examples):
import win32com.client as wc
def main():
outlook = wc.Dispatch("Outlook.Application")
#Create a new mail item
msg = outlook.CreateItem(0)
#Set some properties
msg.BodyFormat = 2
msg.Subject = 'Here is my chart'
msg.Recipients.Add('xxx#yyy.com')
#Add an attachment, with position 0
imageFile = 'C:\\Temp\\myplot.png'
ats = msg.Attachments
att = ats.Add(imageFile,1,0)
#Set the HTMLBody
msg.HTMLBody = '<img src="cid:{0:}" width=200 height=200>'.format(att.FileName)
#Send
msg.Send()
main()
This is what I see in my Sent Items folder:
The HTML in my sent item (using View Source) is this:
<img src="cid:myplot.png" width=200 height=200>
But this is what arrives:
The HTML in the body at the destination is this:
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
<img src="cid:AD1B7014D243624CA83F873A11053F18#GBRP123.PROD.OUTLOOK.COM" width="200" height="200">
</body>
</html>
When I manually insert the image, the received HTML is much longer (so I won't include it all here), but the 'img' tag is here (and this matches what is in the Sent Items folder):
<img width="614" height="461" style="width:6.3958in;height:4.802in" id="Picture_x0020_1" src="cid:image001.png#01D736CD.72206440" alt="Chart, line chart Description automatically generated">
I would appreciate any insights on this! (Sadly Outlook won't let me record a macro to see what the manual process is doing). I have seen one example (from C#) where the SetProperty() method is used on the MailItem to add other items that are not in the Outlook object model (schema, context id), together with a newly created Guid, but is this really necessary?
Using Microsoft 365, Outlook version 2105, Beta Channel
Create an attachment and set the PR_ATTACH_CONTENT_ID property (DASL name "http://schemas.microsoft.com/mapi/proptag/0x3712001F") using Attachment.PropertyAccessor.SetProperty.
Your HTML body (MailItem.HTMLBody property) would then need to reference that image attachment through the cid:
img src="cid:xyz"
where xyz is the value of the PR_ATTACH_CONTENT_ID property.
Look at an existing message with OutlookSpy (I am its author) - click IMessage button.
attachment = mailitem.Attachments.Add("c:\temp\MyPicture.jpg");
attachment.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001F", "MyId1");
mailitem.HTMLBody = "<html><body>Test image <img src=""cid:MyId1""></body></html>";
Related
I'm currently using PYWIN32 to send out automated e-mails with Outlook, using Python.
I am able to send e-mails using Python right now, but it's still not the way I want to.
In the attached image there is an CSV-file, which I'm currently using for sending out my e-mails in an automated way. As you can see it has different columns, such as Manager, Manager-Email, et cetera.
This is the CSV-file:
What I want to do:
First of All, I want to use the e-mail-column, iterate over it and use those e-mail by putting them in the MAIL.TO attribute which I have in my python code. (This is shown in the code as well).
This allows me to send multiple e-mails to different e-mail addresses. There is one thing, some e-mails are the same, and I don't want to send out multiple e-mails to the same manager.
How can I only get the unique e-mails out of this column and use them in to the Send-To attribute in my Python code.
Furthermore, as you can see in the CSV-file, besides the Manager-Email column there are also other columns which have different rows, such as: Model, Serial Number, Description, etc. So for example:
I want to include these rows, corresponding to the Manager-Email columns row in the HTML.BODY attribute in my Python code.
As you can see some e-mails in the Manager-Email have multiple other rows-cells which correspond to that e-mail address, how can I include these rows in the HTML.BODY to the specific e-mail address? (see the 2nd example.)
So for example the e-mail: Joost.Verkaecke#example.com should receive the first to rows of the columns:
Description
SerialNumber
Model
Employee
dafehvlpp21437
5N8KVP2
Latitude 5480
James Hover
dafehvwpp30725
3Z0L2J2
OptiPlex 3040
James Hover
Because these rows correspond with his e-mail-address.
As last, I want to convert the rows which are send to the e-mail, in to a HTML-table. That would be great.
And by this proces I want to send out the e-mails to the different e-mail-addresses.
HERE IS MY CODE
import win32com.client
import pandas as pd
import numpy as np
import csv
import os
import time
def SendEmail():
global Outlook
global df
df = pd.read_csv(os.path.join(os.getcwd(),'test.csv'),delimiter = ',')
unique_emails = df['ManagerEmail'].unique()
uniquermanagername = df['Manager'].unique()
Outlook = win32com.client.Dispatch('outlook.application')
for OutlookAccount in Outlook.Session.Accounts:
if OutlookAccount.DisplayName == '':
print("Sending e-mail from:", OutlookAccount)
for email in unique_emails:
print(email)
Mail = Outlook.CreateItem(0)
Mail._oleobj_.Invoke(*(64209, 0, 8, 0, OutlookAccount))
Mail.HTMLBody = '''\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
</head>
<body>
<p> 'Geachte,' ''' + str(df['Manager'].unique()) + ''' </p>
</body>
</html>
'''
Mail.Display(False)
Mail.Save()
time.sleep(1)
SendEmail()
I explained above already what I tried to do.
There are plenty of answers on how to embed images into emails in python. What I can't figure out is how to embed an image that's clickable and leads you to a site.
Sending Multipart html emails which contain embedded images
I pretty much followed the first comment of this exactly. I just need to figure out how to add a link to that image
this is what I followed
msgRoot = MIMEMultipart('related')
fp = open('test.jpg', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()
msgImage.add_header('Content-ID', "<image1>")
msgRoot.attach(msgImage)
Obviously this just embeds an image, but I need it to embed a linked image!
The example you linked were properly formatted HTML Emails where add_alternative() was used to supply the HTML portion of the email. You excluded this in what you've written. If you include an actual HTML body for your email, then all you need to do is wrap the image in an anchor(link) element with the url you're trying to link to.
(Adapted from your linked question)
msg.add_alternative("""\
<html>
<body>
<p>Click the Image below to visit our site!</p>
<img src="cid:{image_cid}">
</body>
</html>
""".format(image_cid=image_cid[1:-1]), subtype='html')
Edit
Don't have Python 2 around to test, but the following code from the Accepted Answer on that same thread (which was indicated as being Python 2.x compatible) presumably should work.
msgAlternative = MIMEMultipart('alternative')
msgRoot.attach(msgAlternative)
msgText = MIMEText('<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>Nifty!', 'html')
msgAlternative.attach(msgText)
Again, the point here being that you embed the image via html, which also allows you apply anchor tags (and basically any other styling you would like).
I'm trying to show some badge images I made for a RANK APP I've been working for. It's 10 images that should be shown specific for each driver.
I'm not an expert on coding, but I keep searching and studying ways to solve the problem I've been through.
I firstly tried to send base64 images from the API to the browser, using this code:
<!-- language: python -->
for img in imglist: #loop for creating a list of base64 images from a list of image dir.
imgcode = base64.encodestring(open(imgdir + img,"rb").read())
imgcodelist.append(imgcode)
for driver in sortdriverList: #loop for taking drivers points and turn it into RANK img
if (driver['Races'] < 21):
driver['Rank'] = str(imgcodelist[9])
[...]
The second loop is longer than that, stil what I've shown to you above makes any driver that wasn't participating in more than 21 races, be part of a 'NON CLASSIFIED' badge.
I used AngularJS to try to show the base64 image using the code below.
'<html>'
<td><img src="data:image/png;base64,{{ '{{driver.Rank}}'}}"></td>
[driver.Rank] should be the base64 code string. When I run the app, the image is not shown, instead I see the very code of the image inside the table =/
Then I tried to turn [driver.Rank] into a dir string for "img src=", using the codes below.
<!-- language: python -->
imglist = ["notclassified.png", etc...]
imgdir = "static/images/"
for item in sortdriverList:
if (item['Races'] < 21):
item['Points'] = imgdir + imglist[9]
and in my HTML I changed the img src to:
'<html>'
<img src= {{ '{{driver.Rank}}' }}>
and now it shows the directory of the images.
I've been searching for CSS ways to make it possible.
I coudn't find a solution yet.
It's hard to tell what's going on since only segments are pasted, but I'm guessing it has to do with how you are escaping the code. Maybe you could paste the generated code in chrome.
Sometimes seeing a working example helps.
angular.module('App', [])
.controller('DriverCtrl', DriverCtrl);
function DriverCtrl($scope) {
// base64 encode 1x1 black pixel
this.Rank = 'R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';
}
<div ng-app="App">
<div ng-controller="DriverCtrl as driver">
<div>Rank: {{driver.Rank}}</div>
<span>Image:</span>
<img ng-src="data:image/png;base64,{{driver.Rank}}">
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
I'm working with an IP camera. I can use a URL such as this one to grab a static image off the camera:
http://Username:Password#IP_of_Camera:Port/streaming/channels/1/picture
What I want to do is have python/flask download the image from that URL when the client loads the page, and embed the image into the page using an img tag.
If I have a template that looks something like this:
<html>
<head>
<title>Test</title>
</head>
<body>
<img src="{{ image }}">
</body>
</html>
how do I replace the {{ image }} with the downloaded image?
Would I use urllib/requests to download the image to flask's static folder, replace {{ image }} with something like {{ url_for('static', filename="temp_image.png") }}, and then delete the image from the static folder when the page loads? Would I download it someplace else instead (other than the static folder)? Or is there some other way to do it that keeps the image in memory?
PS. I know it's possible to replace {{ image }} with that URL directly, but that reveals the username/password/IP/port of the camera to the client.
I would add a masking route on flask that fetches and serves the image directly.
Lets say domain.com/image/user1/cam1
Your server would typically make a http request to the camera and once it receives a response, you can straight up serve it as a Response object with appropriate mimetype.
In this case, the image you fetched from camera resides in your RAM.
#app.route('image/<userID>/<camID>')
def fun(userID,camID):
# fetch the picture from appropriate cam
pic = requests.get('http://'+
'Username:Password'+ # dynamically replace user id / password/ auth
'#IP_of_Camera:Port'+ #dynamically replace port / IP
'/streaming/channels/1/picture')
# do processing of pic here..
return Response(pic,mimetype="image/png")
However, if this image needs to be served over and over again, then you might wanna cache it. In which case, I would pick something closer to your approach.
If you want to stream the camera images, it is a whole different ballgame.
import requests
url = "http://Username:Password#IP_of_Camera:Port/streaming/channels/1/picture"
response = requests.get(url)
if response.status_code == 200:
f = open("/your/static/dir/temp.png", 'wb')
f.write(response.content)
f.close()
{{ url_for('static' filename="temp.png") }}
Not sure why you would need to delete it, but I guess you could if you thought that was required
I want to provide rss feed under google app engine/python.
I've tried to use usual request handler and generate xml response. When I access the feed url directly, I can see the feed correctly, however, when I'm trying to subscribe to the feed in google reader, it says that
'The feed being requested cannot be found.'
I wonder whether this approach is right. I was considering using a static xml file and updating it by cron jobs. But while GAE doesn't support file i/o, this approach seems not going to work.
How to solve this? Thanks!
There're 2 solutions I suggest:
GAE-REST you can just add to your project and configure and it will make RSS for you but the project is old and no longer maintained.
Do like I do, use a template to write a list to and like this I could succeed generating RSS (GeoRSS) that can be read via google reader where template is:
<title>{{host}}</title>
<link href="http://{{host}}" rel="self"/>
<id>http://{{host}}/</id>
<updated>2011-09-17T08:14:49.875423Z</updated>
<generator uri="http://{{host}}/">{{host}}</generator>
{% for entity in entities %}
<entry>
<title><![CDATA[{{entity.title}}]]></title>
<link href="http://{{host}}/vi/{{entity.key.id}}"/>
<id>http://{{host}}/vi/{{entity.key.id}}</id>
<updated>{{entity.modified.isoformat}}Z</updated>
<author><name>{{entity.title|escape}}</name></author>
<georss:point>{{entity.geopt.lon|floatformat:2}},{{entity.geopt.lat|floatformat:2}}</georss:point>
<published>{{entity.added}}</published>
<summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">{{entity.text|escape}}</div>
</summary>
</entry>
{% endfor %}
</feed>
My handler is (you can also do this with python 2.7 as just a function outside a handler for a more minimal solution):
class GeoRSS(webapp2.RequestHandler):
def get(self):
start = datetime.datetime.now() - timedelta(days=60)
count = (int(self.request.get('count'
)) if not self.request.get('count') == '' else 1000)
try:
entities = memcache.get('entities')
except KeyError:
entity = Entity.all().filter('modified >',
start).filter('published =',
True).order('-modified').fetch(count)
memcache.set('entities', entities)
template_values = {'entities': entities, 'request': self.request,
'host': os.environ.get('HTTP_HOST',
os.environ['SERVER_NAME'])}
dispatch = 'templates/georss.html'
path = os.path.join(os.path.dirname(__file__), dispatch)
output = template.render(path, template_values)
self.response.headers['Cache-Control'] = 'public,max-age=%s' \
% 86400
self.response.headers['Content-Type'] = 'application/rss+xml'
self.response.out.write(output)
I hope some of this works for you, both ways worked for me.
I have an Atom feed generator for my blog, which runs on AppEngine/Python. I use the Django 1.2 template engine to construct the feed. My template looks like this:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xml:lang="en"
xml:base="http://www.example.org">
<id>urn:uuid:4FC292A4-C69C-4126-A9E5-4C65B6566E05</id>
<title>Adam Crossland's Blog</title>
<subtitle>opinions and rants on software and...things</subtitle>
<updated>{{ updated }}</updated>
<author>
<name>Adam Crossland</name>
<email>adam#adamcrossland.net</email>
</author>
<link href="http://blog.adamcrossland.net/" />
<link rel="self" href="http://blog.adamcrossland.net/home/feed" />
{% for each_post in posts %}{{ each_post.to_atom|safe }}
{% endfor %}
</feed>
Note: if you use any of this, you'll need to create your own uuid to go into the id node.
The updated node should contain the time and date on which contents of the feed were last updated in rfc 3339 format. Fortunately, Python has a library to take care of this for you. An excerpt from the controller that generates the feed:
from rfc3339 import rfc3339
posts = Post.get_all_posts()
self.context['posts'] = posts
# Initially, we'll assume that there are no posts in the blog and provide
# an empty date.
self.context['updated'] = ""
if posts is not None and len(posts) > 0:
# But there are posts, so we will pick the most recent one to get a good
# value for updated.
self.context['updated'] = rfc3339(posts[0].updated(), utc=True)
response.content_type = "application/atom+xml"
Don't worry about the self.context['updated'] stuff. That just how my framework provides a shortcut for setting template variables. The import part is that I encode the date that I want to use with the rfc3339 function. Also, I set the content_type property of the Response object to be application/atom+xml.
The only other missing piece is that the template uses a method called to_atom to turn the Post object into Atom-formatted data:
def to_atom(self):
"Create an ATOM entry block to represent this Post."
from rfc3339 import rfc3339
url_for = self.url_for()
atom_out = "<entry>\n\t<title>%s</title>\n\t<link href=\"http://blog.adamcrossland.net/%s\" />\n\t<id>%s</id>\n\t<summary>%s</summary>\n\t<updated>%s</updated>\n </entry>" % (self.title, url_for, self.slug_text, self.summary_for(), rfc3339(self.updated(), utc=True))
return atom_out
That's all that is required as far as I know, and this code does generate a perfectly-nice and working feed for my blog. Now, if you really want to do RSS instead of Atom, you'll need to change the format of the feed template, the Post template and the content_type, but I think that is the essence of what you need to do to get a feed generated from an AppEngine/Python application.
There's nothing special about generating XML as opposed to HTML - provided you set the content type correctly. Pass your feed to the validator at http://validator.w3.org/feed/ and it will tell you what's wrong with it.
If that doesn't help, you'll need to show us your source - we can't debug your code for you if you won't show it to us.