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 “contact@codeforests.com” 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] = 'contact@codeforests.com'")
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. Follow me on twitter for more updates.

You may also like

5 6 votes
Article Rating
Subscribe
Notify of
guest
94 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(‘abc@gmail.com).Folders(‘Inbox’) into two lines to see where the error is coming from

curiousM

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

Lasse

I tried this and still “cant find object”, do you have link to the docs when reading from an outlook account? 🙂

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]=’example@outlook.com'”)
   
   
  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]=’example@outlook.com'”)

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 1 year 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]=’example@outlook.com'”)

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 1 year 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.

Lasse

Im getting an error when trying to find “Inbox”, anything you guys have had? 🙂

EDIT: The Inbox looks like its names “Inbox” but it was actually named in my countrys language “Inkorg” so watch out for that one!

Last edited 4 months ago by Lasse
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”.

Ana

For those mentioning multiple Outlook accounts: if by any chance those accounts all come from the same server and it a Microsoft Exchange Server (or Exchange Online), you could follow the code I have in my article here https://www.codementor.io/@anananet/how-built-a-python-script-to-read-e-mails-from-an-exchange-server-z6xwh76hr

Abhi

Great post and very helpful Ken! Is there a restriction on the date range for which you can download email data? I am using the code below to try and download last 60 days worth of emails, but the output is still the last 30 days worth of emails. Please note that i have used dd/mm/yyyy settings as they are my local region settings.

Kindly help!

#set date range for email messages extraction
received_dt = datetime.now() – timedelta(days=60)
received_dt = received_dt.strftime(‘%d/%m/%Y %H:%M %p’)
messages = messages.Restrict(“[ReceivedTime] >= ‘” + received_dt + “‘”)
#set sender address
messages = messages.Restrict(“[SenderEmailAddress] = ‘abc@bbb.com'”)
#messages = messages.Restrict(“[Subject] = ‘Sample Report'”)

for message in list(messages):
  try:
    s = message.sender
    #sub = message.subject
    body = message.body
    print(f”message from {s} : {body}”)
  except Exception as e:
    print(“error when saving the attachment:” + str(e))

Shubham

Hy Ken
can you please tell a way where we can put credentials also and then login

koenig

Great posts! Simply great! Using this and the “5 tips” post I’ve successfully written a program to save attachments from my outlook which has 7 other accounts linked to it.
Now it seems that Outlook must be running in order to run my program. I’d like to let the program run on a server without launching outlook. Could you please give me tips as how to access directly an exchange server to do the job? Many thanks!

Geovanio Peres

Hi Ken great post. Please help me with this question: how to filter unread emails?

Geovanio Peres

You are amazing… I had already gotten this solution. But I’m Brazilian and around here we don’t have people who lend themselves to help like you have done to everyone on this blog. Congrats

Dylan

Is there anyway to add events to the outlook Calendar via python?

Ian

Great article!

However I am finding that I can access messages.subject, messages.size, messages.ReceivedTime, messages.UnRead, but nothing much else such as messages.body, messages.SenderEmailAddress gives an error

com_error: (-2147467259, ‘Unspecified error’, None, None)

Any ideas? It is my work Outlook account, so maybe there is a block on some operations?

CyberIT

This is some great resource. Im having a little issue though.

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

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

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

inbox = mapi.GetDefaultFolder(6)

messages = inbox.Items

#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] = 'joe@example.com'")
messages = messages.Restrict("[Subject] = 'Test'")

for message in list(messages):
  print(message.Body)

The output is only
joe@example.com

I figured the output would be the text within the body of the email.

Im using Visual Studio Code and running debug mode.

Any help would be appreciated. Thanks!

Bao Nguyen

Hi, outlook = win32com.client.Dispatch(‘outlook.application’) is for outlook apps, can I use Outlook on web?
Thanks in advance!

Logan Elam

Do you think that this can be used to push data to outlook colander?

Sathiya

Novice here! can someone help me with additional codes to click on hyperlinks in the body of the email?

Raushan

For folders other than Inbox, please use this.
inbox = mapi.GetDefaultFolder(6).Parent.Folders(“MyFolderName”)

priti

how can I post single notification on teams channel when I got mail with particular subject name. the below is my code I want to run this code in background but give one notification per one mail only. if I run this code its continuously giving notification until minute end after I got mail. Please help me on this

def outlookFind():
    outlook = client.Dispatch("Outlook.Application")
    namespace=outlook.GetNamespace("MAPI")
    inbox = namespace.GetDefaultfolder(6)
    messages = inbox.Items
    received_datime = datetime.now().strftime('%m/%d/%Y %H:%M %p')
    messages = messages.Restrict("[ReceivedTime] >= '" + received_datime + "'")
    messages = messages.Restrict("[Subject] = 'hai'")
    message= messages.GetLast()
    for message in messages:
            if message:
                  myTeamsMessage = pymsteams.connectorcard("teams channel webhook")
                  myTeamsMessage.color("#8B83DB")
                  myTeamsMessage.title("New Mail")
                  myTeamsMessage.text("You got a new mail with subject hai)
                  myTeamsMessage.send()
                      
while True:       
     outlookFind()
             
                  
chris

Hi – I am getting the following error “error when getting at least one of the message attributes:(-2147352567, ‘Exception occurred.’, (4096, ‘Microsoft Outlook’, ‘The operation failed because of a registry or installation problem. Restart Outlook and try again. If the problem persists, reinstall.’, None, 0, -2147221225), None)”

I restarted the computer and outlook and still get the same error. I do get output though.

Below the relevant part of the Python code. I am getting the output but noticed the following:
1) I compared the total number of items in a terminal sub-folder to the count of outputted records in the corresponding FolderPath and they match. However, the code is always outputting the same conversation, only the attachments shown differ across records. Why aren’t the other items in the sub-folder parsed out and written to the output file? Is it possible to not repeat the same conversation in the output?

2) Sender email address field does not always show the sender’s email. Instead output looks like: /O=EXCHANGE/OU=EXCHANGE ADMINISTRATIVE GROUP (FYDIBOHF23SPDLT)/CN=RECIPIENTS/CN=Elton John:

PYTHON CODE

SenderName = message.Sender
SenderEmail = message.SenderEmailAddress
ReceivedByName = message.ReceivedByName 
ReceivedOnBehalfOfName = message.ReceivedOnBehalfOfName 
RecipientNames = message.To
CCNames = message.CC
BccNames = message.BCC
ConversationID = message.ConversationID 
ConversationTopic = message.ConversationTopic 
ReceivedTime = message.ReceivedTime 
Subject = message.Subject 
Body = message.Body 

# count of attachment(s)
CountAttachments = message.Attachments.Count

   
# getting attachment(s)   
try:
  s = message.sender
  AttachmentsFilenames = “”
  for attachment in message.Attachments:
     # attachment.SaveASFile(os.path.join(outputDir, attachment.FileName))
     # print(f”attachment {attachment.FileName} from {s} saved”)
     AttachmentsFilenames = AttachmentsFilenames + “; ” + attachment.FileName
except Exception as e:
  print(“error when saving the attachment:” + str(e))
   
msg_elements = (folderPath, SenderName, SenderEmail, ReceivedByName, ReceivedOnBehalfOfName, RecipientNames, 
        CCNames, BccNames, ConversationID, ConversationTopic, ReceivedTime, Subject, Body, CountAttachments,
        AttachmentsFilenames)

with open(output_file_name, ‘a’,newline=”, encoding=”utf-8″) as outcsv:
        writer = csv.writer(outcsv)
        writer.writerow(msg_elements)

chandu

yeah hi i have multiple accounts in my outlook would you please help me in that.

William

I assume that this thread is dead, but how would I go about adding a restriction to account for forwards from someone and not just direct emails?

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