imap_tools
A high-level Python library for reading, searching, and managing email over IMAP without hand-rolling imaplib calls.
Repository Health
Technical Analysis
imap_tools wraps Python’s low-level imaplib module in a high-level API for working with email over IMAP. It provides three connection classes for SSL/TLS, plaintext, and STARTTLS connections, a fluent query builder (AND/OR/NOT/Header/UidRange) that maps directly onto RFC 3501 search keys, and a MailMessage object that exposes parsed subject, sender, recipients, body, attachments, and flags as lazily-cached properties instead of raw email.message.Message internals.
Beyond fetching, it covers the full set of mailbox actions a real integration needs: copying, moving, deleting, and flagging messages (with automatic chunking for large UID sets), appending new messages, and a complete folder manager (list, create, rename, subscribe, delete, status). An IDLE manager supports start/poll/stop/wait for real-time new-mail notifications. The library has zero external runtime dependencies, ships a py.typed marker for type checkers, and is tested across every supported CPython version from 3.8 through 3.14 (including the free-threaded 3.14t build) via tox.
What You Get
- Three mailbox classes (
MailBox,MailBoxUnencrypted,MailBoxStartTls) covering SSL/TLS, plaintext, and STARTTLS connections, pluslogin,login_utf8, andxoauth2authentication - A composable query builder (
AND/OR/NOT, aliasedA/O/N) that generates correct RFC 3501 search strings from Python kwargs instead of hand-built IMAP syntax MailMessageobjects with lazily-cached properties for subject, from/to/cc/bcc, date, text, html, flags, headers, and parsedMailAttachmentobjects- Mailbox actions —
fetch,copy,move,delete,flag,append— with automatic UID chunking to avoid oversized IMAP commands on large mailboxes - A full folder manager (
list,set,get,create,exists,rename,subscribe,delete,status) and an IDLE manager (start,poll,stop,wait) for real-time new-mail notifications - Typed exceptions per failed operation (
MailboxLoginError,MailboxFetchError,MailboxMoveError, etc.) instead of raw imaplib response tuples
Common Use Cases
- Polling a shared inbox and processing unseen messages (invoices, support tickets, form submissions) into another system
- Building an email-to-ticket or email-to-task pipeline that reads attachments and body text from incoming mail
- Bulk mailbox maintenance — moving, flagging, or deleting messages in batches based on search criteria (date range, sender, subject)
- Archiving or backing up mail from one IMAP account by fetching messages and re-appending them into another mailbox/folder
- Real-time inbox monitoring with IDLE, triggering a handler as soon as new mail arrives instead of polling on an interval
Under The Hood
Architecture
A BaseMailBox class in mailbox.py is the central abstraction; three subclasses — MailBox (SSL/TLS, port 993), MailBoxUnencrypted (plaintext, port 143), and MailBoxStartTls — select the connection strategy purely by overriding _get_mailbox_client(). Each instance composes a MailBoxFolderManager (folder.py) and IdleManager (idle.py) at construction time, both holding a back-reference to the owning mailbox rather than inheriting from it. Message parsing is fully decoupled from the connection layer: fetch() resolves UIDs via uids(), issues raw client.uid('fetch', ...) calls, and wraps each raw response tuple in a MailMessage (via the overridable email_message_class hook), which exposes attributes through functools.cached_property for lazy, memoized parsing — the same lazy pattern reappears in the LazyHeaders mapping. Query construction lives in an entirely separate module (query.py), where AND/OR/NOT/Header/UidRange classes compose into opaque search-criteria strings consumed by uids()/fetch() with no dependency on mailbox internals. Error handling is centralized through check_command_status(result, SpecificError), called after every imaplib operation to translate raw tag/response tuples into typed exceptions from errors.py.
Tech Stack
Pure Python standard library — imaplib, email, functools — with zero third-party runtime dependencies (setup.py declares no install_requires). Supports Python 3.8 through 3.14, including the free-threaded 3.14t build, with version-conditional client construction (imaplib.IMAP4/IMAP4_SSL argument signatures changed across 3.9 and 3.12). Built with classic setuptools (no pyproject.toml), tested across every supported interpreter via tox and Python’s built-in unittest runner (not pytest), linted with ruff under an unusually strict rule selection (flake8-bandit security checks, pep8-naming, bugbear, isort), and ships a py.typed marker for static type checkers. Distributed on PyPI as imap-tools.
Code Quality
The tests/ directory holds roughly 850 lines across eight unittest-based modules (mailbox, folders, idle, query, message, utils, imap_utf7, connection), plus a tests/_disabled directory holding server-dependent tests excluded from routine runs. Error handling is explicit throughout — each failing operation raises a specific typed exception rather than swallowing or generically re-raising. Naming is consistent snake_case, type hints (Optional, Union, Iterable) are used across public signatures, and the strict ruff configuration suggests active lint discipline. No GitHub Actions workflow is present in the repository, so CI enforcement, if any, happens outside GitHub (the project also maintains a mirror on gitflic.ru).
API Design
The public surface is deliberately small: three constructor classes support the context-manager pattern, so a full session reads as with MailBox(host).login(user, pwd) as mailbox: mailbox.fetch(...). The query builder maps Python kwargs directly onto RFC 3501 search keys (A(subject='x', seen=False)), removing the need to hand-assemble IMAP search strings for common cases while still accepting raw str/bytes for anything the builder doesn’t cover. MailMessage exposes parsed attributes as plain properties, collapsing what would otherwise be manual email.message.Message traversal into a single for-loop. The one surprising ergonomic tradeoff is from_ (trailing underscore to dodge the from keyword) and the library’s reliance on monkeypatching imaplib internals (_MAXLINE, the IDLE command table) — effective, but worth knowing about if you’re debugging alongside raw imaplib behavior.