Need help with imap_tools?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

ikvk
269 Stars 36 Forks Apache License 2.0 314 Commits 3 Opened issues

Description

Work with email by IMAP

Services available

!
?

Need anything else?

Contributors list

.. http://docutils.sourceforge.net/docs/user/rst/quickref.html

imap_tools 📧

Work with email by IMAP:

  • Parsed email message attributes
  • Query builder for searching emails
  • Actions with emails: copy, delete, flag, move, append
  • Actions with folders: list, set, get, create, exists, rename, subscribe, delete, status
  • No dependencies

.. image:: https://img.shields.io/pypi/dm/imap_tools.svg?style=social

=============== =============================================================== Python version 3.5+ 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::

Installation

::

$ pip install imap-tools

Guide

Basic ^^^^^

Info about lib are at: this page, issues, pull requests, examples, source, stackoverflow.com

.. code-block:: python

from imap_tools import MailBox, AND

get list of email subjects from INBOX folder

with MailBox('imap.mail.com').login('[email protected]', 'pwd') 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('[email protected]', 'pwd', 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. TLS connection

example 
_.

BaseMailBox.fetch - first searches email nums by criteria in current folder, then fetch and yields

MailMessage 
_:
  • 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"
  • missnouid = 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.uids - search mailbox for matching message uids in current folder, returns [str | None]

  • criteria = 'ALL', message search criteria,
    query builder 
    _
  • charset = 'US-ASCII', indicates charset of the strings that appear in the search criteria. See rfc2978
  • missnouid = True, not add None values to result when uid item not matched to pattern

BaseMailBox. -

copy, move, delete, flag, append 
_

BaseMailBox.folder -

folder manager 
_

BaseMailBox.numbers - search mailbox for matching message numbers in current folder, returns [str]

BaseMailBox.box - imaplib.IMAP4/IMAP4_SSL client instance.

Email attributes ^^^^^^^^^^^^^^^^

MailMessage and MailAttachment public attributes are cached by functools.lru_cache

.. code-block:: python

for msg in mailbox.fetch():  # generator: imap_tools.MailMessage
    msg.uid          # str | None: '123'
    msg.subject      # str: 'some subject 你 привет'
    msg.from_        # str: 'Bartö[email protected]'
    msg.to           # tuple: ('[email protected]', '[email protected]', )
    msg.cc           # tuple: ('[email protected]', )
    msg.bcc          # tuple: ('[email protected]', )
    msg.reply_to     # tuple: ('[email protected]', )
    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: 'Hello 你 Привет'
    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: '[email protected]'
    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='[email protected]', full='Ya <im>')

Search criteria ^^^^^^^^^^^^^^^

This chapter about "criteria" and "charset" arguments of MailBox methods: fetch, uids, numbers

You can use 3 approaches to build search criteria:

.. code-block:: python

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 the criteria as bytes in the desired encoding - in this case, the encoding will be ignored.

.. code-block:: python

mailbox.uids(A(subject='жёлтый'), charset='utf8')

Query builder implements all search logic described in

rfc3501 
_. It uses this classes:

======== ===== ========================================== ====================================== Class Alias Usage Arguments ======== ===== ========================================== ====================================== AND A combines keys by logical "AND" condition Search keys (see table below) | str OR O combines keys by logical "OR" condition Search keys (see table 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 ======== ===== ========================================== ======================================

See

query examples 
_. A few examples:

.. code-block:: python

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_='[email protected]', text='"the text"'), NOT(OR(A(answered=False), A(new=True))), to='[email protected]')
# 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 nokeyword str* UNKEYWORD KEY without the specified keyword flag `from
str*             FROM
"[email protected]"
contain specified str in envelope struct's FROM field
to             str*             TO
"[email protected]"
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
"[email protected]"
contain specified str in envelope struct's BCC field
cc             str*             CC
"[email protected]"` contain specified str in envelope struct's CC field date datetime.date* ON 15-Mar-2000 internal date is within specified date dategte datetime.date* SINCE 15-Mar-2000 internal date is within or later than the specified date datelt datetime.date* BEFORE 15-Mar-2000 internal date is earlier than the specified date sentdate datetime.date* SENTON 15-Mar-2000 rfc2822 Date: header is within the specified date sentdategte datetime.date* SENTSINCE 15-Mar-2000 rfc2822 Date: header is within or later than the specified date sentdatelt datetime.date* SENTBEFORE 1-Mar-2000 rfc2822 Date: header is earlier than the specified date sizegt int >= 0 LARGER 1024 rfc2822 size larger than specified number of octets sizelt 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 gmaillabel 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.

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

Get uids using 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.

.. code-block:: python

with MailBox('imap.mail.com').login('[email protected]', '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 ^^^^^^^^^^^^^^^^^^^^ .. code-block:: python

with MailBox('imap.mail.com').login('[email protected]', '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}

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:

  1. Look for answer at: this page, issues, pull requests, examples, source, RFCs, stackoverflow.com, internet.
  2. 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 
_

Donate

💰 You may

donate 
_, if this library helped you.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.