Files
govoplan-mail/docs/MAIL_HANDBOOK.md
T
2026-09-08 01:32:44 +02:00

760 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/GovOPlaN/govoplan-campaign/src/branch/main/docs/MAIL_PROFILE_BOUNDARY.md).
## Domain ownership
Mail owns:
- reusable SMTP/IMAP/JMAP profile definitions, dedicated legacy POP3 sources, and scope;
- encrypted SMTP/IMAP/JMAP/POP3 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 IMAP/JMAP mailbox folder/message access, bounded indexes, and encrypted
pending-review records imported from legacy POP3 sources; 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.
## Deployment configuration packages
Mail registers the `mail.configuration` capability for `smtp_profile`
fragments. The provider reads the validated `mail.smtp` entry from the
installer-generated infrastructure capability receipt. Receipt host and port
are authoritative; the generic package workflow asks for missing non-secret
transport fields such as security mode. An external relay may operate without
authentication, or the operator may select an existing credential-envelope id.
Inline usernames, passwords, tokens, and secret values are rejected.
Tenant scope is the default. A system-scoped profile requires system settings
or governance write authority. The fragment's stable slug is its idempotency
identity: an absent profile is created, an exact profile is skipped, and a
conflicting profile is preserved unless the reviewed fragment explicitly sets
`on_conflict` to `update`. Credential bindings are added idempotently and are
never removed merely because a package omits a credential reference.
Mail also registers a module-owned infrastructure dependency provider. Its
authorized Ops inventory lists every persisted SMTP endpoint and legacy SMTP
profile by stable non-secret reference, state and scope, together with numeric
credential-binding evidence. Before the host deployer changes or removes
`mail.smtp`, it requires a fresh, complete inventory from the same installation
and displays these dependencies in the plan. The inventory never contains
transport credentials or decrypted envelope data.
Preflight does not prove SMTP reachability. After apply, use the normal Mail
profile test and Ops health surfaces. If the receipt says SMTP is unavailable,
is invalid, or is not mounted, import is blocked with an operator-facing
resolution instead of creating a partial profile.
Configuration packages remain SMTP-focused. A POP3 legacy source is a deliberate
operational migration action and is not silently exported, cloned, or enabled by
an SMTP profile package.
## Interface patterns and unavailable actions
Mail uses the platform's shared explorer, connection tree, adaptive form,
effective-policy, dialog, status, alert, and confirmation components. The
module-owned surface inventory and consequence classification are recorded in
[Mail interface pattern migration](INTERFACE_PATTERN_MIGRATION.md).
An unavailable action remains visible when it belongs to the current task. Its
hover/focus explanation identifies the missing field, active operation, or
permission. Larger blockers state what must change, who can change it, and
where to go. Contextual Help Center links resolve to the configured Docs module
when installed and otherwise use the public documentation fallback.
Mailbox reads, profile reloads, connection tests, policy saves, bounce scans,
and destructive actions each expose their current loading or blocked state.
Removing a bounce watcher and deactivating profile hierarchy items use shared
confirmation dialogs. Removing a watcher retains existing observations;
deactivating a profile may scrub Mail-owned credentials as described below.
## Concepts
### Profile
A profile is a reusable, named delivery identity with optional SMTP and IMAP
configuration and one or more opt-in JMAP mailbox endpoints. It can additionally own a dedicated POP3 endpoint for an
explicit legacy-import workflow. 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.
An IMAP server may map the standard Inbox, Sent, Drafts, Trash, Archive, and
Junk roles to exact provider folder names. These mappings belong to the reusable
profile/server. Empty roles retain automatic behavior. The historical
`imap.sent_folder` value is read as the Sent mapping and remains synchronized
for compatibility; a Campaign-specific Sent override still wins for that
Campaign.
Use readable Unicode names such as `Entwürfe`, not IMAP wire encodings such as
`Entw&APw-rfe`. Discovery decodes modified UTF-7 before detecting standard
folder roles; SELECT, STATUS, and Sent APPEND encode and quote the chosen name
for the active connection. Literal ampersands, quotes, backslashes, and Unicode
characters round-trip without renaming remote folders. Previously saved
wire-form names are resolved against that account's live folder list; if the
same string is also an actual readable folder name, the readable name wins.
Rediscovery and an explicit profile save replace old encoded configuration
values with readable names; background reads never rewrite configuration.
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 SMTP/IMAP/JMAP 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/JMAP 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.
POP3 imports pin the endpoint/credential transport revision at preview time. A
changed revision or a missing provider UIDL stops import and requires a fresh
preview. POP3 does not participate in the ordinary mailbox folder/message
projection.
### Provider outcomes
SMTP acceptance, partial or complete recipient refusal, temporary/permanent
failure, connection loss, and unknown outcome are distinct. IMAP append is a
separate operation and outcome. Ordinary Campaign recipient delivery retains
its Campaign-owned job ledger. Mail owns an encrypted durable command and
attempt ledger for report delivery and other effects that do not have such a
consumer ledger.
The Mail worker commits an attempt before it starts SMTP and then commits an
effect-start marker before opening the provider effect. Worker redelivery may
recover a stale pre-effect claim, but an accepted, partially accepted,
in-progress-after-effect, or unknown command is never sent automatically.
Unknown outcomes require a separately authorized reconciliation with an
external evidence reference. A deliberate resend creates a new command with a
new idempotency key and links it to the prior command.
## 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/JMAP 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, IMAP, or JMAP 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.
Campaign runtime authorization follows the protocol being used: SMTP batch and
single-message calls enforce the explicit SMTP credential policy, while
append-to-Sent enforces the IMAP credential policy. A valid SMTP call does not
need to carry an unrelated IMAP credential just because the profile supports
both protocols. Full campaign authoring validation and complete profile
summaries continue to require both configured selections when their policies
forbid inherited credentials. The selected protocol's missing credential,
inactive/unauthorized binding or stale transport revision still stops the
operation before decryption or provider contact. Resolving a policy rejection
never requires disabling TLS or weakening either credential policy. Correcting
this runtime check does not change stored configuration or approved builds,
reset job state, or retry/send messages automatically.
Testing a saved profile requires both `mail:profile:test` and
`mail:profile:use`, and the profile must be active. Profile creation or test
authority alone is not enough.
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 persistent workspace header contains the profile selector, **Mailbox tools**,
Help, and one right-aligned **Reload**. These controls remain available when the
mailbox is empty or no usable profile is configured. Reload rechecks the permitted
profiles and refreshes the current folder catalogue and bounded message page;
it also rereads a selected message if that message remains on the page. IMAP
keeps the page offset. JMAP starts a fresh cursor chain at page one while keeping
the search term. An unavailable profile is replaced only by another currently
authorized active profile, or the explicit no-profile state.
Folder icons expand or collapse; labels select. Synthetic grouping labels select
the group without reading a nonexistent provider folder, and Reload in that
state refreshes only the folder catalogue. Refreshes keep user-controlled
expansion. A failed refresh preserves usable loaded data, shows the error, and
leaves Reload available for retry; late responses from an earlier profile or
tenant cannot replace the current context.
Mailbox context reads bypass browser response/promise reuse so an immediate
Reload really rechecks permissions and state. Mail's bounded server-side index
is unchanged; its explicit refresh flag and live/cached provenance still apply.
Failed pagination restores the page and page-size labels belonging to retained
rows. Dismissing or changing the preview while Reload is pending takes
precedence over its remembered selection.
On narrow screens, scroll vertically through folders, message list, and preview
within the mailbox workspace; the profile/tools/Reload header remains visible.
**Mailbox tools** groups the occasional profile-only, folder-only, and
message-only refreshes in a dialog, separate from **Bounce status**. Bounce
status requires `mail:bounce:read` or `mail:bounce:manage`; without either it
remains visible and disabled with an explanation. Escape closes this dialog
without clearing the selected message. These read controls do not grant profile
administration, send SMTP messages, APPEND messages, or change mailbox flags.
The current mailbox UI and API are read-only. An authorized user can list IMAP
or JMAP folders, page through messages, and inspect a bounded full message.
IMAP folder names are decoded for display and encoded and quoted defensively
for mailbox commands; Sent-folder discovery uses provider flags and common
readable names. The default IMAP4rev1 mode uses modified UTF-7. A connection
that has explicitly enabled `UTF8=ACCEPT` uses UTF-8 instead; merely advertising
that capability does not change encoding. Invalid provider encodings produce
an explicit error rather than a replacement name that could address another
folder. Refresh any already-loaded folder list after upgrading. JMAP discovers the Session and Mail account,
uses `Mailbox/get` hierarchy and roles, runs text search with `Email/query`, and
uses `Email/get` for bounded summaries/details. `Email/changes` exposes a
bounded incremental cursor; an expired state tells the caller to perform a full
refresh. The list exposes the provider's `Seen`/`$seen` state as a
read/unread indicator without changing it. It also labels whether the current
page came directly from the provider, from the bounded mailbox index, or from
an index while a refresh is in progress, including the index timestamp when
available.
The mailbox-name boundary follows [RFC 3501 section 5.1.3](https://www.rfc-editor.org/rfc/rfc3501.html#section-5.1.3)
and, only after explicit activation, [RFC 6855 section 3](https://www.rfc-editor.org/rfc/rfc6855.html#section-3).
Message HTML is displayed only in the shared sandboxed message component.
Remote URLs and active markup are removed, embedded `data:`/`cid:` image
references remain isolated, and plain text is always available when supplied.
Attachments and provider/content failures remain explicit rather than being
silently interpreted as an empty message.
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 endpoint response/body policy; ordinary UI should avoid loading a
whole large mailbox or attachment merely to show a list.
JMAP is opt-in per server endpoint. Its authenticated Session URL is governed
by `jmap_hosts`; an advertised API URL on another origin fails closed unless
that origin is explicitly listed for the endpoint. Bearer tokens or Basic
credentials are stored only in Mail credential envelopes. This slice does not
send through JMAP, mutate provider state, download attachment binaries, or add
calendar/contact/thread features.
### Import a legacy POP3 mailbox
POP3 is available only for bounded migration from a legacy server that cannot
provide IMAP or JMAP. It is disabled by default and is not a replacement for
the read-only mailbox UI.
1. An actor with profile-write, secret-management, and `mail:pop3:manage`
authority opens **Legacy POP3 import**, selects a Mail profile, and creates a
dedicated source. The UI stages the endpoint disabled, stores its encrypted
username/password credential, and enables it only after both operations
succeed.
2. The administrator explicitly enables legacy import, sets TLS mode, timeout,
maximum message and batch sizes, and preview body lines, and tests connection,
authentication, TLS, and provider message count. Plain transport remains
subject to deployment egress/security policy and should not be used across
an untrusted network.
3. An operator with `mail:profile:use` and `mail:pop3:import` refreshes a live
preview of at most 100 messages. Preview sends no `DELE`, changes no flags,
and exposes only bounded headers/body text. A server without stable UIDL
identifiers is rejected. If `TOP` is unavailable, Mail uses `RETR` only
inside the configured size bound and suppresses an oversized preview.
4. The operator selects messages. Mail downloads within the size gate and
creates encrypted local `pending_review` records. Tenant, profile, endpoint,
and UIDL form the duplicate boundary. Raw content never appears in list,
provider-state, audit, or DSAR output.
5. Source messages remain untouched by default. Delete-after-import requires
the endpoint's separate `allow_delete_after_import` policy, the operator's
`mail:pop3:delete` permission, an explicit per-batch choice, and destructive
confirmation. Mail commits the local import plus `mail.pop3.imported` audit
evidence before sending `DELE`. It separately records
`mail.pop3.source_deletion`; disconnect during `QUIT` is outcome-unknown and
must be reconciled before another destructive attempt.
The supplied **Mail legacy import operator** role can test, preview, and import
without deleting. The **Mail profile administrator** role also contains source
management and destructive-delete permissions; deployments should remove or
split `mail:pop3:delete` when operators must never delete provider messages.
Imported records follow configured Mail/records retention and require manual
review for a data-subject request or deletion decision.
Contextual help is available from the page and from each policy-sensitive
source setting, credential field, size limit, import action, and destructive
confirmation. Press F1 while a control has focus to open the Mail-owned German
reference for the exact POP3 context; the same topic remains available through
the page help action.
## Profile administration
### Roles
The supplied templates are:
- **Mail profile user:** read/use/test approved profiles and read permitted
mailboxes without reading secrets.
- **Mail profile self-service user:** additionally create, edit, deactivate,
and manage credentials only for the current account's own user-scoped
profiles, subject to the effective Mail policy.
- **Mail profile administrator:** additionally create/update profiles and
create/replace encrypted credentials across tenant-owned scopes, configure
legacy POP3 imports, and—unless the template is narrowed—request source
deletion after import.
- **Mail legacy import operator:** test approved POP3 sources and preview/import
messages without permission to delete them at the provider.
The specific permissions are `mail:profile:read`, `mail:profile:use`,
`mail:profile:test`, `mail:mailbox:read`, `mail:profile:write_own`,
`mail:secret:manage_own`, `mail:profile:write`, `mail:secret:manage`,
`mail:pop3:manage`, `mail:pop3:import`, and `mail:pop3:delete`.
The `_own` permissions are enforced against the authenticated membership id and
never authorize a tenant, group, campaign, system, or another user's profile.
They also do not authorize profile-policy changes. 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.
That separation is fail-closed for transport rebinding: changing an SMTP or
IMAP host, port, or security mode while the profile retains a stored password
requires the matching secret-management permission. A credential-free profile
can be deactivated with profile-write authority alone; deactivation that
scrubs a stored password also requires secret-management authority and records
the deletion in the audit log.
### Create or change a profile
The configured Help Center exposes **Create a custom Mail profile** only when
the current actor has broad or self-service profile-write authority and the
effective user-scope policy permits user profiles. It states the active SMTP/IMAP/JMAP hostname
allow-list groups and deny rules, plus the actor's separate credential, test,
use, and approval requirements. The Settings task creates in the current
account's user scope. Grant `mail:profile:write_own` for self-service;
`mail:profile:write` remains broad profile administration authority.
1. Choose the narrowest suitable scope and a stable, descriptive name/slug.
2. Configure SMTP and optional IMAP, or add an optional JMAP Session endpoint after creating the profile. Set TLS mode, account identity, standard folder
mappings, and timeouts. Folder discovery proposes provider-visible Inbox,
Sent, Drafts, Trash, Archive, and Junk names without mutating the mailbox.
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/JMAP 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.
The shared credential editor resolves Mail server restrictions from the
authorized metadata catalogue when it opens. Names appear as loading completes;
no page refresh is required, and typing in a draft does not reload the catalogue.
Closing and reopening refreshes the available servers and can retry a temporary
metadata failure. Inactive servers remain labelled inactive; deleted or
unauthorized references remain visible as unavailable and are never silently
removed from the credential. Labels do not grant permission to use a server,
and the lookup does not retrieve secrets.
If saving a reusable credential fails, the editor shows the error beside the
unchanged draft. Retry explicitly after correcting the cause. Saving disables
editing and closing until the request finishes; a failed save never silently
discards a replacement secret that has not been stored.
### Delete a profile
Profile deletion is immediate for Mail-owned secrets and audit evidence:
1. Mail clears encrypted SMTP, IMAP, JMAP, and POP3 credential envelopes 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/JMAP hosts;
- permitted From, envelope sender (including bounce address), and envelope
recipient-domain patterns;
- whether SMTP/IMAP may use a default credential or require an explicit Mail-owned credential selection; and
- which lower-level settings are locked by a parent policy.
In **Mail profile policy → Credential selection**, SMTP and IMAP have separate
controls. **Allow profile default credential** (`inherit: true`) permits either
the selected server's default credential or an explicit authorized Mail-owned
credential. **Require explicit Mail credential** (`inherit: false`) requires a
server and credential reference in Campaign Mail settings. Neither option
permits campaign-local passwords or copies a secret into Campaign.
**Inherit policy from parent** leaves the local value unset; it is different
from allowing a server's default credential. System policy always has a concrete
choice. Other scopes show the local choice alongside the saved effective result
and policy path. **Allow override** controls
`allow_lower_level_limits["smtp_credentials.inherit"]` and the equivalent IMAP
key. It is not a separate `allow_override` field in the credential object. A
parent's explicit-credential requirement may be changed by a child only while
that parent allows overrides. Locked fields and their override controls remain
read-only; a lower scope cannot unlock them. Campaign policy has no lower-level
override controls. Editing any policy still requires that scope's policy-write
permission and an unlocked workflow.
Policy saves retain their draft after a failed write and require an explicit
retry. If the policy was saved but a dependent screen refresh fails, the editor
reports that the policy was saved and advises reloading the display; it does not
report a failed save or repeat the accepted write.
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, IMAP, and legacy POP3 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
Current SMTP delivery and Sent-folder APPEND paths start a Mail-owned Core
recovery operation before the network effect. Outbox attempts use the durable
command and attempt number; Campaign jobs and single-message actions pass their
own stable attempt identifiers through the versioned capability. A matching
completed identifier is never sent or appended again merely to reconstruct
caller state. Provider acceptance, definitive rejection, and outcome-unknown
states are recorded independently of the consuming transaction.
Mailbox folder/message indexing and configured bounce/calendar-reply scans are
read-only provider operations. They acquire distributed per-profile/folder or
per-source fences, commit bounded projection state, and verify that state in an
independent session. A failed read is rolled back and can be repeated; it is not
treated as an unknown provider mutation.
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.
7. For an outbox `outcome_unknown`, reconcile the Mail command with provider
evidence. Confirmed acceptance closes the provider operation as succeeded;
confirmed absence records verified recovery and permits only a new,
deliberate attempt identifier.
### Bounded IMAP append batches / Begrenzte IMAP-Ablagestapel
Campaign's bulk Sent-folder operation can use the optional
`mail.campaign_delivery.campaign_imap_batch(tenant_id=..., campaign_id=...)`
context. Opening this context has no provider effect. Mail opens a connection
only after an individual message passes its current authorization, selected
IMAP credential policy, both frozen transport revisions and durable recovery
checks. Subsequent messages reuse that authenticated connection and its detected
Sent folder, including the provider's original Unicode mailbox wire encoding.
Authorization and credential resolution are performed for every message, not
cached. Changing the authorized profile, selected references, folder or resolved
credentials releases the previous connection before the next APPEND.
Each message still receives one sequential APPEND and its own recovery evidence.
There is no parallel APPEND, MULTIAPPEND, automatic SMTP resend or replay after
APPEND starts. A lost APPEND reply remains outcome-unknown and requires explicit
mailbox reconciliation. Failures while connecting, before any APPEND, may use a
bounded reconnect; rejected authentication is not retried. A failure to finalize
accepted recovery evidence closes the batch. Cleanup/logout failure does not
turn an accepted APPEND into a failed one. Session state is scoped to the current
batch, never shared across tenants or campaigns, and is released on exit.
Deployment controls apply to batch connection reuse, not campaign authorization:
| Environment variable | Default | Range / effect |
| --- | --- | --- |
| `GOVOPLAN_IMAP_BATCH_REUSE` | `true` | `false`, `0`, `no` or `off` disables reuse. |
| `GOVOPLAN_IMAP_BATCH_MAX_MESSAGES` | `100` | 110,000 successful APPENDs per connection. |
| `GOVOPLAN_IMAP_BATCH_MAX_AGE_SECONDS` | `300` | 13,600 seconds; rotate before the next message, not during an APPEND. |
| `GOVOPLAN_IMAP_BATCH_IDLE_HEALTH_CHECK_SECONDS` | `30` | 03,600 seconds idle before a NOOP; 0 checks every reuse. |
| `GOVOPLAN_IMAP_BATCH_RECONNECT_ATTEMPTS` | `1` | 05 extra connection attempts, only before APPEND. |
Invalid numeric values use defaults; out-of-range numbers are clamped. An active
session keeps the policy with which it was created. Existing single-message
callers retain one connection per call and no automatic connection retries.
Older Mail capabilities without this optional context keep the single-message
behavior. Safe outcome fields include connection sequence, session reuse and
reconnect count; they contain no hosts, credentials, provider responses or MIME
content. Fewer logins and folder discoveries improve connection overhead, not
the provider's intrinsic per-message APPEND or durable-evidence latency.
Deutsch: Die Sammelablage im Gesendet-Ordner kann eine begrenzte authentifizierte
IMAP-Verbindung wiederverwenden. Der Stapelkontext allein verbindet sich nicht.
Vor jeder Nachricht prüft Mail erneut Berechtigung, IMAP-Zugangsdatenrichtlinie,
beide eingefrorenen Transportrevisionen und Wiederherstellungsnachweise; die
Zugangsdaten werden weiterhin je Nachricht aufgelöst. Profil, ausgewählte
Referenzen, Ordner oder aufgelöste Zugangsdaten dürfen nicht stillschweigend von
einer älteren Verbindung übernommen werden. Ordnererkennung und ursprüngliche
Provider-Kodierung bleiben ausschließlich an dieselbe Verbindung gebunden.
Jede Nachricht erhält weiterhin einen einzelnen, sequenziellen APPEND und einen
eigenen Nachweis. Es gibt kein paralleles APPEND, kein MULTIAPPEND und keine
automatische Wiederholung nach Beginn von APPEND. Ein unbekanntes Ergebnis muss
am Postfach abgeglichen werden; eine fehlende Gesendet-Kopie darf keinen erneuten
SMTP-Versand auslösen. Nur Verbindungsaufbau vor APPEND darf begrenzt wiederholt
werden, nicht eine abgelehnte Anmeldung. Ein Fehler beim Abschluss des
Wiederherstellungsnachweises schließt den Stapel; ein reiner Abmeldefehler macht
eine bestätigte Ablage nicht rückgängig. Mandanten und Kampagnen teilen keine
Stapelverbindung.
Die obigen Betriebsvariablen bedeuten standardmäßig: Wiederverwendung aktiv,
höchstens 100 Nachrichten bzw. 300 Sekunden pro Verbindung, NOOP nach 30 Sekunden
Leerlauf und höchstens einen zusätzlichen Verbindungsversuch vor APPEND. Der
Wechsel erfolgt vor der nächsten Nachricht, niemals mitten im APPEND. `0` beim
Leerlaufintervall prüft jede Wiederverwendung; deaktivierte Wiederverwendung
verwendet weiterhin einzelne APPENDs. Ungültige Zahlen verwenden den Standard,
Zahlen außerhalb des Wertebereichs werden begrenzt. Eine aktive Verbindung
behält ihre beim Aufbau gelesene Richtlinie. Einzelaufrufe und ältere optionale
Mail-Verträge bleiben kompatibel. Verbindungszähler enthalten keine Zugangsdaten
oder Providerdetails. Die Optimierung spart Verbindungsaufbau und Ordnersuche;
Provider-Ablage und dauerhafte Einzelnachweise benötigen weiterhin ihre Zeit.
### Delivery-status and calendar-reply sources
An authorized Mail bounce source scans a bounded IMAP UID range without
changing mailbox flags. It correlates DSN reports with durable Mail commands
and, when the optional Calendar invitation capability is active, forwards
`text/calendar` or `.ics` `METHOD:REPLY` parts to Calendar. Calendar remains
owner of attendee state; Mail records only a raw digest, mailbox coordinates,
Message-ID, and audit linkage. Ordinary or malformed calendar messages do not
block DSN progress. A repeated UID/message digest produces no second Calendar
state transition or outbound synchronization effect.
### Backup, restore, and retirement
Backups contain encrypted credentials and encrypted raw POP3 import records and
therefore need the same protection as the live database and key material.
Restoring a Mail database without the matching encryption key makes those
records 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 and JMAP/POP3 credentials 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. Mail delivery commands emit request,
terminal-outcome, reconciliation, and deliberate-resend events without
addresses or raw provider responses. MIME, envelope addresses, detailed
refusals, and reconciliation notes are encrypted and minimized after their
retention deadline while hashes, counts, attempts, and decisions remain.
## 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
- 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 provider-side mutation, submission, push, thread, calendar, contact,
attachment-binary, and automatic background-sync support; read-only mailbox
synchronization/search is implemented on the stable IMAP mailbox contract.
- Expanding POP3 beyond the implemented explicit legacy download/import
workflow; it has no folder, flag, search, or synchronization contract.
- A full mail client with compose/reply/move/delete/read-state mutation.
- Quick Access may launch the operating environment's configured composer via
`mailto:`. That explicit handoff is not a GovOPlaN Mail delivery: it selects
no Mail profile or credential, bypasses no Mail policy, and reports no
GovOPlaN delivery result. Recent-message and Drafts links remain read-only
deep links into the authorized Mail profile and preserve their Quick Access
return context.
- Recovery-ledger adoption for future provider-side move, delete, and flag
mutations; no such production path exists in the current read-only mailbox.
- Proof that process-local throttling coordinates multiple workers when Redis
is unavailable; it deliberately does not.