# Campaign and Mail Profile Boundary ## Product view A campaign chooses an authorized Mail profile. It does not define a mail server. The Campaign module owns recipients, content, attachment rules, delivery intent, review state, and delivery evidence. The Mail module owns the SMTP/IMAP endpoints, encrypted credentials, connection tests, profile policy, and runtime transport adapters. The persisted campaign contract is therefore deliberately narrow: ```json { "server": { "mail_profile_id": "stable-mail-profile-id" } } ``` No `server.smtp`, `server.imap`, `server.credentials`, credential-inheritance override, or password is valid campaign JSON. ## User journey 1. A Mail administrator creates and tests a reusable profile in Mail. 2. A campaign author opens **Mail settings** and selects one profile available for the campaign's tenant, owner, and policy context. 3. Campaign stores only the stable profile identifier. 4. Validation asks Mail to authorize the active profile and returns only availability flags plus opaque Mail-owned transport revisions. Reading that summary does not decrypt credentials. 5. Build stores the profile identifier, delivery policy, job manifest, and non-secret transport revisions as execution evidence. It stores no resolved host, username, password, or other transport material. 6. A delivery worker invokes one Mail-owned effect operation. Mail re-authorizes the profile, resolves credentials, compares the expected transport revision, checks policy, and sends or appends without returning transport material to Campaign. If the selected profile or its non-secret transport settings changed after build, delivery stops until the campaign is revalidated and rebuilt. A password rotation that leaves the transport identity unchanged does not invalidate the build. 7. Mail returns only Campaign-owned envelope addresses, counts, sanitized refusal classifications/status codes, or the selected Sent folder. Raw server banners, provider bytes, host details, and credentials never enter Campaign attempts or public evidence. Non-dry Campaign report email currently fails closed. It must not bypass the durable job/effect model through a direct SMTP call. Re-enabling it requires the Mail-owned idempotent outbox, attempt, unknown-outcome, and reconciliation path tracked in [`govoplan-mail#17`](https://git.add-ideas.de/GovOPlaN/govoplan-mail/issues/17). Report generation and dry-run validation remain separate from an external effect; recipient-level exports require recipient-export authorization. Campaign authors need `mail:profile:use` in addition to the relevant Campaign permission. Profile visibility remains governed by Mail policy and campaign owner context. ## Existing database rows and migration The current database can contain campaign versions with inline SMTP/IMAP settings or credentials. There is no separate historical Campaign JSON corpus to import or remediate. GovOPlaN treats those inline fields as inert legacy material and does not delete or rewrite the stored audit rows automatically: - API responses omit all legacy transport fields and secrets and expose a `mail_profile_migration_required` marker. - validation, build, queue, retry, and delivery fail closed with an actionable profile-migration error; - unrelated edits preserve the exact stored legacy server object when the submitted public Mail references are unchanged; they cannot silently scrub, edit, or re-submit legacy fields or credentials; - an editable version is migrated only through an explicit Mail-settings save with an authorized profile; and - a locked version remains unchanged. Creating its editable successor records the migration while retaining the locked source as audit evidence. Legacy execution snapshots are likewise retained but cannot be used for delivery. Revalidate and rebuild an editable profile-only version. Normal database backup, restore, encryption, and access controls apply to the current database as a whole; the product does not define a separate historical-JSON or inline-secret recovery workflow. A restored legacy row remains inert and fail-closed under the same rules. ### Migrate from the Campaign UI Open **Mail settings** from the migration notice for the selected version. Select an authorized Mail profile and choose **Migrate selected Mail profile**. The migration action is available even when that profile was already selected and the draft has no other unsaved changes. If the version is locked, first use its supported unlock or editable-successor action; protected source evidence is not rewritten. Reopen the settings and confirm that the migration notice has gone, then validate, build, and review before separately authorizing delivery. Migration itself never sends mail. Mail migration and ZIP policy repairs can be saved in either order. An exact, unchanged ZIP configuration does not require a new acknowledgement merely to save a Mail migration, even if the existing ZIP policy or actor's permission is no longer valid. No actor, timestamp, or consent is invented. Any ZIP change still requires the complete current archive policy and, for ZipCrypto, the dedicated permission and reasoned acknowledgement. Conversely, saving an archive correction with unchanged public Mail references retains the exact legacy transport server-side, without using or reauthorizing the old profile. The migration notice remains until the explicit authorized migration succeeds. Changing any Mail profile, server, or credential reference is not an unrelated repair. Inline transport is rejected even if a caller echoes stored values. The same edit-time separation applies after migration: retaining an unchanged Mail selection does not invoke use-policy while saving an unrelated correction, even if a later policy requires an explicit credential. Selecting a new resource or explicitly migrating still checks Mail permission and current policy; actual validation and delivery always recheck them, regardless of save history. Both repair orders invalidate build/execution evidence; validation, build, review and delivery remain blocked until all outstanding conditions are valid. Successful saves and subsequent refreshes are separate outcomes. A committed save is not undone by a failed workspace refresh. The workspace retains its last usable same-campaign/version data and displays the refresh error; retry Reload to fetch the current server state. Obsolete responses from an earlier campaign, version, signed-in identity, or reload cannot replace newer data. The normal profile selector requests only campaign-authorized profiles. The administrative profile catalogue is requested separately on **Mail policy**; failure or lack of authority there does not empty the usable profile selector. Profile-list errors remain visible next to the affected settings. While migration is required, **Review and send** remains read-only and links to the exact version's Mail settings. It does not repeatedly attempt incompatible attachment-preview requests. These UI affordances do not relax backend validation, build, queue, retry, or delivery enforcement. ### Editor metadata and trusted evidence Read responses may contain server-owned `review_send` and `approval_gate` evidence. Ordinary version mutations send only client-owned `created_from`, `field_overrides`, and `opt_ins` editor metadata. The client omits review and approval evidence; the server rejects attempts to write it through an editor mutation and preserves its existing trusted value during metadata updates. Supported unlock, successor-version, and build invalidation rules still remove stale evidence when required. Opening or leaving the Template editor must not require manually deleting server review metadata. ## Operator checks Before live delivery, confirm that: - the profile is active and still authorized for the campaign owner; - SMTP and optional IMAP profile tests pass; - validation and build occurred after the latest transport-identity change; - append-to-Sent is enabled only when the selected profile has IMAP; and - reports show the profile-bound snapshot revisions and delivery outcomes, never credentials or resolved transport configuration.