From 7d8579194dd014777c2509d139623c32294c9197 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 21 Jul 2026 17:14:55 +0200 Subject: [PATCH] docs: document the Campaign-to-Mail delivery boundary --- README.md | 5 +- docs/CAMPAIGN_DELIVERY_RUNBOOK.md | 9 ++ ...EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md | 16 ++-- docs/MAIL_PROFILE_BOUNDARY.md | 90 +++++++++++++++++++ examples/README.md | 6 +- 5 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 docs/MAIL_PROFILE_BOUNDARY.md diff --git a/README.md b/README.md index df0e8bb..95bf3e1 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The module has one required runtime dependency: Files and mail are optional module integrations declared in the campaign manifest: - `govoplan-files` enables managed attachment selection, frozen file-version evidence, and managed-file usage tracking. Server/API campaigns require this integration for attachments and never resolve caller-supplied local filesystem paths. Legacy file-oriented loading remains available only to explicitly trusted operator/library workflows. -- `govoplan-mail` enables reusable mail profiles, delivery policy checks, SMTP sending, and IMAP append behavior. Without it, campaigns can still be authored, validated, built, and reported, but real delivery/profile features are unavailable. +- `govoplan-mail` owns reusable profiles, encrypted SMTP/IMAP credentials, delivery policy checks, connection tests, and transport execution. Campaign JSON stores only `server.mail_profile_id`; inline transport settings and credentials are rejected. Without Mail, campaigns can still be authored, but profile validation and real delivery are unavailable. Public campaign, version, job, and report responses expose business data and delivery evidence, but never process-local paths, storage-backend keys, or @@ -91,7 +91,10 @@ Platform RBAC and governance rules are documented in `govoplan-core/docs/`. ## Operations +- [Campaign handbook](docs/CAMPAIGN_HANDBOOK.md) provides the adaptive user, process, governance, technical, and operations perspectives. - [Campaign delivery runbook](docs/CAMPAIGN_DELIVERY_RUNBOOK.md) covers queueing, local vs Celery operation, retries, reconciliation, reports, and the live SMTP/IMAP test checklist. +- Report-email preview uses the selected version's stored v5 Mail-profile evidence. Live report email fails closed until [govoplan-mail#17](https://git.add-ideas.de/add-ideas/govoplan-mail/issues/17) provides a durable, idempotent Mail-owned outbox and transport-attempt ledger; per-job CSV is off by default and requires `campaigns:recipient:export` when requested. +- [Campaign/Mail profile boundary](docs/MAIL_PROFILE_BOUNDARY.md) defines profile-only delivery, runtime resolution, execution evidence, and the fail-closed legacy migration path. - [Recipient import guide](docs/RECIPIENT_IMPORT_GUIDE.md) covers user/admin workflows, mapping profiles, validation, and import evidence. - [Recipient and address boundary](docs/RECIPIENT_ADDRESS_BOUNDARY.md) defines the split between campaign-local recipients and future reusable address management. - [Example campaigns and release checklist](docs/EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md) defines the maintained example scenarios and release gates. diff --git a/docs/CAMPAIGN_DELIVERY_RUNBOOK.md b/docs/CAMPAIGN_DELIVERY_RUNBOOK.md index 02b134e..ab44805 100644 --- a/docs/CAMPAIGN_DELIVERY_RUNBOOK.md +++ b/docs/CAMPAIGN_DELIVERY_RUNBOOK.md @@ -15,6 +15,8 @@ been validated, built, reviewed, and locked. ## Before First Live Use - Use dedicated non-production SMTP/IMAP credentials. +- Store and test those credentials in a Mail-module profile. Campaign must + contain only the selected `server.mail_profile_id` reference. - Start the repository test bed in `dev/mail-testbed/` when a local production-like SMTP/IMAP server is sufficient. - Use a dedicated mailbox/folder for append-to-Sent tests. @@ -64,6 +66,13 @@ Before the first live send for a sender domain or mail-server profile: - `claimed` or `sending` that does not progress: treat as a worker interruption. Re-run worker handling or reconcile if SMTP may already have accepted the message. +- IMAP `appending`: A worker owns the durable append claim. Do not start a + second append; if the worker cannot finish, reconcile only after checking the + mailbox. +- IMAP `outcome_unknown`: Never append automatically. An operator with + `campaigns:campaign:reconcile` must record an evidence note and resolve it as + `imap_appended` or `imap_not_appended`. Only the latter becomes explicitly + retryable. ## Reconciliation diff --git a/docs/EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md b/docs/EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md index 6b6c489..48267f7 100644 --- a/docs/EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md +++ b/docs/EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md @@ -16,8 +16,8 @@ schema and are safe to run in non-production environments. - campaign with global attachments and recipient-specific attachment rules - campaign with password-protected ZIP attachments - campaign using a reusable mail profile from the mail module -- campaign using inline SMTP/IMAP settings where policy allows campaign-local - settings +- legacy inline SMTP/IMAP campaign rejected with an actionable profile-migration + error and no returned transport data - campaign with validation warnings that may be sent only after explicit review - campaign with blocked recipients or attachment errors that must not be sent - mock delivery campaign that captures SMTP and IMAP append messages in the mail @@ -28,8 +28,8 @@ schema and are safe to run in non-production environments. - Examples must not contain production recipient data or production credentials. - Attachment examples should use deterministic small files and checksums. -- Secret values must be represented through saved-credential placeholders or - secret references. +- Mail secrets belong only to encrypted Mail profiles and must never appear in + a campaign fixture, placeholder, or campaign-local secret reference. - Examples that require optional modules must declare the required modules and capabilities in their README or fixture metadata. - Examples must stay valid when files or mail modules are physically absent, @@ -49,8 +49,10 @@ Before tagging a campaign release: - Run the GreenMail SMTP/IMAP smoke for a non-production real delivery path. - Confirm reusable mail profile selection is revalidated after campaign owner transfer. -- Confirm inline SMTP/IMAP settings are hidden or blocked when policy disables - campaign-local mail settings. +- Confirm every inline SMTP/IMAP field is rejected on import/write, omitted + from responses, and blocked from legacy execution until explicitly migrated. +- Confirm execution snapshots store only the Mail profile reference and + opaque Mail-owned transport revisions, not resolved transport material. - Confirm delivery reports include SMTP outcome, IMAP append outcome, latest error, generated EML reference, and attachment evidence. - Confirm retries cannot resend messages already accepted by SMTP unless an @@ -71,6 +73,6 @@ Use the Review & Send preflight panel and the delivery runbook together: 2. Build exact messages. 3. Review warnings, generated recipients, body content, and attachment evidence. 4. Run mock delivery if available for the release channel. -5. Test SMTP and IMAP settings against non-production infrastructure. +5. Test the selected Mail profile against non-production infrastructure. 6. Send only after queue, rate limit, and append-to-Sent behavior are understood. 7. Reconcile failed, unknown, or pending jobs from the report/audit surfaces. diff --git a/docs/MAIL_PROFILE_BOUNDARY.md b/docs/MAIL_PROFILE_BOUNDARY.md new file mode 100644 index 0000000..6dc35b0 --- /dev/null +++ b/docs/MAIL_PROFILE_BOUNDARY.md @@ -0,0 +1,90 @@ +# 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/add-ideas/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. + +## Legacy versions and migration + +Older versions can contain inline SMTP/IMAP settings or credentials. GovOPlaN +does not delete or rewrite those stored audit records 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 cannot silently scrub the legacy fields; +- 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 for audit but cannot be used +for delivery. Revalidate and rebuild an editable profile-only version. Removing +the remaining live and backup secret copies requires the separately approved +remediation in +[`govoplan-campaign#77`](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/77). + +## 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. diff --git a/examples/README.md b/examples/README.md index 5487722..42be4e3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,7 +15,7 @@ campaign schema and do not require production data. | `recipient-attachment-rules` | core, access, campaigns; optional files | Match recipient-specific attachment rules and verify per-recipient evidence. | | `zip-protected` | core, access, campaigns | Build password-protected AES ZIP output and verify password-source metadata. | | `mail-profile-send` | core, access, campaigns, mail | Select a reusable mail profile and send through the GreenMail test bed. | -| `inline-mail-settings` | core, access, campaigns, mail | Use campaign-local SMTP/IMAP settings only when policy allows it. | +| `legacy-inline-mail-rejected` | core, access, campaigns, mail | Confirm legacy campaign-local SMTP/IMAP data fails closed and requires explicit profile migration. | | `warnings-review` | core, access, campaigns | Require explicit review before queueing jobs with warnings. | | `blocked-send` | core, access, campaigns | Confirm blocked recipients or missing attachments cannot be queued. | | `mock-delivery` | core, access, campaigns, mail with dev capability | Capture messages in the development mailbox. | @@ -26,8 +26,8 @@ campaign schema and do not require production data. - Do not commit real recipients, mail credentials, or production attachment names. - Keep attachments deterministic and small. -- Store secrets as placeholders, saved-credential references, or local `.env` - values consumed by the test bed. +- Store transport secrets only in encrypted Mail profiles or local `.env` + values consumed directly by the test bed, never in campaign JSON. - Declare optional module requirements in fixture metadata. - Fixtures must not import files or mail modules directly; optional behavior is discovered through core module metadata and capabilities.