---
name: email
description: "Email — dedicated email account. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox (with best-effort per-archive section hints from the graph). Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-status, email-otp-extract."
tools:
  - name: email-provider-info
    publicAllowlist: false
    adminAllowlist: false
  - name: email-setup
    publicAllowlist: false
    adminAllowlist: false
  - name: email-read
    publicAllowlist: false
    adminAllowlist: false
  - name: email-send
    publicAllowlist: false
    adminAllowlist: false
  - name: email-reply
    publicAllowlist: false
    adminAllowlist: false
  - name: email-search
    publicAllowlist: false
    adminAllowlist: false
  - name: email-graph-query
    publicAllowlist: false
    adminAllowlist: false
  - name: email-fetch
    publicAllowlist: false
    adminAllowlist: false
  - name: email-ingest
    publicAllowlist: false
    adminAllowlist: false
  - name: email-otp-extract
    publicAllowlist: false
    adminAllowlist: false
  - name: email-status
    publicAllowlist: false
    adminAllowlist: false
always: false
embed: ["admin"]
metadata: {"platform":{}}
mcp:
  command: node
  args:
    - ${PLATFORM_ROOT}/lib/mcp-spawn-tee/dist/index.js
    - ${PLATFORM_ROOT}/plugins/email/mcp/dist/index.js
  env:
    MCP_SPAWN_TEE_NAME: email
    LOG_DIR: ${LOG_DIR}
    PLATFORM_ROOT: ${PLATFORM_ROOT}
    ACCOUNT_ID: ${ACCOUNT_ID}
    SESSION_ID: ${SESSION_ID}
mcp-manifest: auto
---

# Email

Manages the agent's own dedicated email account — IMAP for reading, SMTP for sending. Admin agent only.

**Identity boundary (operator-identity fix).** This plugin manages the AGENT's email channel: an `:EmailAccount` node with IMAP/SMTP credentials, polling state, and the agent's `agentAddress`. It is NOT the operator-identity store. The operator's own email lives on the OWNS-bound `:Person` (`Person.email`) and is captured via `profile-update` with `personFields`. Confusing the two surfaces (writing the operator's personal email to `EmailAccount.agentAddress`, or vice versa) breaks routing AND identity coverage. See [.docs/agents.md § Coverage-driven elicitation bias] for the operator-identity contract.

## Capabilities

- **Provider lookup:** `email-provider-info` — given just an email address, returns the inferred IMAP/SMTP hosts and the provider's app-password / trusted-application / IMAP-enable requirement, deterministically from the provider map. Call this BEFORE soliciting a password so the requirement is relayed first; most providers reject the normal login password. Returns "unknown" (operator supplies hosts) or "unsupported" (provider blocks IMAP/SMTP, e.g. Tuta).
- **Setup:** `email-setup` — collect credentials in plain chat and connect IMAP/SMTP. Hosts are auto-inferred for known providers; supports alias addresses (`agentAddress`).
- **Read inbox:** `email-read` — metadata only (UID, sender, subject, date), plus a best-effort `Archive`/`Archive-Section` hint when the IMAP message's `receivedAt` falls inside a stored `:ConversationArchive {source:'email'}` section, plus an `Attachments (N): filename (mime, size); …` footer when the message carries enclosures. Supports pagination (`before_uid`), folders (`inbox`/`sent`), filtering by sender/date/subject. Metadata is derived from IMAP `bodyStructure` — no bytes downloaded.
- **Search inbox:** `email-search` — live IMAP search by sender, subject, body keyword, date range. Same per-archive hint and attachment footer as `email-read`.
- **Send:** `email-send` — new outbound email. Pass `attachments` (absolute file paths under the account directory, ≤25 MB each) to attach files as SMTP attachments; an invalid path fails the send with no email dispatched.
- **Reply:** `email-reply` — threaded reply to an existing email by `messageId`. Resolves the original envelope via IMAP `SEARCH HEADER Message-ID` (INBOX then `\Sent`), so threading works for any message still present on the server even if the graph never ingested it. Supports `replyAll` and `attachments` (same path rules as `email-send`).
- **Ingestion — fetch:** `email-fetch` — list new IMAP messages since the stored high-water mark. Metadata + body preview only — including attachment names, mimetypes, and sizes so the operator sees what's attached before approving (Task 402). No bytes downloaded, no graph writes. Stages the batch in-memory for `email-ingest`.
- **Ingestion — apply:** `email-ingest` — apply the operator's per-message decisions to the staged batch. Each Message-ID must carry a disposition (`ingest` or `discard`). Approved messages land on `:ConversationArchive {source:'email'}` via the source-agnostic conversation-archive pipeline (one archive per thread, sessionised into `:Section` chunks). Attachment bytes are fetched, archived to `{accountDir}/archive/email/<uidValidity>-<uid>/<sha256>-<filename>` (0o600, 25 MB cap), and each attachment lands as a content-addressed `:DigitalDocument` keyed on `attachmentId = sha256(bytes)`; the `:HAS_ENCLOSURE` edge from the parent `:ConversationArchive` is wired by `memory-ingest` on its next pass via the `pendingArchiveEdges` queue (Task 402). Advances the inbox high-water mark only after a successful write.
- **Recall/history:** `email-graph-query` — search stored email archives in Neo4j by natural language, participant address, or date range. Each thread is one `:ConversationArchive {source:'email'}` with `:Section` chunks; operator participants attach via `:PARTICIPANT_IN` edges from `:Person`/`:AdminUser`. List mode surfaces a per-archive summary built from concatenated `:Section.summary` values; query mode runs vector search over the `section_embedding` index and dedups to the parent archive.
- **Status:** `email-status` — connection health and configuration state.
- **OTP:** `email-otp-extract` — poll for verification codes during service authentication.

## Ingestion model

Email ingestion is **operator-initiated and human-in-the-loop**. There is no background poll, cron, or auto-classifier. New messages enter the graph only when the operator says "check my emails" and approves each one. See `skills/email-ingest/SKILL.md` for the conversation flow and `skills/email-composition/SKILL.md` for drafting replies once a thread is stored.

## Retrieval paths

- **Live inbox** (`email-read`, `email-search`) — real-time IMAP queries. Use for checking the inbox or reading recent messages.
- **Graph history** (`email-graph-query`) — stored email-thread `:ConversationArchive {source:'email'}` rows (with `:Section` chunks) in Neo4j. Use for recall, semantic search, email history questions.
- **General knowledge** (`memory-search`) — cross-type knowledge graph. Use when the question spans all memory, not just emails.

## Skills

| Skill | Purpose |
|-------|---------|
| [email-ingest](skills/email-ingest/SKILL.md) | Operator-initiated ingestion. Drives "check my emails" → list via `email-fetch` → summarise → ask the operator per message → write the approved subset via `email-ingest`. |
| [email-composition](skills/email-composition/SKILL.md) | Conversational wrapper for drafting replies, new emails, and inbox triage. Orchestrates the five `email-*` tools with human-in-the-loop approval on every send. |

## References

| Reference | Topics |
|-----------|--------|
| [email-reference.md](references/email-reference.md) | Setup form fields, pagination, filtering, replying, alias support, operator-initiated ingestion, threading, OTP integration |

Load the reference via `plugin-read` when detailed configuration or behaviour information is needed.
