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

160 Stars 26 Forks Apache License 2.0 267 Commits 1 Opened issues


Work with email and mailbox by IMAP

Services available


Need anything else?

Contributors list



Work with email and mailbox by IMAP:

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

.. image::

=============== =============================================================== Python version 3.3+ License Apache-2.0 PyPI IMAP RFC VERSION 4rev1 - EMAIL RFC Internet Message Format - =============== ===============================================================

.. contents::



$ pip install imap-tools


Basic ^^^^^ .. code-block:: python

from imap_tools import MailBox, AND

get list of email subjects from INBOX folder

with MailBox('').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('') 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

BaseMailBox.fetch - email message generator, first searches email nums by criteria, then fetch and yields

  • 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
  • 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. -

copy, move, delete, flag, seen, append 

BaseMailBox.folder -

folder manager 
_ - search mailbox for matching message numbers (this is not uids) - 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():  # iter: imap_tools.MailMessage
    msg.uid          # str or None: '123'
    msg.subject      # str: 'some subject 你 привет'
    msg.from_        # str: 'Sender.Bartö[email protected]'           # tuple: ('[email protected]', '[email protected]', )           # tuple: ('[email protected]', )
    msg.bcc          # tuple: ('[email protected]', )
    msg.reply_to     # tuple: ('[email protected]', )         # 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', 'from'), 'anti-virus': ('Clean',)}
    msg.size_rfc822  # int: 20664 bytes - size info from server (*useful with headers_only arg)
    msg.size         # int: 20377 bytes

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      # dict or None: {'email': '[email protected]', 'name': 'Ya 你', 'full': 'Ya 你 <im>'}
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': ''},)

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

This chapter about "criteria" and "charset" arguments of MailBox.fetch.

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 criteria as bytes in desired encoding - charset will be ignored.

Query builder implements all search logic described in


======== ===== ========================================== ============================================================ 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 ======== ===== ========================================== ============================================================

.. code-block:: python

from imap_tools import A, AND, OR, NOT
A(text='hello', new=True)  # '(TEXT "hello" NEW)'
# OR
OR(text='hello',, 3, 15))  # '(OR TEXT "hello" ON 15-Mar-2000)'
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]')
# 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)

See more

query examples 

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

with/without the Answered flag seen bool
with/without the Seen flag flagged bool
with/without the Flagged flag draft bool
with/without the Draft flag deleted bool
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* ON 15-Mar-2000 internal date is within specified date dategte* SINCE 15-Mar-2000 internal date is within or later than the specified date datelt* BEFORE 15-Mar-2000 internal date is earlier than the specified date sentdate* SENTON 15-Mar-2000 rfc2822 Date: header is within the specified date sentdategte* SENTSINCE 15-Mar-2000 rfc2822 Date: header is within or later than the specified date sentdatelt* 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 

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 exception at server side, use 'limit' argument for fetch in this case.

.. code-block:: python

with MailBox('').login('[email protected]', '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: flag as unseen all messages sent at 05.03.2007 in current folder, *in bulk
mailbox.seen(mailbox.fetch("SENTON 05-Mar-2007"), False)

# 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(  # *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('').login('[email protected]', 'pwd') as mailbox:
    # LIST
    for f in mailbox.folder.list('INBOX'):
        print(f)  # {'name': 'INBOX|cats', 'delim': '|', 'flags': ('\\Unmarked', '\\HasChildren')}
    # SET
    # GET
    current_folder = mailbox.folder.get()
    # CREATE
    # EXISTS
    is_exists = mailbox.folder.exists('folder1')
    # RENAME
    mailbox.folder.rename('folder1', 'folder2')
    # DELETE
    # STATUS
    stat = mailbox.folder.status('some_folder')
    print(stat)  # {'MESSAGES': 41, 'RECENT': 0, 'UIDNEXT': 11996, 'UIDVALIDITY': 1, 'UNSEEN': 5}

Exceptions ^^^^^^^^^^

Custom lib exceptions here: 

Release notes

History of important changes:



If you found a bug or have a question, please let me know - create merge request or issue.


  • Excessive low level of
  • Other libraries contain various shortcomings or not convenient.
  • Open source projects make world better.


Big thanks to people who helped develop this library:


💰 You may

_, 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.