Skip to content

angee.messaging_integrate_imap.backend

IMAP channel backend: incremental mailbox sync over IMAPClient.

Transport + cursor only — parsing is :mod:.parser, and the idempotent map onto threads/messages/parts is Message.objects.ingest. The sync is strictly read-only against the server: every folder is opened read-only and bodies are fetched with BODY.PEEK, so no \Seen flag is ever set by a sync.

Incremental state lives on bridge.cursor as per-mailbox UID watermarks::

{"mailboxes": {"INBOX": {"uidvalidity": 123456, "last_uid": 4211}}}

Correctness rests on three facts. UIDVALIDITY is checked every run: a changed value invalidates that mailbox's UID space, so its cursor resets and the folder refetches in full — the (platform, external_id) ingest idempotency converges the refetch instead of duplicating it. UIDNEXT (from STATUS, no SELECT) pre-screens each unchanged mailbox so an idle folder costs one round-trip. And the cursor advances only in memory during a run — Bridge.record_sync persists it after the whole run succeeds, so a crash can re-fetch but never skip mail.

fetch_messages follows the seam's paging contract (one bounded batch per call — config["batch_size"], default 200, with body pulls additionally split under a config["max_batch_bytes"] budget). A message over config ["max_message_bytes"] lands header-only with a truncation marker; a message the MIME layer rejects lands through the parser's fallback envelope — mail is never dropped, and UIDs the server fails to answer for are logged rather than silently skipped. Authentication draws on the channel's credential: basic_auth logs in with username/password, oauth refreshes then presents the access token over XOAUTH2 (Gmail, Outlook). The operator-supplied host passes the shared outbound address judgement under the operator-configured-connection policy (integrate.net): self-hosted private hosts work, metadata escapes never do.

ImapError

python
class ImapError(Exception)

Raised when the channel's IMAP configuration or credential is unusable.

_MailboxWork

python
@dataclass
class _MailboxWork()

One selected mailbox's remaining fetch plan for the current run.

take

python
def take(count: int) -> list[int]

Remove and return the next count UIDs of this mailbox's plan.

ImapChannelBackend

python
class ImapChannelBackend(AnymailEmailChannelBackend)

Sync an IMAP account's mailboxes into messaging, one bounded batch at a time.

config keys: host (required), port (defaults per security), security (ssl default / starttls / plain), username (defaults to the credential's username or connected account email), mailboxes (explicit include list; default prefers the \All special-use folder, else everything selectable minus junk/trash/drafts), skip_mailboxes, own_addresses (direction detection), batch_size, max_message_bytes, max_batch_bytes, timeout.

client_class

The protocol client factory — a seam so tests substitute an in-memory server.

__init__

python
def __init__(integration: object) -> None

Bind to the channel row and start with no in-run paging state.

fetch_messages

python
def fetch_messages() -> list[ParsedMessage]

Return the next batch since the cursor; empty once every mailbox drained.

The first call connects, screens each selected mailbox through its cursor, and builds the fetch plan; subsequent calls page through it, advancing the in-memory cursor past each returned chunk. A chunk whose UIDs all vanished server-side between search and fetch yields nothing — the loop then moves on rather than reporting a premature drain.

sync_partitions

python
def sync_partitions() -> tuple[str, ...]

Return the channel's selected mailbox names — one drainable partition each.

Each mailbox owns an independent UID watermark in the cursor, so mailboxes are the natural parallel unit: every partition syncs on its own backend instance and IMAP connection (servers commonly cap per-connection concurrency, not per-account). Connects once to list folders — the same selection :meth:_discover uses — and releases the connection; the partition drains reconnect on their own instances.

partition_cursor_slice

python
def partition_cursor_slice(partition: str) -> tuple[tuple[str, ...], Any]

Return one mailbox's cursor fragment — ("mailboxes", name) -> watermark.

close

python
def close() -> None

Log out quietly (the ChannelBackend teardown hook); the session may already be gone.

Released under the AGPL-3.0 License.