python read email from outlook and save attachment

How to read email from outlook in python

There are always scenarios that you may wonder how to have a program to automatically read email from outlook and do some processing based on certain criteria. The most common use case is that you want to auto process email attachments when receiving some scheduled reports. In this article, I will be explaining to you how to use python to read email from outlook and save attachment files into the specified folder.

Prerequisites:

In order to be able to access the outlook native application, we will need to make use of the pywin32 library. Make sure you have installed this library and imported into your script.

import win32com.client
#other libraries to be used in this script
import os
from datetime import datetime, timedelta

Let’s get started!

Like communicating with other system or app, you will need to initiate a session in the first place. By calling the GetNamespace function, you can get the outlook session for the subsequent operations.

outlook = win32com.client.Dispatch('outlook.application')
mapi = outlook.GetNamespace("MAPI")

if you have configured multiple accounts in your outlook, you need to pass in the account name when accessing it’s folders, we can cover this topic in another article. For this article, let assume we only have 1 account configured in outlook.

for account in mapi.Accounts:
	print(account.DeliveryStore.DisplayName)

To access the inbox folder, you will need to pass in the folder type – 6 in the below function. You may refer to this doc to understand the full list of folder types, such as the draft, outbox, sent, deleted items folder etc.

inbox = mapi.GetDefaultFolder(6)

What if your email is in a sub folder under your inbox? The GetDefaultFolder has the Folders attribute where you can access to the sub folder by it’s name. For instance, to access the “your_sub_folder” under the inbox folder:

inbox = mapi.GetDefaultFolder(6).Folders["your_sub_folder"]

Read email from outlook

Now you are accessible to the inbox and it’s sub folder. You can view all the messages by getting the items as per below. But you may want filter the messages by certain criteria, such as the receiving date, from, subject etc. To do that, we can apply some filter conditions to the messages.

messages = inbox.Items

Use Restrict function to filter your email message. For instance, we can filter by receiving time in past 24 hours, and email sender as “[email protected]” with subject as “Sample Report”

received_dt = datetime.now() - timedelta(days=1)
received_dt = received_dt.strftime('%m/%d/%Y %H:%M %p')
messages = messages.Restrict("[ReceivedTime] >= '" + received_dt + "'")
messages = messages.Restrict("[SenderEmailAddress] = '[email protected]'")
messages = messages.Restrict("[Subject] = 'Sample Report'")

Save attachment files

With all the above filters, we shall only have the messages that we are interested in. Let’s loop through the message and check for the details.

#Let's assume we want to save the email attachment to the below directory
outputDir = r"C:\attachment"
try:
    for message in list(messages):
	try:
	    s = message.sender
	    for attachment in message.Attachments:
	        attachment.SaveASFile(os.path.join(outputDir, attachment.FileName))
	        print(f"attachment {attachment.FileName} from {s} saved")
	except Exception as e:
		print("error when saving the attachment:" + str(e))
except Exception as e:
		print("error when processing emails messages:" + str(e))

There are other attributes like Body, Size, Subject, LastModificationTime etc., please check this Microsoft documentation for more details.

If the particular problem you are trying to solve is not covered in this article, you may check my another post 5 Tips For Reading Email From Outlook In Python. And you may be also interested to see how to send email from outlook in python, please check this article.

As per always, welcome any comments or questions.

You may also like

5 4 votes
Article Rating
Subscribe
Notify of
guest
66 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
pierre

Great post

Shay

Thank you.
I am getting an error while I try using Restrict, could you help?
pywintypes.com_error: (-2147352567, ‘Exception occurred.’, (4096, ‘Microsoft Outlook’, ‘Cannot parse condition. Error at “\xa0”.’, None, 0, -2147352567), None)

Shay

Thank you for the fast reply.
I managed to resolve this when using Restrict without any spaces before and after the equal sign.

Do you know how can use regex to filter emails? Let us say only messages from .*@gmail.com?

Shay

Thank you, I will try.
I wanted to avoid checking and only run a specific list.

Thank you very much for your help!

Anurag

How to get latest n mails?

Anurag

Thank you very much Ken. It works perfectly. You really saved my day.

Henry Roberts

getting invalid character in identifier error in line

messages = messages.Restrict(“[ReceivedTime] >= ‘” + received_dt + “‘”)
       ^

how do i resolve?

Raphael

Hi, I have linked my gmail account to Outlook but now I think there is the case where you said that I have to pass in the account name when accessing it’s folders. In Outlook i have the section for Outlook and below a section for my gmail-address. This is the one that I’m trying to access. Where do I have to do that?

Raphael

Now I’m getting this error:

Traceback (most recent call last):
 File “M:/Raphael/Programming/Python/TestProject/main.py”, line 12, in <module>
   inbox1 = folder.Folders(‘Inbox’)
 File “M:\Programme\anaconda3\envs\TestProject\lib\site-packages\win32com\client\dynamic.py”, line 197, in __call__
   return self._get_good_object_(self._oleobj_.Invoke(*allArgs),self._olerepr_.defaultDispatchName,None)
pywintypes.com_error: (-2147352567, ‘Exception occurred.’, (4096, ‘Microsoft Outlook’, ‘Der versuchte Vorgang konnte nicht ausgeführt werden. Ein Objekt wurde nicht gefunden.’, None, 0, -2147221233), None)

Raphael

Note:
I split the line inbox1 = mapi.Folders(‘[email protected]).Folders(‘Inbox’) into two lines to see where the error is coming from

curiousM

Hello- what if I have two accounts configured in outlook?

Sai

Hi, This was a great help for me, although my requirement is not getting fulfilled by this, I need to download the attachment from outlook using just a sender and a particular date. Could you take a look at it?

import win32com.client
  import os
  from datetime import datetime, timedelta
   
  Outlook = win32com.client.Dispatch(“Outlook.Application”)
  mapi=Outlook.GetNamespace(“MAPI”)
   
  Inbox = mapi.GetDefaultFolder(6)
  Messages = Inbox.Items
   
  ReceivedDate = datetime.now()
  ReceivedDate = ReceivedDate.strftime(‘%m/%d/%Y’)
   
  messages = Messages.Restrict(“[ReceivedTime] = ‘”+ReceivedDate+”‘”)
  messages = Messages.Restrict(“[SenderEmailAddress]=’[email protected]'”)
   
   
  OutputDir = r”C:\Users\usename\Desktop”
   
  try:
   for message in list(messages):
   try:
   s = message.sender
   print(s)
   for attachment in message.Attachments:
   print(attachment)
   attachment.SaveASFile(os.path.join(OutputDir, attachment.FileName))
   print(f”attachment {actualfilenamewithextension} from {s} saved”)
   except Exception as e:
   print(“error when saving the attachment:” + str(e))
  except Exception as e:
   print(“error when processing emails messages:” + str(e))

Sai

Hi Ken, thanks for your quick look into this, not sure what’s wrong I cannot still make this work.

So, I want to take the start and end dates as variables so I used this.

import win32com.client
import os
from datetime import datetime, timedelta, date

Outlook = win32com.client.Dispatch(“Outlook.Application”)
mapi=Outlook.GetNamespace(“MAPI”)
Inbox = mapi.GetDefaultFolder(6)
Messages = Inbox.Items

dt = date.today()
received_dt_start=(datetime.combine(dt, datetime.min.time()))
received_dt_end = received_dt_start + timedelta(hours=24)

print(received_dt_start)
print(received_dt_end)

messagesfilter = Messages.Restrict(“[ReceivedTime] >= ‘” + received_dt_start + “‘”)
messagesfilter = Messages.Restrict(“[ReceivedTime] <= ‘” + received_dt_end + “‘”)
messagesfilter = Messages.Restrict(“[SenderEmailAddress]=’[email protected]'”)

OutputDir = r”C:\Users\username\Desktop”
try:
for message in list(messagesfilter):
try:
s = message.sender
print(s)
for attachment in message.Attachments:
print(attachment)
attachment.SaveASFile(os.path.join(OutputDir, attachment.FileName))
print(f”attachment {actualfilenamewithextension} from {s} saved”)
except Exception as e:
print(“error when saving the attachment:” + str(e))
except Exception as e:
print(“error when processing emails messages:” + str(e))

Last edited 7 months ago by Sai
Sai

Hi Ken, I’ve changed the date format to m/d/y hh: mm am but the script is not going through the try and except method loop, if it goes through satisfying the condition it should give us the sender’s name if it’s not satisfied it should at least give the final except condition “error when processing emails” but that’s not happening. I’ve looked at the loop and as per my understanding it’s correct could you please check this?

messagesfilter = Messages.Restrict(“[ReceivedTime] >= ‘” + received_dt_start.strftime(‘%m/%d/%Y %H:%M %p’) + “‘”)
messagesfilter = Messages.Restrict(“[ReceivedTime] <= ‘” + received_dt_end.strftime(‘%m/%d/%Y %H:%M %p’) + “‘”)
messagesfilter = Messages.Restrict(“[SenderEmailAddress]=’[email protected]'”)

OutputDir = r”C:\Users\username\Desktop”  

try:
for message in list(messagesfilter):
try:
subject=message.Subject
print(subject)
sender = message.Sender
print(sender)
for attachment in message.Attachments:
print(attachment)
attachment.SaveASFile(os.path.join(OutputDir, attachment.FileName))
print(f”attachment {actualfilenamewithextension} from {s} saved”)
except Exception as e:
print(“error when saving the attachment:” + str(e))
except Exception as e:
print(“error when processing emails messages:” + str(e))

Liz

Hi Ken,
Your post helped me understand the nuances of reading mails through python.
I have been trying to read mails from a shared mail box. For some reason, the restrict date filter seems to be flip day and month. Are you aware of any reason why this might be? I input the date in the following format:
Today = Today.replace(hour =0, minute =0 ).strftime(‘%Y-%m-%d %H:%M %p’)
messages = messages.Restrict(“[ReceivedTime] >= ‘” + Today +”‘”)

and in this format as well
Today = Today.replace(hour =0, minute =0 ).strftime(‘%d-%m-%Y %H:%M %p’)

but in both cases the day and month get mixed up.

Any thoughts on this?

Liz

Hi Ken,
Thanks for the tip. Looks like that is the issue. The system I am using the code in has date in dd/mm/yyyy format; so I think this caused the day and month mix-up.

Caleb

For the filters (ex. messages = messages.Restrict(“[Subject] = ‘Sample Report'”)) – Is there a way to use a wildcard to capture all emails that have a partial match to search term?

T S

Hi Ken,

Your last try loop is wrong, missing file_name, indent error and try except not matching please see if i am wrong. Thanks

Caleb

This did work, made my life mush easier, thanks!

JAYANK KHANEJA

I am stuck here

messages = messages.Restrict("[Subject] = 'Sample Report'")

how to use messages object further to read body of email

maissa

code used to work fine but suddenly i got this error

TypeError: ‘Accounts’ object is not iterable

Not too sure how to fix it

maissa

yes dear,
i used exactly the same code that worked in the first run only. i will try to replace “accounts” with folders and check
thanks!

maissa

replacing “accounts” with folder did not help either:(

Daan

Thanks for your super helpful article, Ken! I am having a similar problem as maissa. Even with the following super simple code:

import win32com.client as win32

outlook = win32.Dispatch(‘Outlook.Application’).GetNamespace(“MAPI”)
accounts = outlook.Session.Accounts
for account in accounts:
   pass

I get “TypeError: ‘Accounts’ object is not iterable”.

If I do:

print(accounts.count)

I get: AttributeError: ‘<win32com.gen_py.Microsoft Outlook 16.0 Object Library._Accounts instance at 0x1571797904136>’ object has no attribute ‘count’

So it seems like I’m even missing some basic functionality. Would you have any suggestions for a solution?

Daan

Hi Ken, thanks for your suggestion, I managed to fix it with help from an answer on stackoverflow that I can’t seem to locate anymore, but it said to delete the folder Appdata/Temp/gen.py, which indeed resolved the issue.

Last edited 5 months ago by Daan
rek

Hi ken, looking for a scenario to restrict inbox mails with 3 different domains like @abc,@123,@xyz. can we use any filter to exclude these domains.If so, please post me the code..

rek

Messages object has no attribute Restrict is the error I have got

rek

I am looking for exchangelib library not win32. Can you post code for exchangelib library.

Trey

Hello, you said that if you have configured multiple accounts in your outlook, you need to pass in the account name when accessing it’s folders, you cover this topic in another article. I have not found any article that says how to do this, and I’m really struggling trying to pull emails from a different account.

alekks

Hi, great post!
Due to some DRM/Encryption software from my company my excel files are encrypted when saved on my computer, and when encrypted I can’t e.g. read the files via pandas.
So, is there a way to read like the bytes of the attachment and directly create a pandas dataframe from that without saving the file locally?
Something like:

import win32com
import io
import pandas as pd

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6) 
messages = inbox.Items

for message in messages:
  for attachment in message.Attachments:
    print(str(attachment))
    if ".xlsx" in str(attachment):
      df = pd.read_excel(io=io.BytesIO(attachment.content), sheet_name="Sheet1")
      print(df)

This doesn’t work becasue i get an attribute error on the pd.read_excel line:
AttributeError: <unknown>.content

The attachment.content conversion to bytes work with the exchangelib library but I don’t have exchange so I can’t use that. Any ideas?

ken

Hi Alekks,

For pywin32, unfortunately there is no such attribute called content for the attachment object, whatever attributes you can find here in VBA can be used the same way in Python. so I guess you will have to save the attachment file into a temp folder and read from there.

Amit Jambhale

Hi Ken,

I am getting an email with an email attachment which finally has the csv file that I want to access and manipulate data. I can write a python code for access csv file from an email. But in this case, I am not able to do so. Looking for a solution.

Regards,
Amit

Amit Jambhale

Thanks Ken.

I could resolve it:

outlook = win32com.client.Dispatch(‘outlook.application’)
mapi = outlook.GetNamespace(“MAPI”)
jvk = mapi.Folders[‘xxxxxxxxxx’]
inbox = jvk.Folders[‘Inbox’]
messages = inbox.Items

messages_php = messages.Restrict(“[Subject] = ‘xxxxxxxxxxx'”)
message = messages_php.GetLast()
attachment = message.Attachments.Item(1)
attachment.SaveAsFile(‘file path xxx.msg’)
msg = extract_msg.openMsg(‘xxxr.msg’)
msg.save_attachments()
file_name = ‘Cummins_xxxxx.zip’
with ZipFile(file_name, ‘r’) as zip:
  zip.extractall()
osr = zip.filelist[0].filename
php_osr_df = pd.read_csv(osr)

It is working 🙂 as of now.

KS13

Okay, great post.

I wanted to ask what should i do if i just wanted to copy content from the first mail.

ali

Hi am getting an error sometimes when i access ReceivedTime. Google says its a python bug. Any ideas to solve?
https://github.com/pyinstaller/pyinstaller/issues/4463

xyz

I am getting error following error after mapi.GetDefaultFolder(6):
<COMObject <unknown>>

Shaistha Seema

Pls write an article “To read Outlook email from a particular sender and write it as a table to an Excel file”.

66
0
Would love your thoughts, please comment.x
()
x