EMAIL is CyberOS's mail server, shared inbox, and AI-native composition surface in a single bundled module. The protocol stack is Stalwart (Rust, single binary) speaking JMAP / IMAP / SMTP / ManageSieve to the world. On top sits a Missive-style UX where a team can manage support@cyberskill.world together - assignment, internal comments, snooze, tagging. Every inbound body passes through a CaMeL quarantined LLM before any privileged CUO context can see it; the privileged side operates on the sanitised extraction (sender, recipient, subject, gist, action requests, entities), never on raw HTML. Outbound mail is DKIM-signed with per-tenant keys, ARC-stamped on forward, and tagged with BIMI for inbox-list branding. Threading is JMAP-native; search is PGroonga (Vietnamese-aware bigram); calendar is iCal. DSAR export bundles every message a subject participated in.

The bigger picture - three strategic roles

EMAIL is structurally hostile territory. It is simultaneously: the second-highest-volume communication channel for any consultancy, the single most dangerous source of indirect prompt-injection attacks (EchoLeak / CVE-2025-32711 in May 2025 set the precedent), and the easiest way to log customer conversations to CRM + PROJ. The three strategic roles below are not nice-to-have features - they're the only architecture that handles those three realities at once.

Role 1 - capture surface

Inbound auto-log to CRM + PROJ thread-to-issue. Tracked domains auto-log every inbound + outbound message as a CRM activity. Thread-to-issue: the AM clicks "Convert to issue" on a thread, the thread becomes a PROJ Issue with the email body as the issue description + the reply chain as comments. Captures the consultancy memory layer that otherwise lives in nobody's inbox - and is gone the moment they leave.

Role 2 - Genie draft

Ask Genie composes; the AM approves before send. A "Genie:" subject prefix OR a sidebar action makes the CUO draft a reply grounded in: the thread history (sanitised by CaMeL), the linked CRM account, memories about prior interactions with this account, and the relevant KB docs. The draft is never auto-sent. The AM reviews + edits + sends. The whole flow takes 30 seconds versus 5 minutes of writing.

Role 3 - outbound send + defence

DKIM + ARC + BIMI; CaMeL anti-injection. Outbound: per-tenant DKIM keys, ARC on forward, BIMI for inbox-list branding. Inbound: every body passes through the CaMeL quarantine LLM (no tools, no memory, produces structured extraction only) before the privileged CUO sees anything. EchoLeak-class attacks fail because the privileged agent never reads raw HTML or untrusted text. AM/CFO approval tokens are required on bulk send.

EMAIL in the orchestration spine

flowchart LR
  EXT["External sender"]
  EMAIL["EMAIL<br/>Stalwart / Missive UX / CaMeL quarantine"]
  CRM["CRM<br/>activity auto-log"]
  PROJ["PROJ<br/>thread-to-issue"]
  CUO["CUO<br/>Genie draft skill"]
  AM["AM<br/>review + send"]
  memory["memory<br/>thread history / CaMeL audit"]
  OBS["OBS<br/>send latency / injection blocked"]
  EXT --> EMAIL
  EMAIL -- "CaMeL quarantine: sanitised extract" --> memory
  EMAIL -- "tracked domain" --> CRM
  EMAIL -- "thread-to-issue" --> PROJ
  CRM --> EMAIL
  PROJ --> EMAIL
  EMAIL --> CUO
  CUO -- "draft" --> EMAIL
  EMAIL --> AM
  AM -- "send" --> EXT
  EMAIL --> OBS
  classDef hub fill:#e2e8f0,stroke:#334155,stroke-width:3px,color:#0f172a
  classDef mod fill:#e0e7ff,stroke:#3730a3
  classDef memory fill:#fef6e0,stroke:#9c750a
  classDef ext fill:#fee2e2,stroke:#b91c1c
  class EMAIL hub
  class CRM,PROJ,CUO,AM,OBS mod
  class memory memory
  class EXT ext

Auto vs human-in-loop operations matrix

OperationHow it happensWhy this split
Inbound CaMeL quarantineAuto on every inbound messageProtocol invariant; privileged context never sees raw body.
Tracked-domain CRM auto-logAuto by domain matchStops the "log it in the CRM" tax; auto = no manual entry.
Thread-to-issue conversionManual AM clickNot every thread becomes an Issue; AM intent required.
Genie draftAuto; never auto-sentDraft is a suggestion; the AM is accountable for prose + tone.
Outbound send (1:1)Manual AM actionCustomer comms = relationship; a human sends.
Bulk send (>=10 recipients)Manual AM + CFO/marketing approvalAnti-spam + brand-tone gate; never one-click bulk.
DKIM / ARC / BIMI signingAuto on outboundStandards-driven; deterministic.
Spam classificationAuto via Stalwart + MLTrash routing; quarantine for borderline.
Bounce + reputation handlingAuto + alarmsEmail reputation is fragile; OBS surfaces dips.
DSAR exportAuto bundle on requestPer-subject; chained audit; matches the memory export pattern.

Why EMAIL exists

Three reasons to own the mail plane rather than outsource to a SaaS provider: (1) injection defence - most of the high-profile 2025 prompt-injection CVEs entered through email, and a hosted provider does not let you put a CaMeL quarantine between the body and the agent; (2) shared-inbox UX - teams managing support@, sales@, billing@ need assignment, internal comments, and snooze, which Gmail / M365 implement only via shaky third-party add-ons; (3) data residency - Vietnamese tenants under Decree 53 must keep certain customer data on Vietnamese soil, which most hosted mail providers cannot promise. Stalwart gives us a credible Rust mail server, a Missive-style UX gives us the shared-inbox primitive, and CaMeL gives us a proof-shaped boundary against indirect prompt injection.

EchoLeak (CVE-2025-32711, May 2025) was the canonical demonstration: a malicious email tricked Microsoft 365 Copilot into exfiltrating data with zero user clicks. The attack class continued through April 2026. The lesson is not "patch the LLM" - it is "do not let the LLM see the raw body". CaMeL is the architecture that enforces that, and EMAIL is the module where that enforcement lives.

What it does - 5W1H2C5M

A structured decomposition of EMAIL's scope. Every cell traces back to §11.2.3.

AxisQuestionAnswer
5W - WhatWhat is EMAIL?A Stalwart-based mail server (JMAP / IMAP / SMTP / MTA-STS / DANE / DKIM / ARC / BIMI) plus a Missive-style shared-inbox UX, gated by a CaMeL dual-LLM filter on every inbound body. Single Rust deployment with a separate TypeScript SPA. Postgres for metadata; object storage (S3 + KMS) for bodies and attachments.
5W - WhoWho uses it?Members: personal inboxes + assigned shared inboxes. Customers: external senders / recipients (never authenticate to CyberOS). Agents: CUO via MCP for draft-reply / categorise / snooze tools. Owner: CCO seat (interim CEO).
5W - WhenWhen does it run?Continuous SMTP listener (25 / 465 / 587). JMAP / IMAP for client sync. The inbound CaMeL scan runs on the receive path before any thread row is committed. Outbound DKIM signing happens at queue time. AI features (draft, summarise) run on user demand.
5W - WhereWhere does it run?P1: single region (SG-1) plus a VN-residency relay for Decree 53 customers. P3+: multi-region with active-active SMTP and an MX-record failover. Object storage and Postgres are tenant-tagged; vn-residency=true tenants land on hanoi-1.
5W - WhyWhy own the plane?Three reasons: (a) prompt-injection defence requires the CaMeL boundary, which a SaaS does not allow; (b) shared-inbox UX needs internal-comments and assignment primitives that Gmail does not provide natively; (c) Vietnamese data-residency obligations under Decree 53.
1H - HowHow does it work?Inbound: SMTP -> DKIM / SPF / DMARC verify -> spam triage -> CaMeL quarantined extraction -> thread merge by JMAP threadId -> notification fan-out. Outbound: composer -> CaMeL outbound scan (block PII leaks) -> DKIM sign -> MTA-STS / DANE next-hop -> SMTP send. AI: the composer pulls a draft from AI Gateway with a persona-stamped JWT.
2C - CostCost budget?P1: ~$110 / month for the SG-1 single-tenant pilot (Stalwart Fargate + RDS Postgres + S3 + Redis cache). 50-tenant: ~$420 / month. Per-message inbound CaMeL cost ~$0.0004 (gpt-4o-mini-equivalent extraction).
2C - ConstraintsConstraints?(a) AGPL-3.0 - Stalwart's licence means our distribution model is service-only. (b) DMARC alignment mandatory - strict policy from P0 exit. (c) No body content into memory by default (task pending); opt-in per Member. (d) CaMeL on every ingest path - non-negotiable.
5M - MaterialsStack?Stalwart Mail Server (Rust), axum HTTP API, sqlx, PostgreSQL 16, PGroonga search, Redis 7 cache, S3 + KMS for bodies, CaMeL adapter on top of AI Gateway, OpenTelemetry SDK, React / TypeScript SPA, htmx + Alpine for the inbox shell.
5M - MethodsMethod choices?JMAP-native threading (no Reply-To header heuristics). PGroonga over tsvector (Vietnamese bigram quality wins). CaMeL dual-LLM (quarantined no-tools + privileged). Per-tenant DKIM keypair stored in KMS. ARC stamping on forward. Single-binary Stalwart deployment (no separate MTA / MDA / MUA processes).
5M - MachinesDeployment?Fargate task per region for SMTP + JMAP + IMAP listeners. RDS Postgres for metadata. S3 + KMS for bodies and attachments. Redis for hot-thread cache. Per-tenant DKIM keypair in KMS.
5M - ManpowerWho maintains?0.5 FTE (CTO seat) at P1 launch. By P2+: the CCO seat owns product; the CTO owns infra; the CSO is consulted on every Stalwart upgrade.
5M - MeasurementHow measured?Deliverability (DMARC pass rate >= 98%), p95 send latency <= 2 s, p95 receive latency <= 5 s, CaMeL injection-block rate >= 99.5% on the test corpus, AI-draft acceptance >= 40% by Member.

Architecture

EMAIL has four planes: the protocol plane (Stalwart, talking SMTP / IMAP / JMAP outbound and inbound to the world); the policy plane (CaMeL filter + DKIM / SPF / DMARC verify + per-tenant ACL); the UX plane (the Missive-style SPA + an htmx-light fallback for slow networks); and the AI plane (draft, summarise, categorise - all routed through AI Gateway with the privileged CUO persona). The diagram shows the canonical inbound path with the CaMeL boundary highlighted.

graph TB
  subgraph WORLD ["Internet"]
    SENDER["External sender<br/>(any SMTP source)"]
    RECIPIENT["External recipient"]
  end
  subgraph EDGE ["Edge / protocol plane"]
    SMTP["Stalwart SMTP<br/>25 / 465 / 587"]
    JMAP["Stalwart JMAP<br/>/jmap/"]
    IMAP["Stalwart IMAP<br/>143 / 993"]
    MTA_STS["MTA-STS / DANE<br/>next-hop policy"]
  end
  subgraph POLICY ["Policy plane"]
    DKIM_VERIFY["DKIM / SPF / DMARC<br/>verify"]
    SPAM["Spam triage"]
    CAMEL["CaMeL quarantine<br/>no-tools LLM"]
    EXTRACT["Sanitised extraction:<br/>{from, to, subj, gist,<br/>actions, entities}"]
    DKIM_SIGN["DKIM sign<br/>per-tenant key (KMS)"]
    ARC["ARC stamp<br/>on forward"]
  end
  subgraph CORE ["EMAIL service (Rust)"]
    THREAD["Thread merger<br/>JMAP threadId"]
    INBOX["Shared-inbox<br/>assignment / comments"]
    SEARCH["PGroonga search<br/>(VN bigram)"]
    CAL["iCal / calendar"]
    SIEVE["ManageSieve<br/>rules engine"]
  end
  subgraph STORES ["Stores"]
    PG[("PostgreSQL<br/>threads / assignments<br/>RLS by tenant_id")]
    S3[("S3 + KMS<br/>bodies + attachments")]
    RED[("Redis 7<br/>hot threads / search cache")]
    KMS_K[("AWS KMS<br/>per-tenant DKIM keys")]
  end
  subgraph SINKS ["Sinks"]
    memory["memory<br/>summaries (opt-in) /<br/>audit rows"]
    OBS["OBS<br/>traces + DMARC reports"]
    CUO["CUO<br/>privileged agent"]
    AI["AI Gateway"]
  end
  SENDER --> SMTP
  SMTP --> DKIM_VERIFY
  DKIM_VERIFY --> SPAM
  SPAM --> CAMEL
  CAMEL --> EXTRACT
  EXTRACT --> THREAD
  THREAD --> PG
  THREAD --> S3
  THREAD --> SEARCH
  THREAD --> memory
  THREAD --> INBOX
  THREAD --> RED
  INBOX --> CUO
  CUO --> AI
  AI --> CAMEL
  CAMEL --> AI
  JMAP --> THREAD
  IMAP --> THREAD
  SIEVE --> THREAD
  CAL --> THREAD
  THREAD --> DKIM_SIGN
  DKIM_SIGN --> KMS_K
  DKIM_SIGN --> ARC
  ARC --> MTA_STS
  MTA_STS --> RECIPIENT
  THREAD --> OBS
  classDef planned fill:#e2e8f0,stroke:#334155
  classDef store fill:#f5f3ff,stroke:#7c3aed
  classDef sink fill:#f5ede6,stroke:#45210e
  classDef camel fill:#fee2e2,stroke:#b91c1c,stroke-width:3px
  class SMTP,JMAP,IMAP,MTA_STS,THREAD,INBOX,SEARCH,CAL,SIEVE,DKIM_VERIFY,SPAM,DKIM_SIGN,ARC planned
  class CAMEL,EXTRACT camel
  class PG,S3,RED,KMS_K store
  class memory,OBS,CUO,AI sink

Internal components

ComponentPath (planned)Responsibility
stalwart_adapter.rsservices/email/src/stalwart_adapter.rsSpawns and supervises the Stalwart binary; bridges its event stream into the rest of CyberOS.
camel.rsservices/email/src/camel.rsCaMeL boundary. Calls the quarantined LLM through AI Gateway with persona=quarantined-extractor (no tools, no memory). Returns the sanitised structured extraction.
dkim_verify.rsservices/email/src/dkim_verify.rsInbound DKIM / SPF / DMARC verification. Wraps the mail-auth crate.
dkim_sign.rsservices/email/src/dkim_sign.rsOutbound DKIM signing with the per-tenant key from KMS. ARC stamping on forward.
thread.rsservices/email/src/thread.rsThread merge using JMAP-native threadId. Reply-To / In-Reply-To / References headers as inputs; never used as authoritative thread keys.
inbox.rsservices/email/src/inbox.rsShared-inbox state - assignment, comments, snooze, tags. RLS-keyed by inbox_id.
search.rsservices/email/src/search.rsPGroonga query builder with Vietnamese bigram tokenisation. Falls back to tsvector for non-CJK locales.
sieve.rsservices/email/src/sieve.rsManageSieve rules engine - per-Member auto-tag, auto-snooze, auto-route. Rules compiled to Stalwart's sieve runtime.
ical.rsservices/email/src/ical.rsiCal parser / generator. Calendar invites surface as native events in CyberOS Calendar.
draft_ai.rsservices/email/src/draft_ai.rsAI-suggested-reply pipeline. Pulls from AI Gateway; persona-stamped; CyberSkill voice; Vietnamese-aware salutation logic.
memory_summariser.rsservices/email/src/memory_summariser.rsPer-thread summary writer. Calls AI Gateway in summarise mode; writes a single memory row per thread with citations back to message IDs. Body bytes never enter memory by default (task pending).
dmarc_report.rsservices/email/src/dmarc_report.rsAggregate DMARC RUA / RUF reports per tenant; surfaced to the Trust Center (task pending).
dsar_export.rsservices/email/src/dsar_export.rsDSAR bundle: every message a subject participated in, with attachments and decisions.
migration_import.rsservices/email/src/migration_import.rsGmail / M365 / IMAP import. Walks the source via JMAP / IMAP, replays threads in CaMeL-filtered order, writes an audit row per imported message.
migrations/services/email/migrations/sqlx migrations. All identity-touching tables RLS-keyed by tenant_id; shared-inbox tables additionally RLS-keyed by inbox_id.

Data model

Metadata lives in PostgreSQL with row-level security keyed by tenant_id; bodies and attachments live in S3 + KMS, addressed by content-hash. Threads are normalised on JMAP threadId, not on Reply-To header heuristics. Shared inboxes are a separate table with their own ACL; a Member can be a participant in many inboxes, and an inbox can be managed by many Members.

erDiagram
  TENANT ||--o{ MAILBOX: "owns"
  TENANT ||--o{ SHARED_INBOX: "owns"
  MAILBOX ||--o{ THREAD: "contains"
  SHARED_INBOX ||--o{ THREAD: "routes"
  THREAD ||--o{ MESSAGE: "contains"
  MESSAGE ||--o{ ATTACHMENT: "has"
  MESSAGE ||--o| CAMEL_EXTRACTION: "produced"
  THREAD ||--o{ ASSIGNMENT: "has"
  THREAD ||--o{ INTERNAL_COMMENT: "has"
  THREAD ||--o{ TAG: "has"
  MAILBOX ||--o{ SIEVE_RULE: "applies"
  MAILBOX ||--o| DKIM_KEY: "signs with"
  THREAD ||--o| AI_SUMMARY: "summarised by"
  MESSAGE ||--o{ DMARC_VERDICT: "verified by"
  THREAD ||--o{ CALENDAR_EVENT: "links"
  TENANT {
    uuid id PK
    string slug
    string vn_residency "true | false"
  }
  MAILBOX {
    uuid id PK
    uuid tenant_id FK
    uuid owner_subject_id FK
    string address "person@tenant.com"
    string display_name
    string kind "personal | shared"
    timestamp created_at
  }
  SHARED_INBOX {
    uuid id PK
    uuid tenant_id FK
    string address "support@tenant.com"
    string display_name
    string default_assignee_subject_id
    timestamp created_at
  }
  THREAD {
    uuid id PK
    uuid tenant_id FK
    uuid mailbox_id FK
    uuid inbox_id FK "nullable"
    string subject
    string jmap_thread_id
    timestamp last_message_at
    string status "active | snoozed | done"
    timestamp snoozed_until
    int message_count
  }
  MESSAGE {
    uuid id PK
    uuid thread_id FK
    string message_id "RFC 5322"
    string from_addr
    string to_addrs "json array"
    string cc_addrs
    string bcc_addrs
    string subject
    timestamp received_at
    string body_s3_key "ciphertext"
    bigint body_size_bytes
    string body_sha256
    string spf_result
    string dkim_result
    string dmarc_result
    string direction "inbound | outbound"
  }
  ATTACHMENT {
    uuid id PK
    uuid message_id FK
    string filename
    string mime_type
    bigint size_bytes
    string s3_key
    string sha256
    bool scanned "antivirus clean"
  }
  CAMEL_EXTRACTION {
    uuid message_id PK
    string from_norm
    string to_norm
    string subject_norm
    string gist
    string action_requests "json"
    string entities "json"
    string sensitive_flags "json"
    string injection_detected_reason "nullable"
    timestamp produced_at
    string ai_model "gpt-4o-mini or equivalent"
  }
  ASSIGNMENT {
    uuid id PK
    uuid thread_id FK
    uuid assignee_subject_id FK
    uuid assigner_subject_id FK
    timestamp assigned_at
    string status "open | resolved"
  }
  INTERNAL_COMMENT {
    uuid id PK
    uuid thread_id FK
    uuid author_subject_id FK
    string body_markdown
    timestamp created_at
  }
  TAG {
    uuid thread_id FK
    string tag
    uuid added_by FK
  }
  SIEVE_RULE {
    uuid id PK
    uuid mailbox_id FK
    string sieve_script
    int priority
    bool enabled
  }
  DKIM_KEY {
    uuid id PK
    uuid tenant_id FK
    string selector "cyberos-2026-q2"
    string kms_key_id
    string public_key_dns
    timestamp valid_from
    timestamp valid_to
  }
  AI_SUMMARY {
    uuid thread_id PK
    string summary_markdown
    string citations "json: message_id + span"
    timestamp generated_at
    string memory_chain "linked audit row"
  }
  DMARC_VERDICT {
    uuid id PK
    uuid message_id FK
    string verdict "pass | fail"
    string aligned_spf
    string aligned_dkim
    timestamp ts
  }
  CALENDAR_EVENT {
    uuid id PK
    uuid thread_id FK
    string ical_uid
    timestamp dtstart
    timestamp dtend
    string summary
  }

Indexing & search strategy

API surface

Four surfaces: JMAP for IETF-standard mail clients, a GraphQL federated subgraph for the CyberOS SPA, MCP tools for the CUO agent, and a small REST admin surface for DKIM rotation and Trust-Center reports.

GraphQL subgraph (federated)

extend schema
 @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@requiresScopes"])

type Mailbox @key(fields: "id") {
 id: ID!
 address: String!
 displayName: String!
 kind: MailboxKind!
 unread: Int!
 threads(status: ThreadStatus, limit: Int = 50, cursor: String): ThreadConnection!
 @requiresScopes(scopes: [["email.read"]])
}

type SharedInbox @key(fields: "id") {
 id: ID!
 address: String!
 displayName: String!
 defaultAssignee: Subject
 unassignedCount: Int!
 threads(status: ThreadStatus, assignee: ID, limit: Int = 50): ThreadConnection!
 @requiresScopes(scopes: [["email.shared.read"]])
}

type Thread @key(fields: "id") {
 id: ID!
 subject: String!
 participants: [String!]!
 messageCount: Int!
 lastMessageAt: DateTime!
 status: ThreadStatus!
 assignment: Assignment
 tags: [String!]!
 comments: [InternalComment!]!
 summary: AISummary
 messages(limit: Int = 50): [Message!]!
}

type Message @key(fields: "id") {
 id: ID!
 from: String!
 to: [String!]!
 cc: [String!]
 subject: String!
 receivedAt: DateTime!
 bodyPreview: String! # CaMeL gist - NOT raw body
 attachments: [Attachment!]!
 dmarc: DMARCResult!
}

type AISummary {
 threadId: ID!
 summary: String!
 citations: [Citation!]!
 generatedAt: DateTime!
}

enum MailboxKind { PERSONAL SHARED }
enum ThreadStatus { ACTIVE SNOOZED DONE }
enum DMARCResult { PASS FAIL NONE }

type Mutation {
 assignThread(threadId: ID!, assigneeId: ID!): Boolean!
 @requiresScopes(scopes: [["email.shared.assign"]])
 snoozeThread(threadId: ID!, until: DateTime!): Boolean!
 resolveThread(threadId: ID!): Boolean!
 addInternalComment(threadId: ID!, body: String!): InternalComment!
 draftReply(threadId: ID!, intent: ReplyIntent): DraftReplyResult!
 @requiresScopes(scopes: [["email.compose"]])
 sendMessage(input: SendMessageInput!): Message!
 @requiresScopes(scopes: [["email.send"]])
}

REST + admin surface

MethodPathPurpose
GET/.well-known/mta-sts.txtMTA-STS policy.
GET/jmap/JMAP entry point (RFC 8620).
POST/jmap/api/JMAP method calls.
POST/jmap/upload/JMAP blob upload.
GET / POST/imap/IMAP IDLE / fetch (port 993).
SMTP:25 / :465 / :587Submission and inter-MTA relay.
POST/admin/dkim/rotateRotate the per-tenant DKIM keypair; old selector retained 30 d.
GET/admin/dmarc/reportsDMARC aggregate report list.
POST/admin/shared-inbox/createCreate a shared inbox.
POST/admin/shared-inbox/{id}/participantsAdd / remove participants.
POST/admin/sieve/{mailbox_id}Install a ManageSieve script.
POST/admin/import/startStart a Gmail / M365 / IMAP migration.
GET/admin/import/{job_id}/statusMigration progress.
POST/admin/dsar/exportGenerate a DSAR bundle for a subject.

MCP tool catalogue

Tool nameInputsOutputsAnnotations
cyberos.email.list_threadsmailbox_id?, inbox_id?, status?, limitThreadreadonly, scope=email.read
cyberos.email.read_threadthread_idThread + Messages (gist only)readonly
cyberos.email.draft_replythread_id, intent{draft_text, tone, language}readonly
cyberos.email.sendSendMessageInput{message_id}destructive, human-confirm, scope=email.send
cyberos.email.assign_threadthread_id, assignee_id{ok}scope=email.shared.assign
cyberos.email.snoozethread_id, until{ok}scope=email.compose
cyberos.email.promote_to_projthread_id, project_id{issue_id}scope=proj.write
cyberos.email.summarise_threadthread_id{summary, citations}readonly

Key flows

Flow 1 - inbound message with CaMeL scan

sequenceDiagram
  autonumber
  participant S as External sender
  participant ST as Stalwart SMTP (25)
  participant V as DKIM/SPF/DMARC verifier
  participant Q as CaMeL quarantined LLM
  participant T as Thread merger
  participant PG as PostgreSQL
  participant S3 as S3 + KMS
  participant B as memory audit
  participant U as Recipient SPA
  S->>ST: DATA (RFC 5322 message)
  ST->>V: verify SPF / DKIM / DMARC
  V-->>ST: {spf:pass, dkim:pass, dmarc:pass}
  ST->>S3: write ciphertext body
  S3-->>ST: s3_key
  ST->>Q: extract({from, subject, body_text, body_html_stripped})
  Note over Q: quarantined LLM has<br/>NO tools, NO memory,<br/>NO memory access
  Q-->>ST: {gist, action_requests, entities, injection_flag}
  ST->>T: merge by jmap_thread_id
  T->>PG: insert message + camel_extraction row
  T->>B: append email.inbound {actor:sender, decision:"received", camel_flag}
  T-->>U: WebSocket push: new message preview (gist only)
  Note over U: SPA renders gist + redacted body,<br/>user can click "show raw" which fetches HTML<br/>in a sandbox iframe - raw never enters CUO context

If injection_flag is set, the message is flagged in the UI with a banner and the AI-draft-reply tool refuses to run on that thread.

Flow 2 - outbound message with DKIM signing

sequenceDiagram
  autonumber
  participant U as Sender SPA
  participant API as EMAIL GraphQL
  participant CO as CaMeL outbound scan
  participant DK as DKIM signer
  participant K as AWS KMS
  participant ST as Stalwart SMTP relay
  participant MX as Recipient MX
  participant B as memory
  U->>API: sendMessage({to, subject, body})
  API->>CO: scan for PII / secrets / persona-leakage
  alt clean
    CO-->>API: ok
  else PII / secret detected
    CO-->>API: 422 {reason:"pii.email.detected"}
    API-->>U: blocked - review required
    API->>B: email.outbound_blocked
  end
  API->>DK: sign(headers, body)
  DK->>K: KMS sign with per-tenant key
  K-->>DK: signature
  DK-->>API: signed headers
  API->>ST: relay
  ST->>MX: SMTP DATA (MTA-STS / DANE next-hop)
  MX-->>ST: 250 OK
  ST-->>API: queued
  API->>B: email.outbound {to, dkim:signed}
  API-->>U: 200 {message_id}

Flow 3 - AI-drafted reply (CyberSkill voice, Vietnamese-aware)

sequenceDiagram
  autonumber
  participant U as Member
  participant API as EMAIL GraphQL
  participant CE as CaMeL extraction store
  participant AG as AI Gateway
  participant CU as CUO
  participant B as memory audit
  U->>API: draftReply(thread_id, intent:"acknowledge + ask follow-up")
  API->>CE: load sanitised gist + extracted entities
  CE-->>API: {gist, entities, language: "vi"}
  Note over API,CU: raw body NEVER enters this path -<br/>only the CaMeL gist + structured fields
  API->>CU: compose({gist, intent, voice:"cyberskill", language:"vi"})
  CU->>AG: chat.completions (persona-stamped JWT, vi salutation rules)
  AG-->>CU: draft text
  CU-->>API: draft + tone notes
  API->>B: email.draft_generated {thread, model, persona_version}
  API-->>U: {draft, tone:"warm", salutation:"Chào Anh / Chị"}
  Note over U: Member edits, then sends via Flow 2

Vietnamese salutation logic: the extracted entities include the recipient's likely gender / title; if confidence < 0.7, the draft defaults to the neutral "Chào Anh/Chị".

Flow 4 - shared-inbox assignment

sequenceDiagram
  autonumber
  participant E as External sender (support@)
  participant ST as Stalwart SMTP
  participant SI as SharedInbox router
  participant CUO as CUO categoriser
  participant PG as PostgreSQL
  participant N as Notify (CHAT)
  participant A as Default assignee
  E->>ST: support@cyberskill.world
  ST->>SI: shared-inbox match
  SI->>CUO: classify({gist, entities})
  CUO-->>SI: {category:"billing", confidence:0.92}
  alt high confidence
    SI->>PG: assign to billing-team default assignee
    SI->>N: ping #shared-inbox-billing
  else low confidence
    SI->>PG: leave unassigned
    SI->>N: ping #shared-inbox-triage
  end
  A->>SI: claim thread / reassign
  SI->>PG: assignment row updated
  SI->>N: post "Claimed by A"

Flow 5 - Gmail to CyberOS migration

sequenceDiagram
  autonumber
  participant U as Migrating Member
  participant CLI as cyberos-email-cli
  participant G as Gmail IMAP
  participant Q as CaMeL quarantined LLM
  participant T as Thread merger
  participant S3 as S3
  participant B as memory audit
  U->>CLI: cyberos-email-cli import gmail --user stephen@...
  CLI->>G: AUTHENTICATE XOAUTH2
  G-->>CLI: SELECT [Gmail]/All Mail (12,481 messages)
  loop per message
    CLI->>G: FETCH BODYSTRUCTURE + HEADER + BODY
    G-->>CLI: raw message
    CLI->>Q: extract - produce gist, flag injections
    Q-->>CLI: extraction
    CLI->>S3: store ciphertext body
    CLI->>T: merge into thread
    T->>B: append email.imported {gmail_uid, jmap_thread_id}
  end
  CLI-->>U: import complete - 12,481 messages, 47 injection-flagged

Injection-flagged historical messages are tagged but not deleted; the AI-draft tool refuses to operate on them, and a UI banner explains the flag.

Message lifecycle

Inbound and outbound messages traverse separate state machines, but every state transition emits a memory audit row.

stateDiagram-v2
  [*] --> SMTP_Accepted: 250 OK at edge
  SMTP_Accepted --> Verifying: SPF/DKIM/DMARC
  Verifying --> Spam_Quarantined: DMARC reject + heuristics
  Verifying --> CaMeL_Pending: passed verification
  CaMeL_Pending --> Extracted: gist + entities produced
  CaMeL_Pending --> Injection_Flagged: prompt-injection detected
  Extracted --> Thread_Merged: jmap_thread_id resolved
  Injection_Flagged --> Thread_Merged: still merged, UI banner shown
  Thread_Merged --> Active: in inbox / shared inbox
  Active --> Snoozed: user snooze
  Snoozed --> Active: snooze expires
  Active --> Resolved: user marks done
  Active --> Archived: rule / age policy
  Active --> Deleted_Tombstoned: user delete (recoverable 30 d)
  Deleted_Tombstoned --> Purged: 30 d elapsed OR DSAR purge
  Resolved --> Archived: auto-archive 14 d
  Spam_Quarantined --> Deleted_Tombstoned: 30 d auto
  Purged --> [*]
  Archived --> [*]

Outbound state machine

StateTransitionAudit row
composingdraft saved every 10 s-
outbound_scanCaMeL outbound PII / secret detectionemail.outbound_scan
queuedsigned, in Stalwart queueemail.outbound_queued
relayedMX SMTP 250 receivedemail.outbound_relayed
deliveredDSN MDN received (if requested)email.outbound_delivered
bouncedpermanent 5xx from MXemail.outbound_bounced
deferred4xx - retry per backoffemail.outbound_deferred

Functional requirements

The CyberOS task catalogue is being rebuilt one feature at a time via the open task-author Agent Skill.

Previous task enumerations were archived 2026-05-14 and are no longer reflected on this page. Specific tasks land here as they are re-authored.

Non-functional requirements

NFRs that EMAIL must satisfy. Cross-referenced at nfr-catalog.html#email.

NFR IDConcernTargetMeasurement
N(task pending)Inbound message -> thread visible to recipientp95 <= 5 sk6 SMTP-to-UI test, nightly
N(task pending)Outbound message accepted to relayp95 <= 2 sk6 send-loop, nightly
N(task pending)CaMeL extraction latencyp95 <= 1.2 sAI Gateway histogram
N(task pending)Thread list page load (50 threads)p95 <= 350 msSPA RUM
N(task pending)Outbound DMARC pass rate>= 99% of attemptedweekly DMARC aggregate
N(task pending)Inbound deliverability (Tier-1 MX accept rate)>= 99.5%SMTP 250-OK ratio
N(task pending)SMTP-receive availability (28-day)>= 99.9%OBS SLO monitor
N(task pending)CaMeL injection-block rate on red-team corpus>= 99.5%monthly red-team replay
N(task pending)Body bytes in memory without opt-in= 0CI gate on memory ingestion paths
N(task pending)Message durability after acceptance0 lost messages under crashchaos test, memory ledger walk
N(task pending)AI-draft acceptance rate>= 40% by MemberSPA telemetry
N(task pending)Shared-inbox first-response timep50 <= 30 min business hoursmemory assignment events
N(task pending)VN-residency tenant message storage region100% hanoi-1S3 key-prefix audit

Dependencies

EMAIL leans on AUTH (every JMAP / GraphQL call), memory (audit + opt-in summary), AI (CaMeL extraction + draft generation), MCP (CUO tools), and OBS (DMARC reports + traces). It is leaned on by CRM (activity auto-log), PROJ ("promote to task"), CUO (assignment categoriser), and KB (publishing a doc as an email digest).

graph LR
  subgraph upstream ["EMAIL depends on"]
    AUTH["AUTH<br/>OAuth / RBAC"]
    memory["memory<br/>audit + opt-in summary"]
    AI["AI Gateway<br/>CaMeL + draft"]
    MCP["MCP<br/>CUO tools"]
    OBS["OBS<br/>traces + DMARC"]
    STALWART["Stalwart<br/>(vendored Rust)"]
    KMS["KMS<br/>DKIM keys"]
    S3["S3<br/>bodies + attachments"]
  end
  EMAIL["EMAIL"]
  subgraph downstream ["EMAIL is depended on by"]
    CRM["CRM<br/>email->activity"]
    PROJ["PROJ<br/>thread->issue"]
    CUO["CUO<br/>shared-inbox classifier"]
    KB["KB<br/>email digests"]
    PORTAL["Portal (P2)"]
  end
  AUTH --> EMAIL
  memory --> EMAIL
  AI --> EMAIL
  MCP --> EMAIL
  OBS --> EMAIL
  STALWART --> EMAIL
  KMS --> EMAIL
  S3 --> EMAIL
  EMAIL --> CRM
  EMAIL --> PROJ
  EMAIL --> CUO
  EMAIL --> KB
  EMAIL --> PORTAL
  classDef shipped fill:#f5ede6,stroke:#45210e
  classDef planned fill:#fef6e0,stroke:#9c750a
  class memory,STALWART,KMS,S3 shipped
  class EMAIL,AUTH,AI,MCP,OBS,CRM,PROJ,CUO,KB,PORTAL planned

Compliance scope

Email is a magnet for regulators. EMAIL must satisfy PDPL, GDPR, RFC compliance, and Decree-13 personal data processing log requirements.

Regulation / standardArticle / clauseEMAIL feature that satisfies it
Vietnam PDPL (Law 91/2025)Art. 14 - DSARcyberos-email-cli dsar-export bundles every message a subject sent / received.
Vietnam Decree 13/2023Art. 17 - Personal data processing logEvery inbound / outbound writes a memory audit row.
Vietnam Decree 53/2022Art. 26 - Data localisationThe per-tenant vn_residency flag routes bodies + attachments to hanoi-1 S3.
GDPR (EU 2016/679)Art. 15 - Right of accessDSAR export.
GDPRArt. 17 - Right to erasureSoft-delete + 30-day purge; DSAR-driven hard purge.
GDPRArt. 32 - Security of processingTLS 1.3 SMTP, KMS-wrapped bodies, RLS, DKIM signing.
RFC 5322Internet Message FormatThe Stalwart parser is RFC-compliant; canonical headers preserved.
RFC 6376DKIM Signaturesdkim_sign.rs + per-tenant KMS key.
RFC 7208SPFInbound verifier; outbound DNS check.
RFC 7489DMARCInbound enforcement; aggregate report ingestion.
RFC 8460SMTP TLS reportingStalwart TLSRPT support; ingested into OBS.
RFC 8461MTA-STSOutbound policy fetch; inbound policy publication.
RFC 8617ARCARC stamping on forward.
RFC 8620JMAPNative JMAP server via Stalwart.
BIMI v1Brand IndicatorsVMC + SVG-Tiny logo serving on opt-in tenants.
OWASP Gen AI Top-10 (2025)LLM01: Prompt InjectionCaMeL dual-LLM is the canonical mitigation.

Risk entries

EMAIL carries the second-highest single-module risk weight after AUTH; injection attacks here propagate to every downstream module. Tracked in the risk register.

IDRiskLikelihoodImpactOwnerMitigation
R-EMAIL-001CaMeL bypass via novel injection techniqueMediumCatastrophicCSORed-team corpus refreshed monthly; the quarantined LLM has zero tools; CUO never sees raw body even on bypass.
R-EMAIL-002Stalwart CVE landing zero-dayMediumHighCTOSubscribe to security advisories; auto-PR + canary test on patch release; rollback plan documented.
R-EMAIL-003DKIM key compromise -> spoofing tenantLowHighCSOKMS-wrapped; rotation every 90 d; old selector retained 30 d.
R-EMAIL-004Outbound spam reputation collapse -> IP blockMediumHighCTOPer-tenant send rate limits; warm-up plan; SubMTA / dedicated IP at P2+; Postmaster Tools monitoring.
R-EMAIL-005Body-content leak into memory via misconfigured ruleLowHighCDOStatic-analysis CI gate on every memory-ingestion code path; canonical ingestion is summary-only.
R-EMAIL-006VN-residency tenant body lands in non-VN S3LowHighCCOS3 key-prefix audit nightly; failure pages the CSO.
R-EMAIL-007Attachment with malware reaches user (AV bypass)MediumMediumCSOClamAV + heuristic scan; "show raw" view in a sandbox iframe; user banner for unscanned attachments.
R-EMAIL-008JMAP / IMAP credential leak via SPA bugLowHighCTOOAuth 2.1 access tokens only; no IMAP password reuse; refresh-token rotation.
R-EMAIL-009Migration import overflows quarantine review queueMediumLowCTORate-limited import (1k msg / min); injection-flagged messages tagged but not blocking the import.
R-EMAIL-010AGPL-3.0 obligations on Stalwart redistributionMediumMediumCLOService-only distribution; source-code-offer URL on the Trust Center; legal review of every Stalwart fork.
R-EMAIL-011Thread-to-issue creates wrong Engagement on conversionMediumMediumCOOConversion shows the AM the auto-suggested Engagement (matched by domain); the AM must confirm or override; per-tenant default for unmatched.
R-EMAIL-012Genie draft includes confidential data from memory that the customer shouldn't seeMediumHighDPOThe Genie draft skill operates in "outbound" mode - the sync_class filter is applied at retrieval; it can only ground in shareable/client-visible memories; AM final review.
R-EMAIL-013Bulk-send approval bypassed via API callLowHighCSOApproval token required at the SMTP submission gate; a CI test verifies bulk-send rejection without a token; OBS alarm on direct-API bulk attempts.
R-EMAIL-014Tracked-domain misconfig - personal email auto-logged to CRMLowHighCSOTracked-domain config audited monthly; CI asserts common personal domains (gmail/yahoo/icloud) are excluded from default tracking; per-Member opt-out.
R-EMAIL-015CaMeL quarantine LLM cost spike from spam waveMediumLowCTOPre-filter blocks obvious spam before quarantine; AI Gateway cost cap enforced; alarm on EMAIL cost > 30% above the 7-day baseline.

KPIs

EMAIL health rolls up into KPIs covering deliverability, injection-defence efficacy, AI-feature quality, and shared-inbox UX.

KPIFormulaSourceTarget
Outbound DMARC pass ratedmarc.pass / dmarc.totalweekly aggregate>= 99%
Inbound deliverability250_ok / smtp_attemptsStalwart logs>= 99.5%
CaMeL injection-block rateblocked / red_team_corpusmonthly red-team>= 99.5%
CaMeL extraction p95histogramOBS<= 1.2 s
AI-draft acceptance ratedrafts_sent / drafts_offeredSPA telemetry>= 40%
Shared-inbox first-response p50median minutesmemory assignment events<= 30 min (BH)
Snooze usagesnoozes / Member / weekSPA telemetrytracked
Spam false-positive ratefp / total_classified_spamuser "not spam" actions<= 0.5%
Outbound CaMeL block rateblocked / sent attemptsmemorytracked; alert on > 1% / day
Attachment AV-quarantine ratequarantined / totalmemorytracked; alert on spike
Thread-to-issue conversion accuracyconversions retained / total conversions (90 d)PROJ events>= 0.90 (low rollback = good auto-match)
Genie draft confidential-leak ratedrafts containing private/personal memories / total draftsCaMeL post-write audit= 0 (hard floor)
Bulk-send token compliancebulk sends with approval token / total bulk sendsSMTP submission audit= 1.0 (hard floor; CI gate)
Tracked-domain audit pass ratetenants passing the monthly tracked-domain audit / totalmonthly CSO review= 1.0
CaMeL cost per inbound msgAI Gateway cost for CaMeL / total inboundcost ledgertracked; alarm on baseline drift > 30%

RACI matrix

EMAIL is owned by the CCO seat. Today (CCO vacant), the CEO is interim accountable; the CTO owns engineering; the CSO is consulted on every CaMeL-touching change.

ActivityCEOCTOCSOCCOCDOCLO
Service design + specARCCIC
ImplementationIA/RCIII
CaMeL boundary reviewICA/RICI
Stalwart upgradeIA/RRIIC
DKIM key rotationIRAIII
DMARC policy authoringCRAIIC
Shared-inbox productCCIA/RII
DSAR fulfilmentICCIRA
Trust Center publicationCCCRIA
AGPL compliance reviewICIIIA/R

R Responsible, A Accountable, C Consulted, I Informed.

Planned CLI surface

A single admin CLI cyberos-email-cli for tenant operators. Destructive commands always write a chained audit row before exit.

1. Create a shared inbox

$ cyberos-email-cli shared-inbox create \
 --tenant cyberskill \
 --address support@cyberskill.world \
 --display "CyberSkill Support" \
 --default-assignee linh@cyberskill.world

[shared-inbox created]
 id: 01HZK1Y2J3K4M5N6P7Q8R9S0T1
 address: support@cyberskill.world
 audit: memory seq=14902 chain=1a2b...3c4d

2. Rotate the per-tenant DKIM key

$ cyberos-email-cli dkim rotate --tenant cyberskill --reason "quarterly-scheduled"

[rotate] new selector: cyberos-2026-q2
[kms] old selector cyberos-2026-q1 retained for verification (30 d)
[dns] _domainkey.cyberskill.world TXT record updated
[audit] memory seq=14905 chain=5d6e...7f80

3. Import from Gmail

$ cyberos-email-cli import gmail \
 --user stephen@cyberskill.world \
 --since 2024-01-01 \
 --rate-limit 1000/min

[import] authenticating via XOAUTH2 ...
[import] selecting [Gmail]/All Mail ... 12,481 messages
[import] walking threads (CaMeL extracting on the fly) ...
[import] complete - 12,481 messages, 47 injection-flagged, 0 errors
[audit] memory seq=14952 chain=9a8b...7c6d

4. View a DMARC aggregate report

$ cyberos-email-cli dmarc report --tenant cyberskill --since 7d

domain policy sent pass fail pct
cyberskill.world reject 8,213 8,201 12 99.85%
- google.com - 4,102 4,098 4 99.90%
- outlook.com - 2,108 2,103 5 99.76%
- proton.me - 1,003 1,000 3 99.70%
- other - 1,000 1,000 0 100.00%

5. Run a CaMeL red-team replay

$ cyberos-email-cli redteam replay --corpus echoleak-v3 --output report.json

[replay] corpus: echoleak-v3 (412 messages)
[replay] running quarantined extraction ...
[replay] 410 / 412 injection attempts blocked (99.51%)
[replay] 2 escapes - see report.json#escapes
[audit] memory seq=14958 chain=ef01...2345

6. Export a DSAR bundle

$ cyberos-email-cli dsar-export --subject stephen@cyberskill.world --output stephen.zip

[dsar] subject: stephen@cyberskill.world
[dsar] threads: 1,247
[dsar] messages: 8,213 (3,142 inbound, 5,071 outbound)
[dsar] attachments: 412 (412 MB)
[dsar] summaries: 1,247 (CaMeL gist)
[dsar] written: stephen.zip (478 MB)
[audit] memory seq=14961 chain=6a7b...8c9d

7. Install a ManageSieve rule

$ cyberos-email-cli sieve install --mailbox stephen@cyberskill.world --file my.sieve

require ["fileinto", "imap4flags"];
if address:is "from" "noreply@github.com" {
 fileinto "GitHub";
 setflag "\\Seen";
 stop;
}

[sieve] parsed - 1 rule
[sieve] installed at priority 100
[audit] memory seq=14963 chain=2b3c...4d5e

Phase status & estimates

CapabilityStatus
Stalwart core (SMTP / JMAP / IMAP)planned, P1
CaMeL quarantined extractionplanned, P1
Per-tenant DKIM signingplanned, P1
Shared inbox + assignmentplanned, P1
Internal comments on threadsplanned, P1
AI-drafted reply (VN salutations)planned, P1
PGroonga Vietnamese searchplanned, P1
ManageSieve rule engineplanned, P1
Gmail / M365 / IMAP importplanned, P1
iCal calendar invite parsingplanned, P1
DMARC aggregate reportingplanned, P1
DSAR export bundleplanned, P1
BIMI logo serving (VMC)planned, P2+
Dedicated SubMTA / reputation IPplanned, P2+
Outbound encryption (S/MIME / PGP)planned, P3+
Multi-region active-activeplanned, P3+

References

All modules | Next module: PROJ

Changelog

History lives in the changelog; this page describes only the current state.


Generated from services/email/docs/index.md — edit the markdown source, not this file (TASK-DOCS-002).