How can I uniquely identify an IMAP message after copying it? - python

I want to move an IMAP message from INBOX to INBOX/Archive using python's imapclient library, which I'm doing basically like this:
def archive_message(imap, message_id):
imap.copy([message_id], getOptions().imap_archive_folder)
imap.delete_messages([message_id])
However, this loses my reference to the message. What I want to do is to store an identifier for the message that will allow me to look up the message later, using something like this:
def retrieve_message(imap, MYSTICAL_STORED_ID):
imap.select_folder(getOptions().imap_archive_folder)
return imap.fetch([MYSTICAL_STORED_ID], parts=["RFC822"])
What ID should I / can I use for this, and how would I do the lookup part of this?

The value in the Message-Id header is supposed to be unique per email message.

Related

python win32com: Delete multiple emails in Outlook

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.
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.
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
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 = 'myname#abc.com'
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.
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.
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://learn.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()
I've implemented an alternative solution in local Outlook, by moving email ítems from.inbox folder to deleted items folder or to an archive folder, by using VBA code or Outlook filter rules directly.
This way, I just mannualy empty the deleted items folder once a week (of course this periodic step can also be programmed).
I observed that this strategy can be more efficient instead of delete item per item using code (you mentioned the internal.indexes problem).

forward message to super group with telethon

Recently I wrote code that should forward every message from a certain user to all groups that I joined but it doesn't.
Here my code:
for message in client.iter_messages('aliakhtari78'):
try:
dialogs = client.get_dialogs()
for dialog in dialogs:
id_chat = dialog.message.to_id.channel_id
entity = client.get_entity(id_chat)
client.forward_messages(
entity, # to which entity you are forwarding the messages
message.id, # the IDs of the messages (or message) to forward
'somebody' # who sent the messages?
)
except:
pass
in this code first I take every message which send to me by 'aliakhtari78' and then get entity of the groups that I joined to and in the end it should forward the message to all groups but it doesn't, I check my code and replace the entity with a user entity and it worked and I know the problem is because of entity, but I cant find out what is my problem.
In addition, I'm sorry for writing mistakes in my question.
In order to send messages to any entities in Telegram, you need two pieces of information:
the constant unique ID of the entity (It's an integer. It's NOT username string)
the access_hash which is different for each user for each entity
You can only pass #username to client.get_entity, and Telethon automatically resolves the #username to an entity with id and access_hash. That's why it works when you change your code like that. However, in your code, you have passed channel_id (which is the constant unique ID of the entity) to client.get_entity, not username.
Note that client.get_dialogs returns entities along with dialogs. You have just ignored the entities! This is how you can get an array of all entities:
dialogs, entities = client.get_dialogs()
Then simply pass the corresponding entity from the entities array to client.forward_messages.

How can I fetch the SMTP headers after sending email?

I'm able to send emails using smtplib with ease. What I'm struggling with is reading the actual headers that were sent one. Specifically, I'm looking to read the Message-ID and References.
I thought at first that sendmail() would return them, but it does not.
Found that I'm able to redirect smtpilb.stderr to my own function and parse out the data that I need. Is there a better way that would allow me to do say:
headers['References']
If you use sendmail() I am not sure how to access the headers, because you don't have a Message object in that case. However, if you use send_message instead - which is very similar to sendmail() - and pass it an email.message.Message object, then all of the email message headers and their values are stored in a dict in your Message object. So e.g., Message-ID can be accessed from an email message object msg with msg['Message-ID'], subject can be accessed using msg['Subject'], etc. I don't think anything will be stored in message-id unless you put it there yourself though. You can 'roll your own' Message-ID using make_msgid() from email.utils:
from email.utils import make_msgid
msg['Message-ID'] = make_msgid()

Forwarded Email parsing in Python/Any other language?

I have some mails in txt format, that have been forwarded multiple times.
I want to extract the content/the main body of the mail. This should be at the last position in the hierarchy..right? (Someone point this out if I'm wrong).
The email module doesn't give me a way to extract the content. if I make a message object, the object doesn't have a field for the content of the body.
Any idea on how to do it? Any module that exists for the same or any any particular way you can think of except the most naive one of-course of starting from the back of the text file and looking till you find the header.
If there is an easy or straightforward way/module with any other language ( I doubt), please let me know that as well!
Any help is much appreciated!
The email module doesn't give me a way to extract the content. if I make a message object, the object doesn't have a field for the content of the body.
Of course it does. Have a look at the Python documentation and examples. In particular, look at the walk and payload methods.
Try get_payload on the parsed Message object. If there is only one message, the return type will be string, otherwise it will be a list of Message objects.
Something like this:
messages = parsed_message.get_payload()
while type(messages) <> Types.StringType:
messages = messages[-1].get_payload()

Python filter for postfix

I am trying to make a simple Python filter for postfix, to add in a 'Reply-to' header to certain messages.
What I've done so far is to take the email from stdin, and parse it into an email object like so:
raw = sys.stdin.readlines()
msg = email.message_from_string(''.join(raw))
Then I've played with headers etc.
msg.add_header('Reply-to', 'foo#bar.com')
And now want to re-inject that back into postfix. Reading the filter readme associated with postfix, I should pass it back using the 'sendmail' command. However, I'm not sure how to pass the email object over to sendmail, for example using subprocess's 'call()' or whether I should use the smtplib's 'smtplib.SMTP()'?
What would be the 'correct' method?
Thanks
You should be able to use both methods, but smtplib.SMTP() is more flexible and makes the error handling easier.
If you need an example, have a look at my framework for python filters:
https://github.com/gryphius/fuglu/blob/master/fuglu/src/fuglu/connectors/smtpconnector.py#L67
the re_inject method does exactly that (FUSMTPClient is a subclass of smtplib.SMTP), so basically it boils down to:
client = smtplib.SMTP('127.0.0.1',<yourportnumber for the receiving postfix instance>)
client.sendmail(<envelope from>, <envelope to>, <yourmessageobject>.as_string())

Categories