Skip to main content

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

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

Python version

3.8+

License

Apache-2.0

PyPI

https://pypi.python.org/pypi/imap_tools/

RFC

IMAP4.1, EMAIL, IMAP related RFCs

Repo mirror

https://gitflic.ru/project/ikvk/imap-tools

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

Description of this^ example.

MailBox, MailBoxStartTls, MailBoxUnencrypted - for create mailbox client. TLS example.

BaseMailBox.<auth> - login, login_utf8, xoauth2, logout - authentication functions, 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 IMAP 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 List[str], 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 - message actions.

BaseMailBox.folder.<action> - list, set, get, create, exists, rename, subscribe, delete, status - folder manager.

BaseMailBox.idle.<action> - start, poll, stop, wait - idle manager.

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

BaseMailBox.numbers_to_uids - Get message uids by message numbers, 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.cached_property

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,)

    # imap_tools.EmailAddress example:
    # EmailAddress(name='Ya', email='im@ya.ru')  # has "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

A few examples below. See detailed query examples.

from imap_tools import A, AND, OR, NOT

# AND: subject contains "cat" AND message is unseen
A(subject='cat', seen=False)

# OR: header or body contains "hello" OR date equal 2000-3-15
OR(text='hello', date=datetime.date(2000, 3, 15))

# NOT: date not in the date list
NOT(OR(date=[dt.date(2019, 10, 1), dt.date(2019, 10, 10)]))

# PYTHON NOTE: you can't do A(text='a', NOT(subject='b')), use kwargs after logic classes (args)
A(NOT(subject='b'), text='a')

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

Key types marked with * can accepts a sequence of values like list, tuple, set or generator - for join by OR.

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 large number of messages IMAP command may be too large and will cause exception at server side:

  • Use chunks arg at copy,move,delete,flag to specify number of UIDs to process at one IMAP command

  • Use limit arg at fetch to limit the number of messages returned

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, move by 100 emails at once
    mailbox.move(mailbox.uids(), 'INBOX/folder2', chunks=100)

    # 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 arg initial_folder 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:

  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, NickC-NZ, mweinelt, lucbouge, JacquelinCharbonnel, stumpylog, dimitrisstr, abionics, link2xt, Docpart, meetttttt, sapristi, thomwiggers, histogal, K900, homoLudenus, sphh, bh, tomasmach, errror, hurricane-dorian

Help the project

  1. Found a bug or figure out how to improve the library - open issue or merge request 🎯

  2. Do not know how to improve library - try to help other open projects that you use ✋

  3. Nowhere to put your money - spend it on your family, friends, loved ones, or people around you 💰

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

imap_tools-1.11.0.tar.gz (46.2 kB view details)

Uploaded Source

Built Distribution

imap_tools-1.11.0-py3-none-any.whl (34.9 kB view details)

Uploaded Python 3

File details

Details for the file imap_tools-1.11.0.tar.gz.

File metadata

  • Download URL: imap_tools-1.11.0.tar.gz
  • Upload date:
  • Size: 46.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.2

File hashes

Hashes for imap_tools-1.11.0.tar.gz
Algorithm Hash digest
SHA256 77b055d301f24e668ff54ad50cc32a36d1579c6aa9b26e5fb6501fb622feb6ea
MD5 ee12ffe861013013feaa6705fe73ed1c
BLAKE2b-256 b0762d74bf4702d7d9fb2dd056e058929961a05389be47b990f3275e8596012e

See more details on using hashes here.

File details

Details for the file imap_tools-1.11.0-py3-none-any.whl.

File metadata

  • Download URL: imap_tools-1.11.0-py3-none-any.whl
  • Upload date:
  • Size: 34.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.2

File hashes

Hashes for imap_tools-1.11.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7c797b421fdf1b898b4ee0042fe02d10037d56f9acacca64086c2af36d830a24
MD5 d787567bb6553c4532317056817eb039
BLAKE2b-256 f98f75524e1a040183cc437332e2de6e8f975c345fff8b5aaa35e0d20dec24f9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page