94 lines
4.4 KiB
Markdown
94 lines
4.4 KiB
Markdown
# 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 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 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.
|
|
|
|
## 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.
|