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
class ImapError(Exception)Raised when the channel's IMAP configuration or credential is unusable.
_MailboxWork
@dataclass
class _MailboxWork()One selected mailbox's remaining fetch plan for the current run.
take
def take(count: int) -> list[int]Remove and return the next count UIDs of this mailbox's plan.
ImapChannelBackend
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__
def __init__(integration: object) -> NoneBind to the channel row and start with no in-run paging state.
fetch_messages
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
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
def partition_cursor_slice(partition: str) -> tuple[tuple[str, ...], Any]Return one mailbox's cursor fragment — ("mailboxes", name) -> watermark.
close
def close() -> NoneLog out quietly (the ChannelBackend teardown hook); the session may already be gone.