Production-oriented IMAP for Python 3.10+ — UID-first mailbox operations, CONDSTORE sync, IDLE, OAuth2, SPECIAL-USE folders, an optional async API (sage_imap.aio), and an optional IMAP ORM (sage_imap.orm).
Current release:
2.0.0— sync API on stdlibimaplib; async via optional[async]extra (aioimaplib).
Python Sage IMAP targets applications that need reliable email access beyond thin imaplib wrappers:
- UID-first search, fetch, move, and flag operations via
IMAPMailboxUIDServiceandIMAPSession - Streaming fetch with
iter_uid_fetch/iter_messagesandParseMode(headers-only, minimal, full, raw) - Incremental sync with CONDSTORE (
MailboxSyncState,CHANGEDSINCE) - IDLE for push-style mailbox notifications
- OAuth2 with refresh (
OAuth2Config,ensure_access_token) - Resilience — retries, connection pooling (
use_pool=True), metrics, health checks - Async parity —
AsyncIMAPSessionundersage_imap.aio(install[async]) - IMAP ORM (optional) — manager/queryset layer, Pydantic schemas, multi-tenant
account_id, CONDSTORE checkpoints (pip install python-sage-imap[orm])
pip install python-sage-imapAsync support:
pip install python-sage-imap[async]IMAP ORM (manager/queryset API, Pydantic schemas):
pip install python-sage-imap[orm]Async ORM:
pip install python-sage-imap[orm,async]Requirements: Python 3.10+, network access to an IMAP server (TLS recommended).
git clone https://github.com/sageteamorg/python-sage-imap.git
cd python-sage-imap
poetry install
poetry install -E async # optional, for sage_imap.aio tests
poetry run pytest -m "not integration"Integration tests against a Mailcow-compatible stack:
make integration-up
make integration-test
make integration-downSet IMAP_HOST, IMAP_USER, IMAP_PASSWORD, and optionally IMAP_PORT. See docker/mailcow/README.md.
Use IMAPSession as the primary entry point:
from sage_imap import IMAPSession, IMAPSearchCriteria, SpecialUse
with IMAPSession("imap.example.com", "user@example.com", "app-password") as session:
session.select("INBOX")
result = session.search(IMAPSearchCriteria.UNSEEN)
trash = session.special_folder(SpecialUse.TRASH)
for msg in session.iter_messages(result.to_uid_message_set(), batch_size=50):
print(msg.uid, msg.subject)More: docs/SESSION.md · Read the Docs
import asyncio
from sage_imap.aio import AsyncIMAPSession
from sage_imap.helpers.search import IMAPSearchCriteria
async def main():
async with AsyncIMAPSession("imap.example.com", "user@example.com", "secret") as session:
await session.select("INBOX")
result = await session.search(IMAPSearchCriteria.UNSEEN)
async for msg in session.iter_messages(result.to_uid_message_set()):
print(msg.subject)
asyncio.run(main())See docs/ASYNC.md and examples/09_async_session.py.
Optional Django-style managers over live IMAP (no SQL). Requires [orm]:
import os
from sage_imap.orm import ImapAccountConfig, ImapMessage, ImapORM, LoadLevel
from sage_imap.orm.schemas import ImapMessageSummarySchema
config = ImapAccountConfig(
account_id="demo",
host=os.environ["IMAP_HOST"],
username=os.environ["IMAP_USER"],
password=os.environ["IMAP_PASSWORD"],
)
with ImapORM.open("demo", config=config) as orm:
orm.select_mailbox("INBOX")
qs = ImapMessage.objects.filter(unread=True).limit(10).with_load_level(LoadLevel.HEADERS)
for msg in qs.iter():
print(ImapMessageSummarySchema.from_imap_message(msg).model_dump(mode="json"))Tutorial: IMAP ORM tutorial · Examples: examples/10_orm_sync.py, examples/11_orm_async.py
| Topic | Sync | Async |
|---|---|---|
| Import | from sage_imap import IMAPSession |
from sage_imap.aio import AsyncIMAPSession |
| Transport | imaplib + threading.RLock |
aioimaplib + asyncio.Lock |
| Install | pip install python-sage-imap |
pip install python-sage-imap[async] |
| OAuth refresh | stdlib (urllib) |
httpx (or thread fallback) |
| ORM | Sync | Async |
|---|---|---|
| Import | from sage_imap.orm import ImapORM, ImapMessage |
from sage_imap.orm.async_session import AsyncImapORM |
| Install | pip install python-sage-imap[orm] |
pip install python-sage-imap[orm,async] |
Async is not re-exported from top-level sage_imap (by design). See docs/MIGRATION_v2.md when upgrading from 1.x.
Services remain available for fine-grained control:
from sage_imap import IMAPClient, IMAPMailboxUIDService, IMAPSearchCriteria
with IMAPClient("imap.example.com", "user@example.com", "secret") as client:
caps = client.transport.get_capabilities()
mailbox = IMAPMailboxUIDService(client)
mailbox.select("INBOX")
result = mailbox.uid_search(IMAPSearchCriteria.ALL)
for msg in mailbox.iter_uid_fetch(result.to_uid_message_set()):
print(msg.subject)IMAPClient delegates raw imaplib methods (e.g. list, select) when connected; prefer UID services for message operations.
Runnable scripts live under examples/. Configure credentials via environment variables:
export IMAP_HOST=imap.example.com
export IMAP_USER=user@example.com
export IMAP_PASSWORD=secret
poetry run python examples/01_basic_client_usage.py| Script | Topic |
|---|---|
01_basic_client_usage.py |
Client, pooling, metrics |
02_connection_pooling_example.py |
use_pool=True |
03_retry_and_resilience_example.py |
Retries and recovery |
04_monitoring_and_metrics_example.py |
ConnectionMetrics |
05_advanced_client_features.py |
OAuth, TLS, health |
06_mailbox_operations_example.py |
Mailbox CRUD |
07_advanced_mailbox_features.py |
Upload, bulk ops |
08_mailbox_uid_operations.py |
UID search/fetch |
09_async_session.py |
Async session |
10_orm_sync.py |
Sync IMAP ORM |
11_orm_async.py |
Async IMAP ORM |
See examples/README.md.
from sage_imap import ConnectionConfig, IMAPSession, build_ssl_context
config = ConnectionConfig(
host="imap.example.com",
username="user@example.com",
password="secret",
port=993,
use_ssl=True,
timeout=30.0,
max_retries=5,
retry_delay=2.0,
enable_monitoring=True,
enable_background_health=False,
)
with IMAPSession.from_config(config) as session:
session.select("INBOX")- Online: https://python-sage-imap.readthedocs.io/
- IMAP ORM tutorial: https://python-sage-imap.readthedocs.io/en/latest/tutorials/orm/index.html
- Session facade: docs/SESSION.md
- Async: docs/ASYNC.md
- v2 migration: docs/MIGRATION_v2.md
- Changelog: CHANGELOG.md
poetry run pytest -m "not integration"
poetry run pytest tests/aio -m "not integration" # requires -E async
poetry run pytest --cov=sage_imap --cov-report=htmlSee CONTRIBUTING.md and SECURITY.md.
make setup-dev
make test
make lintMIT — see LICENSE.