2
votes

I need to delete multiple email messages in Outlook from python via win32com module.

I understand there is a VBA method MailItem.Delete() available to win32com via COM and it works; but it is VERY VERY slow when deleting more than one email since one would have to delete emails sequentially ie loop over the MailItem collection of emails.

Is there any way to delete a selected collection of mailItems at once, something like MailItemCollection.DeleteAll()?

Also, if above is not possible; is it at all possible to delete many emails via multi-threaded approach ie divide the collection of mailItems into, let's say, 4 subsets; have 4 threads operate on those?

I figure since I can delete multiple emails in outlook via its GUI very fast, there has to be a way where I can do the same thing via COM API.

5
@TheIncorrigible1, I would if I could. I must use python... and you are going off topic... - JavaFan
@TheIncorrigible1 - the issue here is the performance of underlying API when used against on online or a large folder, not the overhead of using Python vs any other language - Dmitry Streblechenko
@DmitryStreblechenko When using the outlook com object, you're essentially using whatever office version is installed and emulating click actions so it's only as slow as whatever application is being used (e.g., 2013 v 2016) and the hardware of your host. You're not directly interfacing with an outlook server; the outlook application is. - Maximilian Burszley
@TheIncorrigible1 - absolutely not: Python can access Outlook Object Model in the same fashion as any other language such as VBS or Powershell, no clicks are emulated. You are probably thinking of late binding vs early binding, which is negligible in this case. - Dmitry Streblechenko
@DmitryStreblechenko I didn't mean literal click emulation, but the office com objects are doing essentially that. I can't think of any functions available to the APIs that you couldn't access through the GUI off the top of my head. - Maximilian Burszley

5 Answers

3
votes

Not in OOM - MailItem.Delete or Items.Remove(Index) is all you get.

On the Extended MAPI level (C++ or Delphi, but not Python), you can delete multiple messages using IMAPIFolder.DeleteMessages (which takes a list of entry ids). Or you can use IMAPIFolder.EmptyFolder (deletes all messages in a folder).

If using Redemption (any language; I am its author) is an option, you can use RDOFolder2.EmptyFolder or RDOFolder.Items.RemoveMultiple. RDOFolder can be retrieved from RDOSession.GetRDOObjectFromOutlookObject if you pass Outlook's MAPIFolder object as a parameter.

2
votes

On top of a great answer by @Dimitry I'll add a remark which may be important for you: if you start deleting from Items as you iterate over it, strange things may happen. For example on my system the following Python code:

for mail in folder.Items:
    mail.Delete()

as well as

for index, mail in enumerate(folder.Items, 1):
    folder.Remove(index)

both remove only half of the items in the folder! The reason seems to be that Items uses a range of indices internally to provide an iterator so each time an element is deleted, the tail of the list is shifted by one...

To remove all items in the folder try:

for i in range(len(folder.Items)):
    folder.Remove(1)

If you need to filter by a certain criterion consider first gathering EntryIDs and then deleting searching for ID:

ids = []
for i in range(len(folder.Items), 1):
    if to_be_deleted(folder.Items[index]):
        ids.append(index)
for id in ids:
    outlook.GetEntryByID(id).Delete()

I imagine performance of that is even worse, though :c

2
votes

Great answer from Dedalus above. Wanted to make a more concise version of the code:

import win32com.client

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")

# Select main Inbox
inbox = outlook.GetDefaultFolder(6)
messages = inbox.Items

# Delete all messages from a specific sender
sender = '[email protected]'
try:
    for message in messages:
        try:
            s = message.sender
            s = str(s)
            if s == sender:
                 message.Delete()
        except:
            pass
except:
    pass

You may not need two "trys" but I found it was more stable when applying the script to a long and heavily used inbox. Usually I combine this with a script that limits the message = inbox.Items to within a week so it doesn't do the entire inbox.

1
votes

For me it worked by iterating the items in reverse.

Old:

for mail in folder.Items:
    if 'whatever' in mail.Subject: # just a condition (optional)
        mail.Delete()

New code:

for mail in reversed(folder.Items): # just tried deleting Items in reverse order
    if 'whatever' in mail.Subject: # just a condition (optional)
        mail.Delete()

Hope this helps someone.

0
votes

Am I missing something? Neither Application nor NameSpace objects appear to have a GetEntryByID method, though the rest of what Dedalus pointed out was correct.

Namespace objects have a GetItemFromID method, and MailItem objects have a EntryID property which will uniquely identify them so long as they don't get reorganized into different folders.

Documentation: https://docs.microsoft.com/en-us/office/vba/outlook/how-to/items-folders-and-stores/working-with-entryids-and-storeids

My full solve:

import win32com.client

outlook = win32com.client.gencache.EnsureDispatch("Outlook.Application")

folders = outlook.GetNamespace("MAPI")

inbox= folders.GetDefaultFolder(6)

messages=inbox.Items

email_ids = []

folder_id = inbox.StoreID 

# Here create a function to isolate/exclude. Below is just an example of filtering by a subject line.

email_subjects = ['Subj1','Subj2','Subj3']

for i in range(len(messages)):

    if any(header in inbox.Items[i].Subject for header in email_subjects):

        email_ids.append(inbox.Items[i].EntryID)

for id in email_ids:
    folders.GetItemFromID(id, folder_id).Delete()