I am writing a python code to download a specific attachment from unread emails in outlook and mark those emails as read. I have managed to finish 90% of it i.e. I can do an iteration to open unread emails and download the attachments with a specific emails. However, I have two issues.
I am downloading the attachment with the same name, and if there are two attachments with the same name, it just saves the one it extracts from the last iteration. I tried appending a time stamp at the end of the file name but it has the same effect. Any help would be appreciated. This is not mandatory requirement since the mail comes at stipulated intervals and I can write a separate python code to rename it but I want to pack everything in this single email.
I would like to mark the email as read after the attachment is downloaded. I do not know the command for this one. I have attached the code for your reference.
P.S. This is my first real python code. Also this is my first post here. Apologies if this is was already asked elsewhere.
import win32com.client
import os
import time
date_time_stamp = time.strftime("%Y%m%d-%H%M%S")
#set custom working directory
os.chdir('C:\\Users\user_name\Desktop\')
print(os.getcwd())
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
main_inbox = outlook.GetDefaultFolder(6)
subfolder = main_inbox.Folders.Item("my_child_folder_under_inbox")
subfolderitems = subfolder.Items
message = subfolderitems.GetFirst()
attachment_name = 'my_attachment_name'
#Loop to pick messages that are unread
for message in subfolderitems:
if message.Unread == True:
print("New Mail Found... Downloading Attachment...")
#Loop to check if the attachment name is the same
for attachments in message.Attachments:
if attachments.Filename == attachment_name:
#Saves to the attachment to the working directory
attachments.SaveASFile(os.getcwd() + '\\' + 'my_attachment_name' + date_time_stamp + '.csv')
print (attachments)
time.sleep(2)
break
#Go to next unread messages if any
message = subfolderitems.GetNext()
else:
print ("Checking...")
--
Thanks and Regards,
Sakthi Ganesh K.
I think it may have to do with your 'date_time_stamp', since it tries to download the files in the same second, and the system only keeps the last one. You could try using a UUID for that to ensure it is a unique string:
import uuid
file_uuid = str(uuid.uuid4())
...
attachments.SaveASFile(os.getcwd() + '\\' + 'my_attachment_name' + file_uuid + '.csv')
To mark the message as Read, you could simply do:
message.Unread = False
Related
I am trying to iterate through the contents of a subfolder, and if the message contains an .xlsx attachment, download the attachment to a local directory. I have confirmed all other parts of this program work until that line, which throws an exception each time.
I am running the following code in a Jupyter notebook through VSCode:
# import libraries
import win32com.client
import re
import os
# set up connection to outlook
path = os.path.expanduser("~\\Desktop\\SBD_DB")
print(path)
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6)
target_folder = inbox.Folders['SBD - Productivity'].Folders['Productivity Data Request']
target_folder.Name
messages = target_folder.Items
message = messages.GetLast()
# while True:
x=0
while x < 100:
try:
# print(message.subject) # get the subject of the email
for attachment in message.attachments:
if 'xlsx' in attachment.FileName:
# print("reached")
attachment.SaveAsFile(os.path.join(path, str(attachment.FileName)))
print("found excel:", attachment.FileName)
message = messages.GetPrevious()
x+=1
except:
print("exception")
message = messages.GetPrevious()
x+=1
Looks like the following line of code throws an exception at runtime:
attachment.SaveAsFile(os.path.join(path, str(attachment.FileName)))
First, make sure that you deal with an attached file, not a link to the actual file. The Attachment.Type property returns an OlAttachmentType constant indicating the type of the specified object. You are interested in the olByValue value when the attachment is a copy of the original file and can be accessed even if the original file is removed.
Second, you need to make sure that the file path (especially the FileName property) doesn't contain forbidden symbols, see What characters are forbidden in Windows and Linux directory names? for more information.
Third, make sure that a target folder exists on the disk and points to the local folder. According to the exception message:
'Cannot save the attachment. Path does not exist. Verify the path is correct.'
That is it. Try to open the folder manually first, according to the error message the path doesn't exist. Before calling the SaveAsFile method you need to created the target folder or make sure it exists before.
This code takes email pdf attachments, download it, merge to one pdf file and send further.
Now it takes all emails which are marked with specific category in that inbox, so it merge all pdf's from all emails to one file.
But I want that it take emails one by one, that after download pdf's from one email it will merge and send them, delete them from folder and just after that it will take second email.
How to make such loop for this code?
import datetime
import os
import win32com.client as win32
from PyPDF2 import PdfFileMerger
from pathlib import Path
path = ('C:\\Users\\Desktop\\Work')
today = datetime.date.today()
outlook = win32.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6)
subFolder = inbox.Folders("Test")
messages = subFolder.Items
def save_attachments(subject):
for message in messages:
if message.Categories == "Red Category":
for attachment in message.Attachments:
print(attachment.FileName)
attachment.SaveAsFile(os.path.join(path, str(attachment)))
if __name__ == "__main__":
save_attachments('PB report - next steps')
#Merge PDF's
merger = PdfFileMerger()
path_to_files = r'C:\Users\Desktop\Work/'
for root, dirs, file_names in os.walk(path_to_files):
for file_name in file_names:
merger.append(path_to_files + file_name)
merger.write(r"C:\Users\Desktop\Work\merged.pdf")
merger.close()
#Send PDF with outlook
# construct Outlook application instance
olApp = win32.Dispatch('Outlook.Application')
olNS = olApp.GetNameSpace('MAPI')
# construct the email item object
mailItem = olApp.CreateItem(0)
mailItem.Subject = 'Test'
mailItem.BodyFormat = 1
mailItem.Body = "Pdf merged"
mailItem.To = 'email'
path = (os.path.join('C:\\Users\\Desktop\\Work\\merged.pdf'))
mailItem.Attachments.Add(path)
mailItem.Display()
mailItem.Save()
mailItem.Send()
#Delete PDF's from folder
[f.unlink() for f in Path("C:\\Users\\Desktop\\Work").glob("*") if f.is_file()]
Iterating over all items in the folder is not really a good idea:
for message in messages:
if message.Categories == "Red Category":
Instead, you need to use the Find/FindNext or Restrict methods of the Items class from the Outlook object model. So, in that case you will get all items that correspond to your search criteria and iterate over them only. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Second, there is no need to create a new Outlook Application instance:
# construct Outlook application instance
olApp = win32.Dispatch('Outlook.Application')
olNS = olApp.GetNameSpace('MAPI')
Re-use the existing application instance instead. Moreover, Outlook is a singleton, you can't have two instances running at the same time.
Third, there is no need to display and save the item created before sending:
mailItem.Attachments.Add(path)
mailItem.Send()
So I am trying to read in an Excel file from an attachment in Microsoft outlook. The code below works, but only while the email I am trying to read the attachment from is at the top of my inbox. How can I adjust my code so it looks at all emails in my inbox folder looking for the attachment or finding the emailed based on the subject provided. Also I would eventually like to have this work with a shared mailbox, but that is a secondary issue right now.
from win32com.client import Dispatch
import email
import datetime as date
import os
outlook = Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder("6")
all_inbox = inbox.Items
val_date = date.date.today()
sub_today = 'subject of email'
att_today = 'Filename'
for msg in all_inbox:
yourstring = msg.Subject.encode('ascii', 'ignore').decode('ascii')
if(yourstring.find('"Filename"') != -1):
break
for att in msg.Attachments:
if att.FileName == att_today:
attachments = msg.Attachments
break
attachment = attachments.Item(1)
fn = os.getcwd() + '\\' + att_today
attachment.SaveASFILE(fn)
df = pd.read_excel(fn)
First of all, to iterate over all items you need to skip items, not break the loop. Use the continue keyword instead of break in the loop.
The Outlook object model provides the Find/FindNext and Restrict methods for getting items that correspond to your conditions. The store provider does the job more efficient than just iterating over all items in the folder. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
You can use the following search criteria where the query performs a phrase match query for hello in the message subject (VBA syntax):
filter = "#SQL=" & Chr(34) & "https://schemas.microsoft.com/mapi/proptag/0x0037001E" _
& Chr(34) & " ci_phrasematch " & "'hello'"
To get a shared folder using the NameSpace.GetSharedDefaultFolder method which returns a Folder object that represents the specified default folder for the specified user. This method is used in a delegation scenario, where one user has delegated access to another user for one or more of their default folders (for example, their shared Calendar folder).
I found and modified a code to read CSV attachments file from Outlook application using Python.
What happened in my case is: When I request data for a certain period, they will send me the monthly data of the requested period in separate emails (E.g. request: January 2018 - December 2018; Receive : 12 mails with a single CSV attachment in each one of them.) i save all of the emails coming from the data warehouse in 'DWH Mail'
All of the emails will come from the same subject. So my code will: Save all CSV attachments which are stored in 'DWH Mail' from the Subject specified.
import win32com.client as client
import datetime as date
import os.path
def attach(mail_subject):
outlook = client.Dispatch("Outlook.Application").GetNamespace("MAPI")
folder = outlook.GetDefaultFolder("6").Folders["DWH Mail"]
val_date = date.date.today()
sub_target = mail_subject
for msg in folder.Items:
if msg.ReceivedTime.date() == val_date and msg.Subject == sub_target:
for att in msg.Attachments:
att.SaveASFile(os.getcwd() + "\\" + att.FileName)
print ("Mail Successfully Extracted")
break
print ("Done")
Now I could request for ZIP file, containing the CSV, so that I could receive the file faster. Where and what should I add in my code so that the loop will extract and save the CSV file from the ZIP file? Instead of save the ZIP file and I extract it manually later.
I am relatively new to Python, so any helps would be appreciated. Thank you.
import os
import pandas as pd
import zipfile
curDir = os.getcwd()
zf = zipfile.ZipFile(curDir + '/targetfolder/' + yourFileName + '.zip')
text_files = zf.infolist()
# list_ = []
print ("Decompressing and loading data into multiple files... ")
for text_file in text_files:
print(text_file.filename)
df = pd.read_csv(zf.open(text_file.filename)
# do df manipulations if required
df.to_csv(curDir + '/targetfolder/' + text_file.filename + '.csv')
# df = pd.concat(list_)
This will iterate through all the files and load them with the respective names as present in the zip file.
I'm writing a Python script that searches for .XLSX files with a specific name in a directory and then sends e-mails with those files attached to them.
If there are 3 XLSX files in that directory I want to send 3 e-mails, each with one of the files attached. What is happening with my code is that, in that example, it sends 3 e-mails:
the first email with 1 file attached
the second one with 2 files attached
the third one with 3 files attached
I tried moving the file to another directory after its attachment to the e-mail message, but it didn't work. Here's the code:
for xlsxfile in glob.glob(os.path.join('.', 'START_OF_FILENAME_*.XLSX')):
xlsxpart = MIMEApplication(open(xlsxfile, 'rb').read())
xlsxpart.add_header('Content-Disposition', 'attachment', filename=xlsxfile[1:])
msg.attach(xlsxpart)
shutil.move(xlsxfile, './sent/'+xlsxfile[2:])
try:
client = smtplib.SMTP()
client.connect('XX.XXX.XX.XX', port=25)
client.sendmail(username, rcptlist, msg.as_string())
client.quit()
#...exception handling
for xlsxfile in glob.glob(os.path.join('.', 'START_OF_FILENAME_*.XLSX')):
...
msg.attach(xlsxpart)
...
In every iteration the current file is being added to the existing msg object. By the time the loop gets to the third iteration, msg already has the previous 2 files attached to it.
Instead, a new msg object should be created every iteration:
for xlsxfile in glob.glob(os.path.join('.', 'START_OF_FILENAME_*.XLSX')):
...
msg = Message(...) # however you created msg before the loop
msg.attach(xlsxpart)
...