Skip to content

IMAP

Each IMAP mailbox is configured by name. The name becomes the env-var prefix and the source id passed to corpus ingest. For corpus ingest imap:example:

CORPUS_IMAP_EXAMPLE_HOST=imap.example.com
CORPUS_IMAP_EXAMPLE_PORT=993
CORPUS_IMAP_EXAMPLE_USER=user@example.com
CORPUS_IMAP_EXAMPLE_PASSWORD=...
CORPUS_IMAP_EXAMPLE_FOLDERS=INBOX,Archive   # optional; default is all folders
CORPUS_IMAP_EXAMPLE_SSL=true                 # optional, default true

FOLDERS selects which mailboxes to catalog. Give a comma-separated list to restrict it, or leave it unset (or set all / *) to discover and catalog every selectable folder on the account.

Incremental sync is tracked per folder in the sync_state table: the cursor is a JSON map of folder to UIDVALIDITY:UID, so each folder resyncs independently. A UIDVALIDITY change resets that folder's UID window.

A record's id is <folder>:<UIDVALIDITY>:<UID>, and its uri is the RFC 5092 form imap://<host>/<folder>;UIDVALIDITY=<v>/;UID=<uid>. A UID is unique only within one folder and one UIDVALIDITY epoch: after a change the server may hand an old UID to a new message, and an id without the validity would match the stored message and be skipped. Messages stored under the previous epoch stay as they are.

Folders are opened read-only (EXAMINE), so fetching never sets \Seen or changes the mailbox in any other way.

Stalwart and other self-hosted servers

Any IMAPS server works, Stalwart included. For a shared or role mailbox, log in as the mailbox's own principal (its service password) instead of as a member with access to it. Stalwart group principals log in by their bare principal name (kasserar), not the full address. The fetcher then sees the mailbox as its own INBOX, and access revokes when that password is rotated. Configure one fetcher name per mailbox (imap:kasserar, imap:post, …), so each mailbox keeps its own cursor and can be stored in its own schema or database.