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

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

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

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.