Mail-source adapter
July 7, 2026 · View on GitHub
Reference adapter for a generic IMAP mailbox as a backend for the
security-issue-import family of skills. Stub status — this
document describes the contract; the concrete CLI / MCP wiring is
TBD and will land when an adopter actually wires IMAP in. The
contract here lets that adopter know what they need to provide
without re-deriving it from scratch.
See ../contract.md for the abstract
mail-source-backend operations + capability matrix + adopter
resolution rules this adapter conforms to.
Capability claim
| Operation | Supported? | Notes |
|---|---|---|
list_recent_threads(list, since) | ✓ | IMAP SEARCH SINCE <date> against the inbox / <security-list> subfolder |
read_thread(thread_id) | ✓ | Fetch all messages whose References: / In-Reply-To: chain reaches the thread root; thread ID = root Message-ID |
list_drafts(thread_id) | depends | Only if the IMAP server exposes the Drafts mailbox writably to the agent's account. Many corporate IMAPs do; many shared role mailboxes don't |
list_sent_since(thread_id, since) | ✓ | IMAP SEARCH against the Sent mailbox filtering on the thread root's Message-ID in In-Reply-To: / References: |
create_draft(thread_id, body, …) | depends | Same gating as list_drafts — the agent's IMAP account needs INSERT rights on the Drafts mailbox. Subject and threading headers are set per ../../gmail/threading.md (the threading rule is shared across backends) |
thread_url(thread_id) | ✓ (best-effort) | If the project has a public mailing-list archive (PonyMail, Pipermail, hyperkitty), construct the deep-link URL from the Message-ID per the archive's template. If there is no public archive, return a imap://<host>/<mailbox>;UID=<n> URL that only works for someone with the same IMAP credentials |
thread_id_kind | rfc5322-message-id | RFC-5322 Message-ID of the thread root |
Auth + setup
An adopter wiring IMAP needs to declare:
- Server connection — host, port, TLS preference, server-side
capabilities the agent will rely on (
IDLE,MOVE,UIDPLUS). - Account — the IMAP user the agent authenticates as. For a shared role mailbox (security-triage@example.org) prefer an app-password / service-account credential so it can be rotated without disrupting individual triagers.
- Folder layout — the inbox path for
<security-list>, the sent path, the drafts path (ornullto declare the drafts ops unsupported). - Credential storage — where the agent reads credentials from.
The framework convention is the same shell-env / secret-manager
pattern other adapters use (see
../../gmail/oauth-draft/README.mdas a reference for how a credential lifecycle is documented).
Threading model
IMAP servers don't have a native "thread" concept — the client
reconstructs threads from References: / In-Reply-To: chains.
The adapter MUST canonicalise on the root Message-ID as the
thread identifier (not the most recent message, not a server-side
folder UID, which can change). This matches the contract's
thread_id_kind: rfc5322-message-id so the tracker can store a
stable identifier across reconnects, folder moves, and server
migrations.
What an adopter declares in project.md
## Mail sources
| Backend | Role | Mandatory | Notes |
|---|---|---|---|
| `imap` | primary | yes | Subscribed to `security@example.org` via the team mailbox; drafts via Sent-as |
…plus an imap_* section under Mail sources documenting the
host / account / folders / credential path. Use the same shape the
other backends in <project-config>/project.md use for their
per-tool config (see the Gmail and PonyMail section in the
template for the pattern).
Why this is a stub
The reference adopter (airflow-s / apache-airflow) does not
currently use IMAP — Gmail covers the primary path and PonyMail
covers the archive backstop. The stub exists so an adopter that
does live on a corporate IMAP (or a self-hosted Postfix +
Dovecot setup) can declare it as the primary backend without
authoring the contract from scratch. When the first adopter wires
it in, the concrete CLI / MCP wiring lands in this directory
alongside this README.