Skip to content

sageteamorg/python-sage-imap

Python Sage IMAP

PyPI version Python License: MIT codecov Documentation

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 stdlib imaplib; async via optional [async] extra (aioimaplib).

Why this library?

Python Sage IMAP targets applications that need reliable email access beyond thin imaplib wrappers:

  • UID-first search, fetch, move, and flag operations via IMAPMailboxUIDService and IMAPSession
  • Streaming fetch with iter_uid_fetch / iter_messages and ParseMode (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 parityAsyncIMAPSession under sage_imap.aio (install [async])
  • IMAP ORM (optional) — manager/queryset layer, Pydantic schemas, multi-tenant account_id, CONDSTORE checkpoints (pip install python-sage-imap[orm])

Installation

pip install python-sage-imap

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

Development

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

Set IMAP_HOST, IMAP_USER, IMAP_PASSWORD, and optionally IMAP_PORT. See docker/mailcow/README.md.

Quick start (sync)

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

Quick start (async)

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.

Quick start (IMAP ORM)

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

Sync vs async

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.

Lower-level API

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.

Examples

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.

Configuration

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

Documentation

Testing

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

Contributing

See CONTRIBUTING.md and SECURITY.md.

make setup-dev
make test
make lint

License

MIT — see LICENSE.

Support

About

Managing IMAP connections and performing various email operations.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages