quorate/docs/spec/authoritative-email-server-spec.md

13 KiB

Authoritative Email Server Specification

1. Scope

Stalwart is the authoritative email server for quorate.dev. It keeps hosted delivery inside the cluster and submits external delivery through OCI Email Delivery.

This specification owns the Stalwart deployment, configuration, routing, storage, protocol, identity, and filtering boundaries.

2. Topology

flowchart LR
    Ingress["Declared ingress surfaces"]
    Stalwart["Stalwart\n≥2 replicas"]
    Controller["Configuration controller\nexactly one owner"]
    PG["PostgreSQL\ndurable state"]
    S3["VersityGW\ndurable blobs"]
    Cache["DragonflyDB\nnon-durable state"]
    OCI["OCI Email Delivery"]
    Hosted["Hosted mailbox"]
    External["External recipient"]

    Ingress --> Stalwart
    Controller --> Stalwart
    Stalwart --> PG
    Stalwart --> S3
    Stalwart --> Cache
    Stalwart --> Hosted
    Stalwart -->|"authenticated TLS"| OCI
    OCI -->|"best-effort TLS"| External

3. Configuration Ownership

stateDiagram-v2
    [*] --> Empty
    Empty --> RecoveryAccess: one Job starts
    RecoveryAccess --> RecoveryAccess: retry after interruption
    RecoveryAccess --> Reconciled: controller applies ordinary desired state
    Reconciled --> Ownerless: Job, controller, and recovery workload removed
    Ownerless --> Steady: replicas and controller start
    Steady --> Steady: continuous reconciliation
Owner Authority
Git All readable, non-secret desired state
Vault Service credentials
Bootstrap Job At most one one-shot Job; owns only temporary recovery access and waits for controller readiness
Controller Sole owner of declared Stalwart configuration during initialization and steady operation, plus bounded certificate-ID derivation
Terraform Dependency and external-service state
Flux Bootstrap or normal workload state, never both

The bootstrap Job contains no Stalwart desired-state plan and performs no configuration mutation. It provides the recovery-authenticated management endpoint while the controller applies the ordinary desired-state plan, then exits after the controller reports successful convergence. No Stalwart Bootstrap object is used. The recovery credential and recovery workload are absent before the steady controller starts, where the permanent controller identity is authenticated independently.

The deployment has the following minimum identities:

Identity Kind Authority or use Identity authority Hosted mailbox
Recovery administrator Temporary automation Recovery-authenticated management during bootstrap only — None
Configuration controller Automation Declarative configuration reconciliation Zitadel None
Alert submitter Automation Authenticated cluster-local SMTP submission to the operator mailbox Zitadel None
Authenticated client test Automation TCP/465 and TCP/993 protocol qualification as [email protected] Zitadel Required
Human administrator Human Administration and operator mailbox membership Zitadel —
[email protected] Shared mailbox Operational alerts and Android shared-folder access Zitadel-authenticated members Required
[email protected] Test mailbox Authenticated client, inbound delivery, and routing acceptance Authenticated client test Required

Zitadel provides the human-administrator role. The human administrator is a member of the operator shared mailbox.

Human identity remains Zitadel-owned. Mail clients that cannot use the Zitadel access token through OAUTHBEARER or XOAUTH2 use a per-user Stalwart app password as a revocable protocol credential.

The controller:

Field Contract
Ownership PostgreSQL advisory lock; a competing owner performs no reconciliation
Interval 5 minutes
Timeout 2 minutes per reconciliation
Retry Exponential backoff from 5 seconds to 5 minutes
Persistent failure Unready after 5 consecutive failures while retries continue
Desired input Git state plus runtime-projected secret values
Derived input Server certificate ID resolved from its declared SAN
Secret rotation Vault-owned service credentials converge after rotation
Pruning Exhaustive declared configuration is pruned; accounts are upserted
Status Liveness, readiness, last success, persistent failure, and plan revision

Manual API patches, generated snapshot adoption, and live kubectl mutation are not deployment or configuration mechanisms. Break-glass mutation is limited to failure response and teardown.

App-password frontend Contract
Implementation Custom UI backed by Zitadel login and Stalwart app-password API
Scope Signed-in user's app passwords
Endpoint mail.quorate.dev/account/
Ingress Gateway R and direct Stalwart frontend
TLS Gateway R certificate on both ingress paths
Root redirect HTTPS / → /account/

4. Protocol Boundary

Surface Required listener availability TLS Authentication
SMTP TCP/25 Public-ingress target Opportunistic STARTTLS AUTH prohibited
Submissions TCP/465 Public-ingress target Implicit TLS Zitadel token or per-user app password
Submission TCP/587 Cluster-local Cilium transparent encryption Vault-backed automation authentication
IMAPS TCP/993 Public-ingress target Implicit TLS Zitadel token or per-user app password
JMAP and user HTTPS TCP/443 Public-ingress and direct target Required Zitadel-backed human authentication
Administration API at mail.quorate.internal Internal-ingress target Required outside the cluster Zitadel-backed human authentication

5. Routing

flowchart LR
    Copy["Accepted recipient copy"]
    Classify{"In Stalwart's\nhosted-domain set?"}
    Local["Local delivery\nPostgreSQL + VersityGW"]
    Explicit{"Explicit external\nrecipient or disclosure?"}
    Relay["OCI Email Delivery"]
    Reject["Reject"]

    Copy --> Classify
    Classify -->|yes| Local
    Classify -->|no| Explicit
    Explicit -->|yes| Relay
    Explicit -->|no| Reject
Condition Result
Hosted account, alias, or mailbox Deliver or queue entirely inside the cluster
Unknown hosted recipient Reject during SMTP without a later bounce
External authenticated recipient Submit through OCI Email Delivery
Mixed recipients Keep the hosted copy local; submit only the external copy
Explicit external forwarding or report Treat as an external disclosure and minimize quoted original content
OCI failure Keep hosted delivery available and queue external delivery
Local-store failure Queue locally or return 451; never reclassify as external
SMTP pressure Apply bounded session and concurrency controls; return retryable 451 where SMTP permits it
Routing loop Never convert a hosted destination into external delivery

Hosted-domain classification comes from Stalwart's authoritative domain set, not suffix matching or a duplicated routing list. Authenticated cluster-local submission to hosted recipients never contacts OCI.

6. Storage and Credentials

Component Role
PostgreSQL Durable account, metadata, queue, and search state
VersityGW Durable message bodies and attachments
DragonflyDB Disposable Redis-compatible state
Vault Source authority for service credentials

Stalwart reports SMTP success only after the required PostgreSQL state and VersityGW blob are durable. Dependency failure causes local queueing or a temporary failure without changing the recipient's routing class.

Coordinated PostgreSQL and VersityGW recovery is outside this specification.

7. Encryption and Filtering

The server-wide application-encryption capability is enabled.

Identity Application-layer encryption policy
Recovery, controller, and alert-submitter identities Not applicable; none has a mailbox
[email protected] Disabled
External human account Enabled by default; explicit per-account opt-out

Human onboarding records the OpenPGP public key or S/MIME certificate, recovery implications, supported search boundary, spam-training consequences, and append-encryption choice. Delivery and append encryption are verified independently.

Encrypted and plaintext accounts coexist without changing each other's policy. Plaintext accounts retain body search. Encrypted accounts expose only supported header search. Plaintext spam training requires recorded consent.

Stalwart's built-in spam and phishing filter is the only v1 content filter. It runs before application-layer encryption for unauthenticated inbound mail. Authenticated alert submission bypasses inbound spam classification.

Resolver and filter-update concurrency, cache, and timeouts are bounded. Resolver failure does not reject all inbound mail. Filter-update failure keeps the last valid data or enters the declared bounded degraded behavior without opening general egress.

OpenPGP and S/MIME remain available when message content must stay opaque to OCI Email Delivery and subsequent relays.

8. Mail Identity

SPF, OCI DKIM, DMARC, the envelope sender, bounce handling, MTA-STS, and TLS reporting align with the quorate.dev authority.