Work with email and mailbox by IMAP:
- Parsed email message attributes
- Query builder for searching emails
- Actions with emails: copy, delete, flag, move, seen
- Actions with folders: list, set, get, create, exists, rename, delete, status
- No dependencies
Python version | 3.3+ |
License | Apache-2.0 |
PyPI | https://pypi.python.org/pypi/imap_tools/ |
IMAP RFC | VERSION 4rev1 - https://tools.ietf.org/html/rfc3501 |
EMAIL RFC | Internet Message Format - https://tools.ietf.org/html/rfc2822 |
Contents
$ pip install imap_tools
from imap_tools import MailBox, AND
# get list of email subjects from INBOX folder
with MailBox('imap.mail.com').login('test@mail.com', 'password') as mailbox:
subjects = [msg.subject for msg in mailbox.fetch()]
# get list of email subjects from INBOX folder - equivalent verbose version
mailbox = MailBox('imap.mail.com')
mailbox.login('test@mail.com', 'password', initial_folder='INBOX') # or mailbox.folder.set instead 3d arg
subjects = [msg.subject for msg in mailbox.fetch(AND(all=True))]
mailbox.logout()
MailBox(BaseMailBox), MailBoxUnencrypted(BaseMailBox) - for create mailbox instance.
BaseMailBox.login, MailBox.xoauth2 - authentication functions
BaseMailBox.fetch - email message generator, first searches email nums by criteria, then fetch and yields Message:
- criteria = 'ALL', message search criteria, query builder
- charset = 'US-ASCII', indicates charset of the strings that appear in the search criteria. See rfc2978
- limit = None, limit on the number of read emails, useful for actions with a large number of messages, like "move"
- miss_defect = True, miss emails with defects
- miss_no_uid = True, miss emails without uid
- mark_seen = True, mark emails as seen on fetch
- reverse = False, in order from the larger date to the smaller
- headers_only = False, get only email headers (without text, html, attachments)
- bulk = False, False - fetch each message separately per N commands - low memory consumption, slow; True - fetch all messages per 1 command - high memory consumption, fast
BaseMailBox.<action> - copy, move, delete, flag, seen
BaseMailBox.folder - folder manager
BaseMailBox.search - search mailbox for matching message numbers (this is not uids)
BaseMailBox.box - imaplib.IMAP4/IMAP4_SSL client instance.
Message and Attachment public attributes are cached by functools.lru_cache
for msg in mailbox.fetch(): # iter: imap_tools.Message
msg.uid # str or None: '123'
msg.subject # str: 'some subject 你 привет'
msg.from_ # str: 'Sender.Bartölke@ya.ru'
msg.to # tuple: ('iam@goo.ru', 'friend@ya.ru', )
msg.cc # tuple: ('cc@mail.ru', )
msg.bcc # tuple: ('bcc@mail.ru', )
msg.reply_to # tuple: ('reply_to@mail.ru', )
msg.date # datetime.datetime: 1900-1-1 for unparsed, may be naive or with tzinfo
msg.date_str # str: original date - 'Tue, 03 Jan 2017 22:26:59 +0500'
msg.text # str: 'Hello 你 Привет'
msg.html # str: '<b>Hello 你 Привет</b>'
msg.flags # tuple: ('SEEN', 'FLAGGED', 'ENCRYPTED')
msg.headers # dict: {'Received': ('from 1.m.ru', 'from 2.m.ru'), 'AntiVirus': ('Clean',)}
msg.size_rfc822 # int: 20664
msg.size # int: 20377
for att in msg.attachments: # list: imap_tools.Attachment
att.filename # str: 'cat.jpg'
att.payload # bytes: b'\xff\xd8\xff\xe0\'
att.content_id # str: 'part45.06020801.00060008@mail.ru'
att.content_type # str: 'image/jpeg'
att.content_disposition # str: 'inline'
att.part # email.message.Message: original object
att.size # int: 17361
msg.obj # email.message.Message: original object
msg.from_values # dict or None: {'email': 'im@ya.ru', 'name': 'Ya 你', 'full': 'Ya 你 <im@ya.ru>'}
msg.to_values # tuple: ({'email': '', 'name': '', 'full': ''},)
msg.cc_values # tuple: ({'email': '', 'name': '', 'full': ''},)
msg.bcc_values # tuple: ({'email': '', 'name': '', 'full': ''},)
msg.reply_to_values # tuple: ({'email': '', 'name': '', 'full': ''},)
This chapter about "criteria" and "charset" arguments of MailBox.fetch.
You can use 3 approaches to build search criteria:
from imap_tools import AND, OR, NOT
mailbox.fetch(AND(subject='weather')) # query, the str-like object
mailbox.fetch('TEXT "hello"') # str
mailbox.fetch(b'TEXT "\xd1\x8f"') # bytes, *charset arg is ignored
The "charset" is argument used for encode criteria to this encoding. You can pass criteria as bytes in desired encoding - charset will be ignored.
Query builder implements all search logic described in rfc3501. See query examples.
Class | Alias | Usage | Arguments |
---|---|---|---|
AND | A | combines keys by logical "AND" condition | Search keys (see below) | str |
OR | O | combines keys by logical "OR" condition | Search keys (see below) | str |
NOT | N | invert the result of a logical expression | AND/OR instances | str |
Header | H | for search by headers | name: str, value: str |
UidRange | U | for search by UID range | start: str, end: str |
from imap_tools import A, AND, OR, NOT
# AND
A(text='hello', new=True) # '(TEXT "hello" NEW)'
# OR
OR(text='hello', date=datetime.date(2000, 3, 15)) # '(OR TEXT "hello" ON 15-Mar-2000)'
# NOT
NOT(text='hello', new=True) # 'NOT (TEXT "hello" NEW)'
# complex
A(OR(from_='from@ya.ru', text='"the text"'), NOT(OR(A(answered=False), A(new=True))), to='to@ya.ru')
# encoding
mailbox.fetch(A(subject='привет'), charset='utf8')
# python note: you can't do: A(text='two', NOT(subject='one'))
A(NOT(subject='one'), text='two') # use kwargs after logic classes (args)
Search key table. Key types marked with * can accepts a sequence of values like list, tuple, set or generator.
Key | Types | Results | Description |
---|---|---|---|
answered | bool | ANSWERED|UNANSWERED | with|without the Answered flag |
seen | bool | SEEN|UNSEEN | with|without the Seen flag |
flagged | bool | FLAGGED|UNFLAGGED | with|without the Flagged flag |
draft | bool | DRAFT|UNDRAFT | with|without the Draft flag |
deleted | bool | DELETED|UNDELETED | with|without the Deleted flag |
keyword | str* | KEYWORD KEY | with the specified keyword flag |
no_keyword | str* | UNKEYWORD KEY | without the specified keyword flag |
from_ | str* | FROM "from@ya.ru" | contain specified str in envelope struct's FROM field |
to | str* | TO "to@ya.ru" | contain specified str in envelope struct's TO field |
subject | str* | SUBJECT "hello" | contain specified str in envelope struct's SUBJECT field |
body | str* | BODY "some_key" | contain specified str in body of the message |
text | str* | TEXT "some_key" | contain specified str in header or body of the message |
bcc | str* | BCC "bcc@ya.ru" | contain specified str in envelope struct's BCC field |
cc | str* | CC "cc@ya.ru" | contain specified str in envelope struct's CC field |
date | datetime.date* | ON 15-Mar-2000 | internal date is within specified date |
date_gte | datetime.date* | SINCE 15-Mar-2000 | internal date is within or later than the specified date |
date_lt | datetime.date* | BEFORE 15-Mar-2000 | internal date is earlier than the specified date |
sent_date | datetime.date* | SENTON 15-Mar-2000 | rfc2822 Date: header is within the specified date |
sent_date_gte | datetime.date* | SENTSINCE 15-Mar-2000 | rfc2822 Date: header is within or later than the specified date |
sent_date_lt | datetime.date* | SENTBEFORE 1-Mar-2000 | rfc2822 Date: header is earlier than the specified date |
size_gt | int >= 0 | LARGER 1024 | rfc2822 size larger than specified number of octets |
size_lt | int >= 0 | SMALLER 512 | rfc2822 size smaller than specified number of octets |
new | True | NEW | have the Recent flag set but not the Seen flag |
old | True | OLD | do not have the Recent flag set |
recent | True | RECENT | have the Recent flag set |
all | True | ALL | all, criteria by default |
uid | iter(str)/str/U | UID 1,2,17 | corresponding to the specified unique identifier set |
header | H(str, str)* | HEADER "A-Spam" "5.8" | have a header that contains the specified str in the text |
gmail_label | str* | X-GM-LABELS "label1" | have this gmail label. |
Server side search notes:
- For string search keys a message matches if the string is a substring of the field. The matching is case-insensitive.
- When searching by dates - email's time and timezone are disregarding.
First of all read about uid at rfc3501.
You can use 2 approaches to perform these operations:
- "in bulk" - Perform IMAP operation for message set per 1 command
- "by one" - Perform IMAP operation for each message separately per N commands
MailBox.fetch generator instance passed as the first argument to any action will be implicitly converted to uid list.
For actions with a large number of messages imap command may be too large and will cause an exception, use 'limit' argument for fetch in this case.
with MailBox('imap.mail.com').login('test@mail.com', 'pwd', initial_folder='INBOX') as mailbox:
# COPY all messages from current folder to folder1, *by one
for msg in mailbox.fetch():
res = mailbox.copy(msg.uid, 'INBOX/folder1')
# MOVE all messages from current folder to folder2, *in bulk (implicit creation of uid list)
mailbox.move(mailbox.fetch(), 'INBOX/folder2')
# DELETE all messages from current folder, *in bulk (explicit creation of uid list)
mailbox.delete([msg.uid for msg in mailbox.fetch()])
# FLAG unseen messages in current folder as Answered and Flagged, *in bulk.
flags = (imap_tools.MailMessageFlags.ANSWERED, imap_tools.MailMessageFlags.FLAGGED)
mailbox.flag(mailbox.fetch(AND(seen=False)), flags, True)
# SEEN: mark all messages sent at 05.03.2007 in current folder as unseen, *in bulk
mailbox.seen(mailbox.fetch("SENTON 05-Mar-2007"), False)
with MailBox('imap.mail.com').login('test@mail.com', 'pwd') as mailbox:
# LIST
for folder_info in mailbox.folder.list('INBOX'):
print(folder_info) # {'name': 'INBOX|cats', 'delim': '|', 'flags': ('\\Unmarked', '\\HasChildren')}
# SET
mailbox.folder.set('INBOX')
# GET
current_folder = mailbox.folder.get()
# CREATE
mailbox.folder.create('folder1')
# EXISTS
is_exists = mailbox.folder.exists('folder1')
# RENAME
mailbox.folder.rename('folder1', 'folder2')
# DELETE
mailbox.folder.delete('folder2')
# STATUS
folder_status = mailbox.folder.status('some_folder')
print(folder_status) # {'MESSAGES': 41, 'RECENT': 0, 'UIDNEXT': 11996, 'UIDVALIDITY': 1, 'UNSEEN': 5}
Custom lib exceptions here: errors.py.
History of important changes: release_notes.rst
If you found a bug or have a question, please let me know - create merge request or issue.
- Excessive low level of imaplib library.
- Other libraries contain various shortcomings or not convenient.
- Open source projects make world better.
Big thanks to people who helped develop this library:
shilkazx, somepad, 0xThiebaut, TpyoKnig, parchd-1, dojasoncom, RandomStrangerOnTheInternet, jonnyarnold, Mitrich3000, audemed44, mkalioby, atlas0fd00m, unqx, daitangio, upils, Foosec, frispete, PH89, amarkham09, nixCodeX, backelj, ohayak, mwherman95926, andyfensham, mike-code, aknrdureegaesr, ktulinger, SamGenTLEManKaka, devkral, tnusraddinov, thepeshka, shofstet
💰 You may thank me, if this library helped you.