Work with email by IMAP
Project description
High level lib for work with email by IMAP:
Basic message operations: fetch, uids, numbers
Parsed email message attributes
Query builder for search criteria
Actions with emails: copy, delete, flag, move, append
Actions with folders: list, set, get, create, exists, rename, subscribe, delete, status
IDLE commands: start, poll, stop, wait
Exceptions on failed IMAP operations
No external dependencies, tested
Python version |
3.5+ |
License |
Apache-2.0 |
PyPI |
|
RFC |
|
Repo mirror |
Installation
$ pip install imap-tools
Guide
Basic
Info about lib are at: this page, docstrings, issues, pull requests, examples, source, stackoverflow.com
from imap_tools import MailBox, AND
# Get date, subject and body len of all emails from INBOX folder
with MailBox('imap.mail.com').login('test@mail.com', 'pwd') as mailbox:
for msg in mailbox.fetch():
print(msg.date, msg.subject, len(msg.text or msg.html))
MailBox, MailBoxTls, MailBoxUnencrypted - for create mailbox client. TLS example.
BaseMailBox.<auth> - login, login_utf8, xoauth2, logout - authentication functions, they support context manager.
BaseMailBox.fetch - first searches email uids by criteria in current folder, then fetch and yields MailMessage, args:
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”. May be int or slice.
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; int - fetch all messages by bulks of the specified size, for 20 messages and bulk=5 -> 4 commands
sort = None, criteria for sort messages on server, use SortCriteria constants. Charset arg is important for sort
BaseMailBox.uids - search mailbox for matching message uids in current folder, returns [str | None], None when MailMessage.from_bytes used, args:
criteria = ‘ALL’, message search criteria, query builder
charset = ‘US-ASCII’, indicates charset of the strings that appear in the search criteria. See rfc2978
sort = None, criteria for sort messages on server, use SortCriteria constants. Charset arg is important for sort
BaseMailBox.<action> - copy, move, delete, flag, append
BaseMailBox.folder - folder manager
BaseMailBox.idle - idle manager
BaseMailBox.numbers - search mailbox for matching message numbers in current folder, returns [str]
BaseMailBox.client - imaplib.IMAP4/IMAP4_SSL client instance.
Email attributes
Email has 2 basic body variants: text and html. Sender can choose to include: one, other, both or neither(rare).
MailMessage and MailAttachment public attributes are cached by functools.lru_cache
for msg in mailbox.fetch(): # generator: imap_tools.MailMessage
msg.uid # str | None: '123'
msg.subject # str: 'some subject 你 привет'
msg.from_ # str: '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'), 'anti-virus': ('Clean',)}
msg.size_rfc822 # int: 20664 bytes - size info from server (*useful with headers_only arg)
msg.size # int: 20377 bytes - size of received message
for att in msg.attachments: # list: imap_tools.MailAttachment
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 bytes
msg.obj # email.message.Message: original object
msg.from_values # imap_tools.EmailAddress | None
msg.to_values # tuple: (imap_tools.EmailAddress,)
msg.cc_values # tuple: (imap_tools.EmailAddress,)
msg.bcc_values # tuple: (imap_tools.EmailAddress,)
msg.reply_to_values # tuple: (imap_tools.EmailAddress,)
# EmailAddress(name='Ya', email='im@ya.ru') # "full" property = 'Ya <im@ya.ru>'
Search criteria
The “criteria” argument is used at fetch, uids, numbers methods of MailBox. Criteria can be of three types:
from imap_tools import AND
mailbox.fetch(AND(subject='weather')) # query, the str-like object
mailbox.fetch('TEXT "hello"') # str
mailbox.fetch(b'TEXT "\xd1\x8f"') # bytes
Use “charset” argument for encode criteria to the desired encoding. If criteria is bytes - encoding will be ignored.
mailbox.uids(A(subject='жёлтый'), charset='utf8')
Query builder implements all search logic described in rfc3501. It uses this classes:
Class |
Alias |
Description |
Arguments |
---|---|---|---|
AND |
A |
Combine conditions by logical “AND” |
Search keys (see table below) | str |
OR |
O |
Combine conditions by logical “OR” |
Search keys (see table below) | str |
NOT |
N |
Invert the result of a logical expression |
AND/OR instances | str |
Header |
H |
Header value for search by header key |
name: str, value: str |
UidRange |
U |
UID range value for search by uid key |
start: str, end: str |
See query examples. A few examples:
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')
# python note: you can't do: A(text='two', NOT(subject='one'))
A(NOT(subject='one'), text='two') # use kwargs after logic classes (args)
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.
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 |
Actions with emails
First of all read about UID at rfc3501.
Action’s uid_list arg may takes:
str, that is comma separated uids
Sequence, that contains str uids
To get uids, use the maibox methods: uids, fetch.
For actions with a large number of messages imap command may be too large and will cause exception at server side, use ‘limit’ argument for fetch in this case.
with MailBox('imap.mail.com').login('test@mail.com', 'pwd', initial_folder='INBOX') as mailbox:
# COPY messages with uid in 23,27 from current folder to folder1
mailbox.copy('23,27', 'folder1')
# MOVE all messages from current folder to INBOX/folder2
mailbox.move(mailbox.uids(), 'INBOX/folder2')
# DELETE messages with 'cat' word in its html from current folder
mailbox.delete([msg.uid for msg in mailbox.fetch() if 'cat' in msg.html])
# FLAG unseen messages in current folder as \Seen, \Flagged and TAG1
flags = (imap_tools.MailMessageFlags.SEEN, imap_tools.MailMessageFlags.FLAGGED, 'TAG1')
mailbox.flag(mailbox.uids(AND(seen=False)), flags, True)
# APPEND: add message to mailbox directly, to INBOX folder with \Seen flag and now date
with open('/tmp/message.eml', 'rb') as f:
msg = imap_tools.MailMessage.from_bytes(f.read()) # *or use bytes instead MailMessage
mailbox.append(msg, 'INBOX', dt=None, flag_set=[imap_tools.MailMessageFlags.SEEN])
Actions with folders
BaseMailBox.login/xoauth2 has initial_folder arg, that is “INBOX” by default, use None for not set folder on login.
with MailBox('imap.mail.com').login('test@mail.com', 'pwd') as mailbox:
# LIST: get all subfolders of the specified folder (root by default)
for f in mailbox.folder.list('INBOX'):
print(f) # FolderInfo(name='INBOX|cats', delim='|', flags=('\\Unmarked', '\\HasChildren'))
# SET: select folder for work
mailbox.folder.set('INBOX')
# GET: get selected folder
current_folder = mailbox.folder.get()
# CREATE: create new folder
mailbox.folder.create('INBOX|folder1')
# EXISTS: check is folder exists (shortcut for list)
is_exists = mailbox.folder.exists('INBOX|folder1')
# RENAME: set new name to folder
mailbox.folder.rename('folder3', 'folder4')
# SUBSCRIBE: subscribe/unsubscribe to folder
mailbox.folder.subscribe('INBOX|папка два', True)
# DELETE: delete folder
mailbox.folder.delete('folder4')
# STATUS: get folder status info
stat = mailbox.folder.status('some_folder')
print(stat) # {'MESSAGES': 41, 'RECENT': 0, 'UIDNEXT': 11996, 'UIDVALIDITY': 1, 'UNSEEN': 5}
IDLE workflow
IDLE logic are in mailbox.idle manager, its methods are in the table below:
Method |
Description |
Arguments |
---|---|---|
start |
Switch on mailbox IDLE mode |
|
poll |
Poll for IDLE responses |
timeout: Optional[float] |
stop |
Switch off mailbox IDLE mode |
|
wait |
Switch on IDLE, poll responses, switch off IDLE on response, return responses |
timeout: Optional[float] |
from imap_tools import MailBox, A
# waiting for updates 60 sec, print unseen immediately if any update
with MailBox('imap.my.moon').login('acc', 'pwd', 'INBOX') as mailbox:
responses = mailbox.idle.wait(timeout=60)
if responses:
for msg in mailbox.fetch(A(seen=False)):
print(msg.date, msg.subject)
else:
print('no updates in 60 sec')
Read docstrings and see detailed examples.
Exceptions
Most lib server actions raises exception if result is marked as not success.
Custom lib exceptions here: errors.py.
Release notes
History of important changes: release_notes.rst
Contribute
If you found a bug or have a question, then:
Look for answer at: this page, issues, pull requests, examples, source, RFCs, stackoverflow.com, internet.
And only then - create merge request or issue.
Reasons
Excessive low level of imaplib library.
Other libraries contain various shortcomings or not convenient.
Open source projects make world better.
Thanks
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, the7erm, c0da, dev4max, ascheucher, Borutia, nathan30, daniel55411, rcarmo, bhernacki, ilep, ThKue, repodiac, tiuub, Yannik, pete312, edkedk99, UlisseMini, Nicarex, RanjithNair1980, NickC-NZ, mweinelt, lucbouge, JacquelinCharbonnel, stumpylog, dimitrisstr, abionics, link2xt, Docpart, meetttttt, sapristi, thomwiggers, histogal, K900, homoLudenus
Help the project
Found a bug or figure out how to improve the library - open issue or merge request 🎯
Do not know how to improve library - try to help other open projects that you use ✋
Nowhere to put your money - spend it on your family, friends, loved ones, or people around you 💰
Star the project ⭐
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file imap-tools-1.7.4.tar.gz
.
File metadata
- Download URL: imap-tools-1.7.4.tar.gz
- Upload date:
- Size: 36.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/24.0 requests/2.27.1 requests-toolbelt/0.8.0 urllib3/1.26.8 tqdm/4.29.1 importlib-metadata/3.7.3 keyring/20.0.0 rfc3986/1.4.0 colorama/0.4.3 CPython/3.6.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ad3b4ca29141c1fba28e697f4c93bdc1cd4c05c44d60ee89dec4a3ac308f8acf |
|
MD5 | c77dc6957a85d36d270f95ad98441415 |
|
BLAKE2b-256 | 3f5cfd1d399bbbc20b578a9232429ab36bba1c378caaf9901655490588bcbd71 |
File details
Details for the file imap_tools-1.7.4-py3-none-any.whl
.
File metadata
- Download URL: imap_tools-1.7.4-py3-none-any.whl
- Upload date:
- Size: 33.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/24.0 requests/2.27.1 requests-toolbelt/0.8.0 urllib3/1.26.8 tqdm/4.29.1 importlib-metadata/3.7.3 keyring/20.0.0 rfc3986/1.4.0 colorama/0.4.3 CPython/3.6.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | de66fb949d84f9cd411e4937daef17604db629c312954ebb1edb0ba88caf26c5 |
|
MD5 | ce623593e3f6a76bfdd55d537c5fa988 |
|
BLAKE2b-256 | 5955c48dd5bfe11b955b012c87bf06ea1891ae3e8d863d1bbd58e66a22ca3b0b |