docs: document the Campaign-to-Mail delivery boundary

This commit is contained in:
2026-07-21 17:14:55 +02:00
parent 09c63de813
commit 7d8579194d
5 changed files with 115 additions and 11 deletions

View File

@@ -31,7 +31,7 @@ The module has one required runtime dependency:
Files and mail are optional module integrations declared in the campaign manifest: 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-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 Public campaign, version, job, and report responses expose business data and
delivery evidence, but never process-local paths, storage-backend keys, or 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 ## 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. - [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 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. - [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. - [Example campaigns and release checklist](docs/EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md) defines the maintained example scenarios and release gates.

View File

@@ -15,6 +15,8 @@ been validated, built, reviewed, and locked.
## Before First Live Use ## Before First Live Use
- Use dedicated non-production SMTP/IMAP credentials. - 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 - Start the repository test bed in `dev/mail-testbed/` when a local
production-like SMTP/IMAP server is sufficient. production-like SMTP/IMAP server is sufficient.
- Use a dedicated mailbox/folder for append-to-Sent tests. - 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. - `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 Re-run worker handling or reconcile if SMTP may already have accepted the
message. 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 ## Reconciliation

View File

@@ -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 global attachments and recipient-specific attachment rules
- campaign with password-protected ZIP attachments - campaign with password-protected ZIP attachments
- campaign using a reusable mail profile from the mail module - campaign using a reusable mail profile from the mail module
- campaign using inline SMTP/IMAP settings where policy allows campaign-local - legacy inline SMTP/IMAP campaign rejected with an actionable profile-migration
settings error and no returned transport data
- campaign with validation warnings that may be sent only after explicit review - campaign with validation warnings that may be sent only after explicit review
- campaign with blocked recipients or attachment errors that must not be sent - 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 - 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. - Examples must not contain production recipient data or production credentials.
- Attachment examples should use deterministic small files and checksums. - Attachment examples should use deterministic small files and checksums.
- Secret values must be represented through saved-credential placeholders or - Mail secrets belong only to encrypted Mail profiles and must never appear in
secret references. a campaign fixture, placeholder, or campaign-local secret reference.
- Examples that require optional modules must declare the required modules and - Examples that require optional modules must declare the required modules and
capabilities in their README or fixture metadata. capabilities in their README or fixture metadata.
- Examples must stay valid when files or mail modules are physically absent, - 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. - Run the GreenMail SMTP/IMAP smoke for a non-production real delivery path.
- Confirm reusable mail profile selection is revalidated after campaign owner - Confirm reusable mail profile selection is revalidated after campaign owner
transfer. transfer.
- Confirm inline SMTP/IMAP settings are hidden or blocked when policy disables - Confirm every inline SMTP/IMAP field is rejected on import/write, omitted
campaign-local mail settings. 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 - Confirm delivery reports include SMTP outcome, IMAP append outcome, latest
error, generated EML reference, and attachment evidence. error, generated EML reference, and attachment evidence.
- Confirm retries cannot resend messages already accepted by SMTP unless an - 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. 2. Build exact messages.
3. Review warnings, generated recipients, body content, and attachment evidence. 3. Review warnings, generated recipients, body content, and attachment evidence.
4. Run mock delivery if available for the release channel. 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. 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. 7. Reconcile failed, unknown, or pending jobs from the report/audit surfaces.

View File

@@ -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.

View File

@@ -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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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 - Do not commit real recipients, mail credentials, or production attachment
names. names.
- Keep attachments deterministic and small. - Keep attachments deterministic and small.
- Store secrets as placeholders, saved-credential references, or local `.env` - Store transport secrets only in encrypted Mail profiles or local `.env`
values consumed by the test bed. values consumed directly by the test bed, never in campaign JSON.
- Declare optional module requirements in fixture metadata. - Declare optional module requirements in fixture metadata.
- Fixtures must not import files or mail modules directly; optional behavior is - Fixtures must not import files or mail modules directly; optional behavior is
discovered through core module metadata and capabilities. discovered through core module metadata and capabilities.