docs: add multi-perspective Mail handbook

This commit is contained in:
2026-07-21 17:29:01 +02:00
parent 4a9201aeea
commit 0f1327638e

379
docs/MAIL_HANDBOOK.md Normal file
View File

@@ -0,0 +1,379 @@
# Mail Handbook
## Purpose and status
This handbook is the canonical multi-perspective description of the GovOPlaN
Mail module. Mail is a governed transport and mailbox capability. It is not a
campaign definition, contact database, or general records store.
| Perspective | Start here |
| --- | --- |
| User selecting a profile or reading a mailbox | [User tasks](#user-tasks) |
| Mail profile administrator | [Profile administration](#profile-administration) |
| Policy or tenant administrator | [Policy hierarchy](#policy-hierarchy) |
| Delivery operator | [Operations and recovery](#operations-and-recovery) |
| Integrator | [Capability contract](#capability-contract) |
| Security or audit reviewer | [Security, deletion, and audit](#security-deletion-and-audit) |
| Release reviewer | [Acceptance checklist](#acceptance-checklist) |
See also [Mail protocol roadmap](MAIL_PROTOCOL_ROADMAP.md) and the Campaign
[Mail profile boundary](https://git.add-ideas.de/add-ideas/govoplan-campaign/src/branch/main/docs/MAIL_PROFILE_BOUNDARY.md).
## Domain ownership
Mail owns:
- reusable SMTP/IMAP profile definitions and scope;
- encrypted SMTP/IMAP credentials and safe credential replacement;
- effective profile policy and visibility/authorization decisions;
- connection tests and protocol adapters;
- SMTP send and IMAP append operations exposed to consumers;
- read-only mailbox folder/message access and its bounded indexes; and
- the transport sanitization boundary and throttling behavior. A general
Mail-owned provider-attempt/diagnostic ledger remains planned.
Consumers own their business intent. For example, Campaign owns message
content, recipients, approval, jobs, and delivery evidence, while Mail owns the
profile and performs the provider operation. A consumer stores only a stable
Mail profile reference and non-secret evidence returned by Mail. It never
receives decrypted credentials or a raw resolved transport configuration.
Core owns authentication, tenant context, permission evaluation, database
session primitives, cryptographic secret helpers, audit infrastructure, and the
module registry. Optional consumers provide narrow context through capabilities;
Mail does not import their ORM or service implementations.
## Concepts
### Profile
A profile is a reusable, named delivery identity with optional SMTP and IMAP
configuration. It has a stable id, lifecycle state, scope, owner context, and
non-secret connection metadata. Passwords are write-only encrypted values and
are never returned through list/read/capability responses.
Profiles may be scoped to system, tenant, user, group, or campaign context.
Scope controls where a profile can be discovered; effective policy can narrow
that further. A visible profile is not automatically authorized for every
operation: using, testing, managing, and managing secrets are separate rights.
### Policy
Mail policy controls approved profile ids, which lower scopes may define
profiles, allowed/denied hosts and addressing patterns, credential inheritance,
and lower-level limits. The effective result is assembled from applicable
system, tenant, user/group, and campaign context. Denials and locked parent
limits cannot be relaxed by a lower scope.
### Transport identity and revision
Mail owns opaque random revisions for relevant SMTP/IMAP identity and
configuration. They are concurrency tokens, not deterministic hashes that a
consumer could use to guess a host or account. Credentials are excluded. A
consumer can freeze these revisions at build time and ask Mail to require the
same identity when the effect is later performed. Credential rotation alone
therefore need not invalidate prepared work, while a host/account/protocol
identity change does.
The revision is evidence and a concurrency guard, not a substitute for
authorization. Mail re-evaluates profile activity, visibility, policy, and
revision immediately before it resolves credentials and performs the
effect.
### Provider outcomes
SMTP acceptance, recipient refusal, temporary/permanent failure, connection
loss, and unknown outcome are distinct. IMAP append is a separate operation and
outcome. Mail returns a sanitized transport result; the consuming domain owns
the durable business job and its retry/reconciliation semantics.
## User tasks
### Choose a profile
1. Open the task that requires mail, such as Campaign **Mail settings**.
2. Choose from the profiles visible and authorized for the current tenant,
owner, group, and task context. Never enter or copy a profile id manually
when the UI can present a picker.
3. Review the safe summary: name, scope, active state, SMTP/IMAP availability,
and policy-relevant sender identity. Credentials and raw provider internals
are not visible.
4. Save the reference in the consuming module. If ownership or policy changes,
select and validate again.
The user needs `mail:profile:use`; reading profile summaries and testing may
require their own scopes. The consuming module still requires its own action
permission.
### Test a profile
An authorized profile test verifies connection and authentication for the
selected active/visible SMTP or IMAP profile using Mail-owned credentials. The
consumer-use path evaluates effective Mail policy separately. Use a
non-production provider and mailbox first. A successful connection test does
not prove policy authorization for a later Campaign context, deliverability,
recipient acceptance, SPF/DKIM/DMARC alignment, or future availability.
Raw settings test endpoints accept new settings only for actors who may both
test profiles and manage secrets. They are an administration aid, not a way for
ordinary consumers to bypass reusable profiles.
### Read a mailbox
The current mailbox UI and API are read-only. An authorized user can list IMAP
folders, page through messages, and inspect a bounded full message. Folder
names are parsed and quoted defensively; Sent-folder discovery uses provider
flags and common names.
Mailbox access requires both `mail:mailbox:read` and `mail:profile:use`. It must
not mutate read/unread, delete, move, or reply state. Message responses are
bounded by the deployment response policy; ordinary UI should avoid loading a
whole large mailbox or attachment merely to show a list.
## Profile administration
### Roles
The supplied templates are:
- **Mail profile user:** read/use/test approved profiles and read permitted
mailboxes without reading secrets.
- **Mail profile administrator:** additionally create/update profiles and
create/replace encrypted credentials.
The specific permissions are `mail:profile:read`, `mail:profile:use`,
`mail:profile:test`, `mail:mailbox:read`, `mail:profile:write`, and
`mail:secret:manage`. System-scoped definitions use the corresponding system
settings authority. Keep secret management separate when an institution wants
profile metadata administrators not to know or replace credentials.
### Create or change a profile
1. Choose the narrowest suitable scope and a stable, descriptive name/slug.
2. Configure SMTP, optional IMAP, TLS mode, account identity, Sent-folder
behavior, and timeouts. Sender/envelope/recipient constraints belong to
effective Mail policy; Campaign rate limits remain delivery configuration.
3. Enter credentials only in the dedicated credential fields. Returned profile
data indicates whether credentials are configured without returning them.
4. Save and run SMTP/IMAP tests against a non-production target.
5. Verify the effective policy for every intended consumer context.
6. Communicate changes that alter the non-secret transport identity; prepared
consumer snapshots will deliberately stop until rebuilt.
An update that omits a password preserves the current encrypted password. A
credential replacement never depends on reading the old cleartext value back.
### Delete a profile
Profile deletion is immediate for Mail-owned secrets and audit evidence:
1. Mail clears both encrypted SMTP and IMAP passwords in the same transaction.
2. It deactivates the remaining non-secret tombstone state so historical stable
references can fail safely rather than resolve to another profile.
3. When owned encrypted secrets existed, it emits
`mail.profile_credentials_deleted` with profile id, scope, reason, and the
kinds of secret removed—never values, usernames/passwords, or raw
configuration.
4. If scrubbing or audit insertion fails, the transaction rolls back and the
deletion fails.
5. Repeating deletion after the secrets are gone is an idempotent no-op for the
secret-deletion effect.
Consumers that still reference the profile fail closed at validation or
execution. Deletion does not silently rebind them to a default profile.
## Policy hierarchy
Effective policy is contextual. Administrators should document:
- which profile ids are approved globally or for a tenant;
- whether tenant, user, group, or campaign-scoped profiles may be created;
- allowed and denied SMTP/IMAP hosts;
- permitted From, envelope sender (including bounce address), and envelope
recipient-domain patterns;
- whether SMTP/IMAP credentials inherit from the reusable profile; and
- which lower-level settings are locked by a parent policy.
Campaign delivery requires reusable profile credentials. A legacy policy that
requires campaign-local credentials fails closed with guidance to store them on
the Mail profile and enable effective inheritance. This preserves compatibility
of the policy model without reopening a consumer-owned secret store.
Policy reads are available through system/tenant/context routes to suitably
authorized actors. Adaptive Docs exposes a safe explanation of the effective
tenant posture; it does not expose credential material.
## Operations and recovery
### Deployment egress policy
Private-network connector access is controlled deployment-wide by
`GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS`. Whether private or public targets
are allowed, SMTP and IMAP resolve, validate, and connect to the exact approved
address records at connection time while retaining the original hostname for
TLS SNI and certificate verification. A DNS change cannot redirect the socket
after validation.
Transports that cannot pin every connection peer or revalidate protocol-managed
redirects/referrals must fail before client construction. Do not weaken this
rule to make a target test pass; change the connector implementation or the
target topology.
### Response and time bounds
The shared defaults are 16 MiB for structured connector responses and 512 MiB
for file transfers. Mailbox full-message fetch additionally uses bounded IMAP
ranges and the applicable deployment limit. Operators may lower limits for
their environment. A declared or streamed over-limit response fails visibly
rather than being retained partially as if complete.
Configure connection/read timeouts and provider-specific message/attachment
limits. A timeout after the SMTP effect may be an unknown outcome; it must not
be flattened into a safe-to-retry connection failure.
### Throttling
Mail rate limiting uses a Redis lock and next-allowed timestamp across worker
processes when worker mode is enabled. Direct development uses a process-local
limiter. If Redis fails in worker mode, the current implementation silently
falls back to a limiter that protects only that process; it does not provide
cluster-wide coordination or emit its own health signal. Production operators
must therefore monitor Redis independently and should stop or restrict
multi-worker delivery during an outage until explicit degradation telemetry is
implemented.
### SMTP/IMAP incidents
1. Stop new consumer work if duplicate effects or credential compromise are
possible.
2. Preserve safe Mail, consumer-job, worker, and provider evidence.
3. Rotate credentials in Mail. Rebuild consumers only if the revisioned
non-secret identity also changed.
4. Treat post-command connection loss as potentially unknown until provider
evidence establishes whether SMTP accepted the message.
5. Handle IMAP append failure independently; do not resend an accepted message
merely to recreate a Sent copy.
6. Record the incident/reconciliation reference in the consuming domain's audit
trail without copying raw provider secrets or message content unnecessarily.
### Backup, restore, and retirement
Backups contain encrypted credentials and therefore need the same protection as
the live database and key material. Restoring a Mail database without the
matching encryption key makes credentials unusable; restoring it with keys can
reactivate sensitive historical state and must be controlled.
Destructive module retirement first applies the same immediate credential
scrub/audit rule to every remaining profile, then drops Mail-owned tables after
the installer snapshot/confirmation gate. Any scrub or audit failure blocks
retirement. Historical backups are separate retained copies and require the
deployment's approved backup-destruction policy.
## Capability contract
Mail provides `mail.campaign_delivery` version `0.2.x`. This version is a
breaking ownership boundary: it exposes reference-oriented operations, not raw
resolved configuration or credentials.
The Mail REST API supplies the profile list used by pickers. Given a stable
profile reference, the capability supports these kinds of operation:
- resolve a profile reference and evaluate its Mail context/policy to produce a
non-secret delivery summary and
opaque Mail-owned revisions;
- send Campaign-owned RFC message bytes using the selected profile;
- optionally append an accepted message to Sent; and
- return sanitized outcome/revision evidence.
For a real effect, one Mail call must:
1. resolve current campaign/tenant/owner context through the optional narrow
context capability;
2. load the selected active profile;
3. evaluate profile-scope eligibility and effective policy;
4. compare the consumer's expected opaque transport revision;
5. decrypt credentials in Mail memory;
6. perform the SMTP or IMAP operation; and
7. return only the safe result.
Keeping comparison, credential resolution, and transport inside one operation
avoids a time-of-check/time-of-use gap and prevents the consumer from becoming
a credential-processing boundary. The capability does not receive the acting
principal: the Campaign route must first enforce `mail:profile:use`, while Mail
still re-evaluates active profile scope and contextual policy inside the call.
Mail optionally consumes `campaigns.mail_policy_context` and
`addresses.lookup`. Their physical absence must leave Mail installable and its
unrelated profile/mailbox behavior usable. Interface-version compatibility is a
mandatory release gate.
## Security, deletion, and audit
Security invariants:
- Decrypted SMTP/IMAP passwords never cross the Mail capability/API boundary.
- Password fields are write-only and encrypted at rest; safe responses expose
configuration state, not values.
- Consumers persist stable profile references, not transport copies.
- Host policy is deployment-wide and every supported connection is DNS/IP
pinned at socket creation.
- Redirect/referral-capable transports fail closed unless every peer can be
revalidated and pinned.
- Profile deletion and destructive retirement scrub every Mail-owned encrypted
password and emit non-secret audit in the same lifecycle action.
- Mailbox reads are separately authorized, read-only, paginated, and bounded.
- Logs/audit/results minimize message content, addresses, provider responses,
and secret-like values.
Credential replacement/deletion emits canonical non-secret Mail audit events.
Profile/policy create/update/deactivate currently emits change-feed evidence,
but connection tests and the complete administrative lifecycle do not yet have
equivalent canonical audit events. Consumer send/retry/reconciliation evidence
belongs primarily to the consuming module today. A Mail-owned restricted
provider-attempt ledger with correlation, retention, and unknown-outcome
reconciliation remains part of the durable outbox work in
[`govoplan-mail#17`](https://git.add-ideas.de/add-ideas/govoplan-mail/issues/17).
## Acceptance checklist
Before claiming a Mail composition is production-ready:
1. Install Mail alone with Core and with each optional consumer absent.
2. Prove profile scope, visibility, use, test, write, and secret permissions for
representative tenant/user/group contexts.
3. Prove policy deny precedence, locked lower limits, approved-profile mode, and
the campaign-local-credential fail-closed path.
4. Verify all read/list/capability/audit/log responses contain no plaintext or
encrypted password, raw credential token, or consumer-owned inline config.
5. Test DNS rebinding resistance and private/public deployment policy for SMTP
and IMAP.
6. Test SMTP acceptance, refusal, temporary/permanent failure, timeout/unknown
outcome, and IMAP append success/failure against the target provider.
7. Test Redis worker throttling and prove that independent infrastructure
monitoring detects Redis loss; restrict multi-worker delivery during the
current silent local-only fallback.
8. Delete tenant and system profiles, inject audit failure, repeat deletion, and
prove transactional scrub/rollback/idempotency.
9. Run destructive-retirement preflight on a snapshot and prove credentials are
scrubbed/audited before table drop.
10. Pass module permutations, interface/version alignment, WebUI/i18n, full
security audit, backup/restore, and target-environment release checks.
## Explicitly planned, not yet claimed
- Durable, idempotent Campaign report delivery with Mail-owned attempts,
unknown-outcome reconciliation, and partial-refusal evidence
([`govoplan-mail#17`](https://git.add-ideas.de/add-ideas/govoplan-mail/issues/17)).
- Canonical audit events for profile tests and the remaining profile/policy
administration lifecycle, plus an operator-visible Redis-throttling
degradation signal.
- Reusable SMTP batch sessions and their final connection/error/isolation
semantics (`govoplan-mail#16`).
- Final Campaign **test / single send / single resend** semantics; those are a
Campaign business-action contract built on Mail transport operations.
- JMAP mailbox synchronization/search; it is preferred only after the IMAP MVP
is stable.
- POP3 except for a future explicit legacy download/import requirement.
- A full mail client with compose/reply/move/delete/read-state mutation.
- Proof that process-local throttling coordinates multiple workers when Redis
is unavailable; it deliberately does not.