Files
govoplan-campaign/docs/CAMPAIGN_HANDBOOK.md
T
zemion c51fc180fb
Module Package Release / publish-packages (push) Successful in 12s
Release govoplan-campaign v0.1.28: stabilize saving, review and delivery recovery
2026-09-08 01:32:26 +02:00

947 lines
52 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Campaign Handbook
## Purpose and status
This is the canonical, multi-perspective handbook for the Campaign module. It
describes the current implementation, the operational contract it relies on,
and the remaining work required before Campaign can be presented as GovOPlaN's
maintained reference composition.
Use the section that matches the task at hand:
| Perspective | Start here |
| --- | --- |
| Campaign author | [Prepare a campaign](#prepare-a-campaign) |
| Reviewer | [Review and complete review](#review-and-complete-review) |
| Sender or delivery operator | [Deliver and resolve outcomes](#deliver-and-resolve-outcomes) |
| Campaign or tenant administrator | [Administration and policy](#administration-and-policy) |
| Platform operator | [Operations and recovery](#operations-and-recovery) |
| Integrator or developer | [Composition and integration contracts](#composition-and-integration-contracts) |
| Security, privacy, or audit reviewer | [Assurance model](#assurance-model) |
| Release reviewer | [Reference-composition acceptance](#reference-composition-acceptance) |
The shorter task documents remain useful companions:
- [Campaign delivery runbook](CAMPAIGN_DELIVERY_RUNBOOK.md)
- [Mail profile boundary](MAIL_PROFILE_BOUNDARY.md)
- [Recipient import guide](RECIPIENT_IMPORT_GUIDE.md)
- [Recipient and Addresses boundary](RECIPIENT_ADDRESS_BOUNDARY.md)
- [Examples and release checklist](EXAMPLE_CAMPAIGNS_AND_RELEASE_CHECKLIST.md)
## What Campaign is for
Campaign turns governed source data into individually built messages or
printable output and then controls their review, delivery, and evidence. It is
intentionally a composition module: it demonstrates how one user journey can
use optional Mail, Files, Addresses, Distribution Lists, Templates, Postbox,
and Notifications capabilities alongside Core access/audit infrastructure
without copying ownership from those modules. Policy may consume Campaign
context through a narrow capability; Campaign does not import Policy.
Campaign owns:
- the communication purpose, content, campaign-local fields, and templates;
- campaign-local recipient snapshots, exclusions, and personalization;
- message and attachment rules for a version;
- validation, review, build, queue, and delivery-control state;
- the durable jobs and attempts needed to explain delivery outcomes; and
- campaign-specific reports, shares, frozen execution evidence, and governed
human collaboration entries.
Campaign does not own:
- SMTP/IMAP profiles, credentials, protocol adapters, or mailbox policy;
- long-lived address-book master data, consent lifecycle, or deduplication;
- managed file bytes, connector credentials, or external-file provenance;
- general-purpose workflow definitions; or
- global identity, organization, permission, or retention policy.
Those boundaries matter in both persistence and UI. A campaign references an
authorized Mail profile and managed file versions; it must never become a
second secret store, file store, or address directory.
## Process view
The supported process is a controlled progression, not a single "send" call:
```text
create/edit
-> validate and resolve policy/integrations
-> review warnings and blockers
-> build exact recipient messages and/or printable artifacts
-> complete review and queue
-> selected Mail, Postbox, or print effect per job
-> optional IMAP append per accepted job
-> report, retry, reconcile, or correct
-> archive when no active/uncertain delivery remains
```
Campaign status summarizes the whole campaign. Version workflow state describes
the selected immutable/editable version. Each recipient job separately records
build, validation, queue, SMTP, and IMAP state. Operators must use the job-level
states when deciding whether another external effect is safe.
Important distinctions:
- **Validation lock** is the reversible lock created by a successful
validation/build path. Editing requires unlocking and invalidates derived
evidence as appropriate.
- **User lock** is an explicit audit-safe lock. A permanently locked or
delivery-final version is not edited in place; create an editable successor.
- **SMTP accepted** means the provider accepted the message. It does not prove
inbox delivery, reading, or business acknowledgement.
- **Outcome unknown** means an attempt may have had an external effect. It must
be investigated and reconciled before retry.
- **IMAP append** is a separate effect after SMTP acceptance. An append failure
is not evidence that sending failed.
- **Archive** preserves evidence. Draft-only campaigns without built, locked,
or delivery evidence may be deleted where policy allows; evidence-bearing
campaigns are archived instead.
- **Copy campaign** creates a new campaign and one fresh editable version from
the selected source version. It copies configuration, but never delivery
jobs, outcomes, shares, locks, or audit evidence.
- **Archive historical version** hides only a non-current version from the
default history. It does not change the version's workflow state or remove
configuration, reports, delivery results, or audit evidence.
Campaign lifecycle confirmations are bound to the state shown in the UI. If a
job, version, share, or campaign state changes before confirmation, the server
rejects the stale action and requires a reload. The lifecycle-policy response
states the applicable built-in rule and the reason for every unavailable
action.
## User tasks
### Discuss campaign work
Open **Collaboration** inside a Campaign to keep human coordination beside the
work without changing its version history. Discussion access is independent
from Campaign editing: parent Campaign read access remains mandatory, while
`campaigns:discussion:read`, `campaigns:discussion:post`, and
`campaigns:discussion:moderate` separately control reading, posting, and
moderation. A read share is sufficient as the parent grant and a comment never
turns that share into write access.
Comments are append-only and bounded to 8,000 characters. They can carry one
validated reference to an immutable Campaign version, saved recipient import,
attachment rule, delivery job, or report. References to version-bound evidence
include the exact version ID and never edit that version. Authors can withdraw
their own comments; moderators can redact comments and use moderator-only
visibility. Both operations remove displayed text but preserve a tombstone,
content hash, actor snapshot, timestamp, reference context, and bounded Audit
event. There is deliberately no comment-edit API.
Mentions are limited to 20 active users who already have Campaign ownership or
share access. When Notifications is installed and healthy, Campaign emits a
content-free in-app mention notification. Collaboration remains usable without
Notifications. The thread displays only human discussion; approvals, workflow
state, delivery events, and durable system evidence remain on their owning
surfaces and in Tenant audit.
### Assign accountable campaign work
Open **Work** to assign one bounded purpose to an account, group, or
organization function that already has Campaign access. Assignment records
responsibility only: it never creates a share, transfers ownership, or grants a
permission. Assignees may accept, complete, or reject their work; rejection is
distinct from administrative cancellation. Managers may reassign or cancel
open work, and every transition retains the expected revision, actor snapshot,
typed target, and append-only event history.
Workflow may create or reference a Campaign and open the same assignment through
the optional `campaigns.workOrchestration` capability. Those assignments pin the
Campaign version and store the Workflow instance, step, correlation, and
idempotency provenance. Campaign emits `campaign.work.changed` for assignment,
acceptance, start, reassignment, completion, rejection, and cancellation.
Workflow uses the assignment ID and event revision, rechecks current Campaign
access, and then resumes the matching durable external hand-off without browser
polling. A missing Tasks or Notifications capability only removes the optional
projection or notification. A missing Campaign provider, revoked Campaign
access, or stale event revision keeps the Workflow blocked and inspectable.
Campaign also contributes the opt-in **Accountable Campaign work hand-off**
Workflow template. It is deliberately not activated on installation. A
configurator must copy or activate it and supply either `campaign_id` or
`create_campaign`; unused optional input keys must be present with `null`
values. The template prepares the assignment idempotently, opens the exact
Campaign work URL, and waits for completion, rejection, cancellation, or the
configured timeout. Opening the link never completes the Workflow.
### Prepare a campaign
1. Create a campaign and confirm its owner or owning group.
2. Define global settings, fields, templates, and recipient data.
3. Import one-off recipient data or select a reusable Addresses source when the
optional capability is installed. Review source provenance and stale-source
warnings; Campaign freezes the selected rows rather than following later
directory changes silently.
4. Select managed attachments through Files. Server/API campaigns do not accept
arbitrary local paths. Preview rules and unmatched files before building.
5. Open **Mail settings** and select an available Mail profile. The campaign
stores only `server.mail_profile_id`; it never accepts SMTP/IMAP settings,
usernames, passwords, or credential references.
If the selected profile permits a campaign-scoped Mail credential, that
credential remains Mail-owned even though it is created from this surface.
For that credential and for password-valued campaign fields, the shared
password generator keeps its candidate separate from the form until **Use
password** is explicitly confirmed. Copying a candidate does not save or
submit it.
If legacy transport data is reported, choose **Migrate selected Mail
profile**, including when the existing profile selection is unchanged.
Follow a locked version's supported unlock or editable-successor action
before migration. Migration is explicit and audited, never sends mail, and
requires validation, build, and review again.
6. Save the editable version, validate the relevant sections, and resolve every
blocking issue. Warnings remain explicit review decisions.
7. Build the exact messages and inspect recipient, addressing, template,
attachment, and generated-message evidence.
If a selected optional module is absent, Campaign remains loadable and explains
which function is unavailable. It must not fail startup because Mail, Files, or
Addresses is not installed.
Opening or leaving Template without editing does not change the saved HTML or
mark the page dirty. Visual/source inspection and read-only changes likewise
do not require a save. Actual saves send only client-owned editor metadata. Review
and approval evidence remains server-owned and cannot be overwritten by an
ordinary editor save. Omitting that readable evidence from a save does not
delete it; normal version-lock and invalidation rules remain authoritative.
### Preserve recipient address order
In an individual or global address dialog, use the up/down actions to arrange
the addresses, then choose **Save** in the dialog. The campaign draft keeps that
order; saving no longer alphabetically sorts it. Duplicate email addresses keep
their first position, and pasted addresses append in their entered order.
The first individual To address is also the primary name/email shown in the
recipient row. Use the page's **Save** to persist the campaign draft. A rejected
page save keeps the reordered draft for an explicit retry. **Cancel** in the
dialog discards only its unconfirmed changes.
### Permit Legacy ZipCrypto as an explicit compatibility exception
AES remains the secure default. Campaign **Settings**, **Policies**, and
**Attachments** expose the effective archive policy, configuration links for
authorized administrators, and **Reload archive policy**.
1. A policy administrator opens **Administration → SYSTEM → Campaign archive
encryption**, enables **Legacy ZipCrypto**, and saves. The controls work
before the first system override exists; opening defaults alone does not
create an override or unsaved changes. Changing this global ceiling requires
both `system:settings:write` and `admin:policies:write`; tenant policy
authority alone cannot loosen it.
2. Check tenant and owner policy restrictions. Lower scopes may narrow, never
loosen, inherited methods and password-delivery channels.
3. The Campaign actor also needs `campaigns:archive:use_legacy_zipcrypto` and
edit access to the selected version. Policy administration does not replace
that dedicated permission.
4. Return to Campaign, reload archive policy, and select **Legacy ZipCrypto**
under **Attachments → ZIP attachments**. Acknowledge weak encryption, enter
an operational reason of at least 10 characters, and select an allowed
separate password-delivery channel.
5. Save, validate, build, and review. Policy/configuration saves never send
mail; delivery remains a separate action.
Legacy remains blocked while Policy is unavailable. Neither an encryption
error nor an incompatible client causes automatic fallback from AES to
ZipCrypto. The build retains policy and acknowledgement evidence but never the
password; see the manifest topic `campaigns.archive-encryption-governance`.
Mail migration and ZIP corrections can be saved in either order. A Mail-only
migration preserves unchanged ZIP settings without granting permission to use
them or adding acknowledgement evidence. An archive correction with unchanged
Mail references preserves legacy transport server-side until its separate,
explicit migration. Changes to ZIP settings still require the current policy
and any dedicated legacy permission; changes to Mail references still require
authorized migration. Validate, build and review again after both repairs.
A save and the following workspace refresh are separate operations. A failed
refresh does not undo a committed save or clear the last usable workspace.
Keep any newer unsaved edits, inspect the refresh error, and use Reload to fetch
the current state. Responses for an earlier campaign, version or signed-in
identity cannot overwrite the current workspace.
### Review and complete review
The reviewer should verify the immutable candidate that will be delivered, not
just the authoring form:
If a legacy Mail migration notice appears, follow **Open Mail settings** for
that exact version, complete migration, and validate and build again. Review
stays read-only until migration is resolved and does not repeatedly request
an attachment preview that the legacy transport boundary must reject.
1. Confirm purpose, owner, selected version, and recipient count.
2. Inspect blocking errors, warnings, exclusions, and recipients requiring
review.
3. Inspect representative and exceptional rendered messages, including From,
To/CC/BCC, Reply-To, subject, body, and attachment evidence.
4. Confirm the selected Mail profile is authorized for the campaign's current
tenant and owner context.
5. Confirm attachment behavior when a rule matches no files, ZIP/password
behavior, and any recipient-specific files.
6. Record review completion and the inspected message keys through the review
surface. Validation, build, review, and exception evidence records the actor,
timestamp, and immutable build token/message digest where applicable. If
content, recipients, attachment inputs, owner context, or non-secret
transport identity changes, revalidate and rebuild.
The Review & Send surface separates three kinds of attention. Critical
blockers must be corrected before delivery, individual review items require a
recorded message decision, and non-critical group items may be acknowledged
together after individual review is complete. Each warning or blocker names
the required action, the responsible role, and the workspace to open. The
review summary keeps reviewed and remaining counts visible; a completed review
acknowledges the group items and remains bound to the current build token.
Save each individual acceptance to persist its reason and reviewed state before
completing the entire review. Wait for acknowledgement; reloading then resumes
that build's saved progress. If saving fails or conflicts, the pending note
remains available for an explicit retry rather than becoming a false success.
This small save only loads the selected persisted jobs: it does not rebuild
messages, materialize attachments, or reload the whole workspace. Another
reviewer's existing decisions and attribution remain intact. Partial progress
does not enable delivery; final completion still checks the complete build.
Use **Accept similar review conditions** to record the same decision for a
counted selection of currently loaded matching messages. The server defines
eligible categories from the complete combination of overridable conditions;
the UI does not interpret a warning badge as permission to override. Select
one category, inspect the listed recipients, deselect any exceptions and enter
a common reason (required for attachment exceptions). A submission contains
at most 200 explicit message IDs. When more remain, save this selection and
reopen the dialog; the counts never imply acceptance of unloaded messages or
other categories. The reason is recorded separately against each selected
message's frozen evidence. A failed save retains the selection and reason for
an explicit retry, while a changed build prevents stale acceptance. This
action neither sends messages nor completes the final review gate. Hard
blockers cannot be accepted this way. Deliberate policy exclusions and
attachment rules that explicitly permit zero matches remain informational
and do not require review decisions.
An optional rule with explicit `missing_behavior: continue` may yield no files
without creating review work; that outcome remains informational evidence.
Required attachment and hard-block policies cannot be weakened by this setting.
The separate policy for sending a wholly attachment-free message still applies.
Rebuild existing messages after changing attachment policy; historical build
evidence is not rewritten.
The incremental review API uses `merge_progress: true`, the acknowledged
`base_revision`, and `build_token` set to the public `review_build_token`.
It merges exact reviewed keys/decision job IDs for the current build, with an
optional `decision_category_key` to bind a grouped acceptance. The safe review
reference is available without diagnostic access; raw build tokens remain
diagnostic data. Stale build/revision or simultaneous writes return HTTP 409
without overwriting progress. Normal review authorization and audit apply.
Accepted or expected attachment conditions remain satisfied in **Confirm and
send** for the same build. Raw missing/ambiguous source counts remain visible
for context; they are not a second approval gate. Reviewed-stage mock delivery
uses `use_reviewed_build: true`: it verifies the existing execution seal,
completed review, frozen job issues and EML integrity, and current Mail transport
before capturing anything in the mock mailbox. It uses those stored messages,
not freshly rendered replacements, and never mutates Campaign delivery state.
Stale review, changed inputs, changed bytes or changed transport stop the test
before captures or requested mailbox clearing. The authoring/mock-preview API
keeps its existing transient-build default; `include_needs_review` does not
bypass frozen review checks.
Validation details and repeated-file lists use the shared DataGrid pagination
controls so every item is reachable. Related missing-rule causes and their
attachment-free policy outcomes appear together, with the technical evidence
still expandable. Built messages have four operational states: **Ready**,
**Needs review**, **Blocked**, and **Excluded**, plus an explanatory column.
Accepted explicit decisions are Ready; warnings awaiting acknowledgment remain
Needs review. This presentation does not remove or rewrite frozen issues or
audit evidence.
This evidence is the Campaign input to separation-of-duties policy. Generic
approve/reject chains, delegation, substitutions, escalation, and signatures
belong to the optional Approvals capability. Campaign must not claim an
approval merely because validation, building, or message review completed.
When a campaign has an Approval request reference, mock and real delivery
resolve `approvals.requests` and require an approved request for the exact
`campaign_version` subject and current version id. A missing Approvals module,
unknown request, pending/rejected chain, or approval for an older version blocks
delivery with an explicit requirement. Campaign stores the approval reference,
not Approval tables; changing the campaign version requires a new exact-subject
approval.
Normal readers and reviewers see business state and safe evidence. Process-local
paths, storage keys, worker claim tokens, and raw provider diagnostics require
the dedicated diagnostic permission and must not leak through ordinary campaign,
version, job, or report responses.
Campaign now provides a separate aggregate **Reports** surface for readers with
`campaigns:report:read` and access to the campaign. It loads only the safe
aggregate projections, applies small-cell suppression, and offers no recipient
rows, drill-down, filtering, export, or delivery actions. The recipient-aware
**Campaign Report** still requires recipient-read access and does not yet hide
every action control that the actor lacks. The server authorizes each action,
but permission-aware action visibility on that detailed surface remains open
work; do not confuse it with the aggregate reader experience.
The module-local aggregate surface remains at `/campaigns/reports`. When the
optional Reporting module is enabled, Campaign also contributes the same
recipient-free projection as the `campaigns/delivery-outcomes` report provider.
Reporting owns `/reports`, records the run purpose, effective audience, source
campaign/version revision, privacy transformations, retention, actor/time,
output hash, and export history, and applies Policy before returning the
result. Campaign does not register a fallback `/reports` route when Reporting
is absent.
The detailed Campaign Report includes the selected campaign's effective
retention policy, its system/tenant/owner/campaign provenance, and the current
evidence state. It distinguishes retained, redacted, expired, partially
minimized, unavailable, and not-applicable source JSON, stored report detail,
generated EML, and Postbox-copy evidence. When Policy is absent, the report
shows the platform defaults and explicitly warns that automated retention
enforcement is unavailable. Retention removes or minimizes detail; aggregate
counters and audit references may remain so outcomes can still be explained.
### Deliver and resolve outcomes
Use the [delivery runbook](CAMPAIGN_DELIVERY_RUNBOOK.md) for the detailed
operator sequence.
At a minimum:
1. Queue only a validated, locked, built version. Use **Queue for workers** for
ordinary batches; the durable progress remains visible after leaving and
returning to Review and send.
2. Use **Send now** only when the exact persisted eligible build is within the
effective synchronous limit shown by the UI. The unchanged default limit
is 25 recipient jobs. The backend repeats the count and preflights every
message and the Mail profile revision before contacting SMTP.
3. Treat `smtp_accepted` as protected from ordinary retry.
4. Retry `failed_temporary` explicitly after inspecting the cause.
5. Include `failed_permanent` only after correcting the cause and making a
conscious override.
6. Never retry `outcome_unknown` blindly. Inspect SMTP/provider evidence and
reconcile it as **accepted** or **not sent**, with a note identifying the
evidence.
7. Process append-to-Sent only for SMTP-accepted jobs and investigate append
failure independently. Never retry an `outcome_unknown` IMAP append blindly:
reconcile mailbox evidence as **appended** or **not appended** with a note.
Only the latter becomes explicitly retryable, and neither decision resends
the already SMTP-accepted message.
8. Archive only after active and uncertain effects are resolved.
9. Use **Delete draft** only for an untouched draft. If retained evidence or an
active share exists, revoke the share where appropriate or archive instead.
Pause stops new eligible work but cannot undo a provider effect already in
progress. Cancel marks work that has not yet produced a protected SMTP outcome;
it cannot recall accepted mail.
Configure **Administration → SYSTEM → Campaign delivery** with
`system:settings:read/write`. The default stays 25, but an administrator may
explicitly choose 0500, for example 200 for a 183-recipient-job run. Zero disables
Send now. **TENANT → Campaign delivery** uses `admin:policies:read/write` and may
only narrow the inherited system policy. Clearing an override restores
inheritance. An explicitly configured deployment ceiling
`GOVOPLAN_CAMPAIGN_SYNCHRONOUS_SEND_MAX_RECIPIENTS` remains authoritative; the
implicit default does not prevent a system administrator choosing a larger
bounded value. Save changes only this setting, preserves unrelated settings,
checks a revision token including inherited policy, and records before/after
configuration history and audit. Failed saves retain the draft; conflicting
saves require explicit reload/reconciliation. Policy edits never send mail or
change existing reviews or approval requirements.
This limit applies to one interactive Send now request, not campaign size or
worker batching. Larger interactive requests run longer and can meet proxy
timeouts. Queue for workers is independent and requires enabled, healthy
Redis/Celery infrastructure; changing the numeric limit does not start workers.
The effective value and source are returned by the protected delivery-options
API, recorded for successful/rejected synchronous commands, and stated in the
configured handbook topic.
### Test and one-message actions
The current baseline includes mock send, queue dry-run, synchronous immediate
delivery, sending one selected job, retry selection, and unattempted-job
selection. Their audit and state behavior is not yet the final vocabulary
accepted for issue `govoplan-campaign#69`.
The intended distinction is:
- **Test:** deliver once to the configured test path, audit it, and leave the
production job unsent.
- **Single send:** send one unsent job, audit it, and mark its production effect
complete.
- **Single resend:** intentionally send one job again regardless of an earlier
failure or acceptance, with distinct authority, warning, reason, and audit.
Until that slice is implemented and target-tested, do not present the existing
buttons as a complete resend policy. Ordinary retry protection remains the safe
default.
## Data and evidence model
### Portable Campaign transfer
Campaign offers two reuse paths with different boundaries. **Copy campaign**
creates another campaign inside the same installation and can reuse selected
local shares, policies, and Mail profile references. **Export package** creates
a versioned JSON hand-off whose selected scopes can cross an installation
boundary; **Import package** always creates a separately owned draft.
The export dialog starts with only metadata and template/configuration. Add
recipients, attachment rules, review state, or delivery history only when the
handoff requires them and the destination and retention are approved. Recipient
and delivery scopes remain protected by recipient/report export permissions.
Transport secrets, credential references, password-field values, local storage
locators, and attachment bytes are always removed. The manifest records scope
counts and redactions, while the envelope carries source Campaign/version
provenance and a SHA-256 digest.
Import verifies format, scope, checksum, schema, and destination identity before
showing the plan. Editing the destination identity or selected scopes makes the
preview stale and requires a new check. The apply step clears source Mail
references, creates one editable draft, and stores a bounded source/package and
created/skipped receipt. Historical validation/build summaries, review state,
approvals, delivery jobs, attempts, and sent outcomes are never replayed. File
content is never embedded, so reconnect managed files and local Mail profiles,
then validate, build, review, and approve normally.
### Versions and snapshots
Editable campaign JSON is versioned. Build creates recipient jobs and an
execution snapshot. A new profile-only snapshot contains the stable Mail
profile id, delivery policy/evidence, and opaque Mail-issued transport
revisions. It contains no resolved SMTP/IMAP configuration or credentials.
At delivery time Mail re-authorizes the profile, compares the expected opaque
revisions, resolves credentials inside the same Mail-owned operation, and
performs the transport effect. Campaign receives only the sanitized result it
needs for job state and evidence. This same-call check prevents a
validate-then-use race across the module boundary.
Credential rotation that does not change the non-secret transport identity may
continue to satisfy the snapshot. A host, account identity, protocol, sender,
or other revisioned transport change stops delivery until the campaign is
revalidated and rebuilt.
### Existing legacy database records
The current Campaign database may contain versions with inline SMTP/IMAP
material. No separate historical Campaign JSON corpus exists. Inline transport
material in those rows is treated as inert legacy data and is never interpreted
as an executable Mail configuration:
- public responses remove legacy transport fields and secrets;
- validation, build, queue, retry, and delivery fail closed;
- computed previews that require a current Campaign configuration return an
actionable validation problem instead of a server error;
- an editable version changes to profile-only form only through an explicit
Mail-settings save; and
- a locked version is preserved and must be forked to an editable successor.
Normal database backup/restore and access controls cover those rows together
with the rest of the current database. There is no separate historical-JSON,
backup-scanning, or inline-secret migration program. Restoring an existing
legacy row preserves it as inert evidence and does not make it deliverable.
### Recipient and attachment evidence
The delivery record should be able to identify:
- source/import context and the frozen recipient row;
- effective addressing and message id;
- template inputs and unresolved-placeholder decisions;
- managed file/version ids, source provenance, checksums, and ZIP evidence;
- generated EML checksum and size;
- Mail profile reference and opaque transport revisions;
- each SMTP and IMAP attempt, its classification, safe provider response, and
reconciliation note; and
- actor/system trigger, timestamps, policy context, and corrections.
An excluded recipient/message is a completed validation decision, not a
pending delivery. Its SMTP and IMAP states are both `skipped`; it is counted and
filterable separately from unattempted, failed, accepted, and append outcomes.
No SMTP or IMAP attempt exists for such a row. If historical data contains
actual transport evidence despite an exclusion marker, that evidence is
preserved for audit and reconciliation rather than relabelled.
## Administration and policy
### Roles and permissions
The supplied role templates deliberately separate preparation, review, and
delivery:
- **Campaign manager** prepares, validates, and builds campaigns and recipients.
- **Campaign reviewer** inspects prepared material and records review
completion for the exact messages checked.
- **Campaign sender** queues, sends, pauses/resumes/cancels, retries, reconciles,
reads reports, and has diagnostic access.
Administrators may compose narrower roles from the declared permissions. Keep
these separations where institutional policy requires four-eyes approval. Mail
profile use additionally requires `mail:profile:use`; profile and credential
administration remains a Mail permission.
Campaign access is also constrained by tenant, owner/group context, and explicit
shares. A share grants only its declared campaign permission; it does not grant
Mail credentials, Files administration, or tenant-wide recipient access.
### Configuration checklist
- Install compatible Core and Campaign versions. Access and base audit are Core
infrastructure; install optional Mail, Files, Addresses, Notifications, and
Policy modules only where the configured journey requires them.
- Configure Mail profiles and policy in Mail, not in campaign fixtures or JSON.
- Configure Files storage/connectors and attachment permissions in Files.
- Define role assignments and separation-of-duty policy.
- Configure Redis/Celery for durable batch workers where production volume
requires it. Local execution is a development/small-run mode, not horizontal
worker coordination.
- Set rate limits for the target provider. Mail may use Redis for shared
throttling when present and a process-local fallback in development.
- Establish retention, deletion, archive, report-export, and diagnostic-access
policy before production recipient data is loaded.
- Use non-production SMTP/IMAP identities and the maintained examples before
allowing a production profile.
### Owner transfer
Mail profile visibility can depend on campaign owner or group. Transferring an
editable campaign with a selected profile clears/requires reselection and
revalidation. A locked or delivery-final version is never silently rewritten;
create an editable successor.
## Operations and recovery
### Interactive delivery and Sent-folder progress
Send now and workerless Report retry/continue use a compact blocking progress
dialog, as does inline append-to-Sent. It refreshes saved counters only, not the
whole campaign behind the overlay. Successful, pending, in-progress, failed,
uncertain and excluded messages are shown separately; a currently sending or
appending message therefore does not disappear between totals. Read errors keep
the last known counters. After a connection interruption, processing may still
be running; inspect saved evidence before repeating any action. A successful
write is not reclassified as failed when its later display refresh fails.
The recipient-aware Report shows all frozen To, Cc and Bcc addresses, not only
the primary row identity. Address order and recipient-read authorization remain
unchanged. SMTP/IMAP diagnostics use translated status-list filters.
Without workers, explicitly retry eligible failures or continue unattempted
jobs through Report. Each request uses the canonical job/attempt recovery
boundary and is limited by the effective synchronous policy. Accepted and
uncertain SMTP outcomes remain protected. Active abandoned claims require an
expired durable lease, proven stopped/replaced owner, current revision and
valid original evidence before recovery can mark them unknown. A separate
evidence-note reconciliation is required before retrying. A timeout alone is
never proof of non-delivery. See the delivery runbook for required permissions
and operational limitations.
Append-to-Sent is scoped to the selected version and reuses a bounded Mail-owned
connection/folder resolution (default 100 messages or 300 seconds), while
performing one sequential APPEND and all current checks per message. Uncertain
appends are never automatically repeated, and repairing Sent never resends SMTP.
Link required files before locking. Lock and validate waits for attachment
matches, rechecks them immediately before locking, and asks for Link and lock
confirmation if new unlinked files are found. A locked version cannot acquire
new attachment links; use an editable version and validate/build/review again.
### Fortschritt, Wiederherstellung und Dateiverknüpfungen
Jetzt senden, synchrone Wiederholung/Fortsetzung im Bericht und Kopieren nach
Gesendet verwenden einen kompakten sperrenden Fortschrittsdialog. Nur gespeicherte
Zähler werden aktualisiert, nicht der Arbeitsbereich im Hintergrund. Erfolgreich,
ausstehend, in Bearbeitung, fehlgeschlagen, ungewiss und ausgeschlossen bleiben
getrennt sichtbar. Bei Lesefehlern bleiben die letzten Werte erhalten. Nach einer
getrennten Verbindung kann die Verarbeitung weiterlaufen; prüfen Sie Nachweise,
bevor Sie erneut handeln. Ein bestätigter Versand wird durch einen nachfolgenden
Anzeigefehler nicht nachträglich als fehlgeschlagen dargestellt.
Der empfängerbezogene Bericht zeigt alle eingefrorenen An-, Cc- und Bcc-Adressen
in ihrer Reihenfolge. Leseberechtigungen bleiben unverändert. SMTP und IMAP
verwenden übersetzte Zustandslisten zum Filtern.
Ohne Worker können bekannte Fehler ausdrücklich wiederholt und unversuchte
Aufträge begrenzt fortgesetzt werden. Die wirksame synchrone Grenze, gespeicherte
Aufträge, Prüfungen, Freigaben und Wiederherstellungsnachweise bleiben verbindlich.
Angenommene und ungewisse SMTP-Ergebnisse werden nicht blind wiederholt. Die
Wiederherstellung aktiver, verlassener Aufträge benötigt eine abgelaufene Sperre,
nachweislich gestoppte/ersetzte Laufzeit und gültige ursprüngliche Nachweise. Sie
setzt ausschließlich auf ungewiss; vor Wiederholung sind externe Nachweise und
ein getrennter Abgleich mit Notiz erforderlich. Zeitablauf allein genügt nicht.
Kopieren nach Gesendet betrifft nur die ausgewählte Version. Mail verwendet die
Verbindung und Ordnerauflösung begrenzt wieder (Standard: 100 Nachrichten oder
300 Sekunden), prüft aber jede Nachricht erneut und führt APPEND nacheinander
aus. Ungewisse Ergebnisse werden nicht automatisch wiederholt. Verknüpfen Sie
benötigte Dateien vor dem Sperren; eine frische Prüfung fragt bei unverknüpften
Treffern nach Verknüpfen und sperren. Gesperrte Versionen benötigen zum Ändern
eine bearbeitbare Version mit erneuter Validierung, Build und Prüfung.
### Health to observe
- database and migration health;
- compatible module interface versions;
- queue publication, worker heartbeat, claim age, and backlog;
- SMTP/IMAP profile test result and deployment egress policy;
- Mail profile authorization/transport-revision mismatch;
- Files resolution and frozen attachment availability;
- counts by queue, SMTP, IMAP, and reconciliation state; and
- audit and report generation failures.
### Worker interruption
A crashed worker can leave a job claimed or sending. Do not infer non-delivery
from worker loss. If the SMTP boundary may have been crossed, classify the
outcome as unknown and reconcile from external evidence. Only work that is
provably unattempted may be returned to an ordinary queue.
### Retry and reconciliation
Retries create new attempt evidence; they do not overwrite the previous
attempt. Reconciliation is a privileged factual correction supported by an
operator note. Repeated automated requests should be idempotent for the same
eligible job state; a deliberate resend is a different, not-yet-finalized
business action and must never be disguised as a retry.
### Backups and restoration
Back up Campaign, Mail, Files, Core/Audit, and shared storage consistently for
the composition. A database restore without generated EML/file storage, or file
storage without matching metadata, does not reconstruct the evidence chain.
After restore, keep outbound delivery paused until queue/attempt state and
provider evidence have been reconciled; never let restored accepted jobs send
again merely because a queue message was lost.
For unreferenced generated objects after process loss, platform operators first
run the bounded Campaign artifact inventory in dry-run mode. Apply only an
inspected page with a unique incident idempotency key. The cleanup keeps exact
keys out of ordinary Campaign responses, does not clear database references,
and leaves storage failures in the Core recovery ledger for explicit retry.
### Incident handling
1. Pause new delivery when duplicate or unknown effects are possible.
2. Preserve database, queue, provider, worker, and audit evidence.
3. Scope affected campaigns/jobs without exposing recipient content broadly.
4. Reconcile uncertain jobs individually or through an approved bounded tool.
5. Correct configuration in its owning module, then revalidate/rebuild where
revisioned transport inputs changed.
6. Record the incident reference and recovery rationale in audit evidence.
## Composition and integration contracts
Campaign has one required platform dependency: Core. Optional module behavior
is discovered through versioned, Core-mediated capabilities; Campaign must not
import optional sibling ORM, services, or WebUI implementation.
Current principal contracts include:
| Contract | Direction | Purpose |
| --- | --- | --- |
| `mail.campaign_delivery` 0.2.x | Mail -> Campaign | Summarize a known reference; authorize and revision-gate it; send/append using Mail-owned configuration and credentials; return sanitized results |
| `files.campaign_attachments` 0.1.x | Files -> Campaign | Select/materialize governed file versions and preserve campaign usage/evidence |
| `addresses.lookup` 0.1.x | Addresses -> Campaign | Optional address suggestions |
| `addresses.recipient_source` 0.1.x | Addresses -> Campaign | Optional versioned recipient-source snapshots |
| `dist_lists.source` / `dist_lists.expand` 0.1.x | Distribution Lists -> Campaign | Discover, preview, and freeze reusable audiences without importing module internals |
| `templates.catalog` / `templates.renderer` 0.1.x | Templates -> Campaign | Select compatible published printable templates and produce deterministic, evidence-bearing artifacts |
| `campaigns.access` 0.1.x | Campaign -> platform | Explain campaign access/existence without exporting ORM objects |
| `campaigns.mail_policy_context` 0.1.x | Campaign -> Mail | Resolve campaign tenant/owner context for Mail policy |
| `campaigns.delivery_tasks` 0.1.x | Campaign -> workers | Execute narrow queued send/append tasks |
| `campaigns.retention` 0.1.x | Campaign -> retention | Apply Campaign-owned retention behavior |
Breaking payload or ownership changes require an interface-version bump and a
release-composition alignment gate. Optional absence must be tested physically,
not only hidden in navigation.
### Bulk recipient activation
Recipient data can activate all currently inactive rows or deactivate all
currently active rows. The action displays the exact affected count and
requires confirmation. It changes only the local Campaign draft until the
operator saves. Saving follows the ordinary versioned Campaign update path, so
the resulting recipient state is retained in version and protocol evidence and
any stale validation, build, or review evidence is invalidated.
### Reusable Distribution Lists
When Distribution Lists is available, Recipient data offers a separate import
dialog. The author selects a visible list revision, supplies declared
parameters, requests candidate channels, and previews included, excluded,
stale, ambiguous, suppressed, policy-blocked, and provider-unavailable results.
The final action freezes an idempotent Distribution Lists snapshot and copies
the resulting rows into the editable Campaign version.
Each copied row retains the list and revision IDs, definition and expansion
hashes, snapshot ID, source entry IDs, provider references, channel candidates,
the explicitly selected primary route, optional fallback, and the decision
explanation. Campaign-only fields, attachment rules, review state, and outcomes
remain local to Campaign and never mutate the reusable list.
A later list revision only raises a drift warning. Refresh is deliberate and
uses append or replace; saving that changed Campaign version clears prior
validation, build, review, and execution state through the normal content
invalidation path. Preferred or single usable candidates are preselected
visibly; ambiguous rows must be decided before freezing. Postal and
internal-mail routes remain active when a compatible published Templates output
is selected.
### Governed hybrid and printable delivery
Campaign supports Mail, Postbox, printable output, and bounded ordered
fallbacks without making any of those provider modules mandatory. Opt-in and
channel-preference data are inputs to the visible routing decision; they never
silently cause duplicate delivery.
For a printable route, select a published label, envelope, serial-letter,
form-letter, list-layout, or generic template on the Template page. Validation
checks the selected revision, output format, and required fields. Build sends
one deterministic item collection to `templates.renderer`, records template,
input, output, actor, route, and artifact hashes, and stores the resulting
artifact through Files when configured. The review stage exposes that exact
artifact and its hashes before execution.
Each recipient job records an idempotent print acceptance attempt for its item
in the frozen artifact. `mail_then_print` and `postbox_then_print` invoke print
only after a confirmed rejection before acceptance. An accepted or
outcome-unknown digital effect never falls through to print because that could
produce duplicate delivery. Reports and CSV exports include route provenance,
print state, attempts, artifact reference, and hashes.
Without Templates, Campaign still loads and Mail/Postbox authoring remains
available; validation explains why a configured print route cannot proceed.
Without Files, Templates may return a bounded artifact instead of a managed
file. Campaign copies that payload into shared object storage and exposes it
through the Campaign ACL plus `campaigns:recipient:read`; it never redistributes
the broader Templates URL. A print-only Campaign does not require Mail or Postbox.
### External API expectations
- Tenant and campaign access are evaluated for every operation.
- Writes require CSRF/auth behavior supplied by Core and the specific declared
scope.
- Queue/retry/reconcile endpoints operate on persisted state and return safe
summaries; initiating HTTP success is not proof of external delivery.
- Delta endpoints are optimization surfaces, not a separate source of truth.
- Report exports contain permitted business/evidence fields but no credentials,
local paths, storage keys, or worker claims.
## Assurance model
### Security invariants
- No new campaign payload, fixture, response, or execution snapshot contains
SMTP/IMAP settings or credentials.
- Mail resolves credentials and performs transports inside Mail-owned calls.
- Every real connector peer is validated and pinned at connection time under
deployment-wide private-network policy.
- API/server attachment paths use managed Files references; arbitrary and
traversal-capable local paths are rejected.
- Public responses recursively remove infrastructure locators and secret-like
legacy fields.
- Accepted and outcome-unknown jobs are protected from ordinary retry.
- Diagnostic permission is separate from campaign read/report access.
### Privacy
Recipient fields and rendered messages may contain personal or sensitive data.
Grant recipient read/export, report export, and diagnostic access separately.
Prefer aggregate status for readers who do not need recipient detail. Define
purpose, lawful basis, minimization, export control, and retention before the
campaign starts; do not use Campaign as a substitute consent or address-master
system.
The Core data-subject-request workflow discovers Campaign through the optional
`privacy.dsar.campaigns` capability. After the request's email, membership, and
namespaced Campaign references have been independently authorized and
corroborated, the provider searches only the effective tenant and isolates the
matching recipient entries and jobs. Its JSON result includes safe Campaign,
version, delivery-attempt, schedule, report-projection, share, import-mapping,
attachment, generated-artifact, and relevant collaboration metadata. It also
finds collaboration entries authored, mentioned, or moderated by the subject.
Text authored by the subject is included; somebody else's text is not copied
merely because the subject was mentioned. Generated EML bytes and paths,
storage locators, delivery target snapshots, worker claims, idempotency
material, credentials, secret-like values, and unrelated recipients are never
embedded in that result. Authorized Campaign and Files review surfaces remain
the source for content that cannot safely be copied into the DSAR case.
Built, locked, published, terminal, delivered, or corrected records are
retained with an explicit reason and continue through Campaign's configured
retention and redaction process. Draft recipient content and user-owned
attachment content require coordinated manual review because copies may span
version JSON, jobs, generated messages, and managed files. The provider can
idempotently revoke an active share aimed at the subject and delete the
subject's personal recipient-import mapping profile. It does not rewrite
delivery evidence, delete generated artifacts, or report derived Campaign
counts as a separate store. Collaboration withdrawal and redaction retain the
tombstone, content hash, context, and Audit evidence; the DSAR workflow does
not rewrite these append-only records. Re-running an approved action is safe: already
revoked or absent data is reported as unchanged, and tenant, subject, and row
ownership are revalidated immediately before mutation.
### Audit and destructive actions
Material authoring, validation, locking, review, queueing, send, retry,
reconciliation, sharing, owner transfer, archive, and permitted deletion actions
emit attributable evidence. Audit details must be non-secret and should refer to
stable ids rather than repeat message bodies or credentials.
Draft deletion is allowed only while no audit-relevant build, delivery job, or
lock exists. Evidence-bearing campaigns are archived. Destructive module
retirement remains a separately confirmed installer operation with backup and
retirement evidence.
The Campaign **Audit** page is currently an explained handoff, not a second
audit store: Campaign emits bounded platform audit records and authorized
readers inspect them in Administration > Tenant audit. A future object-scoped
projection may improve that navigation without duplicating Audit ownership.
Evidence-bundle export and offline verification remain owned by Audit #3.
The advanced **JSON** page displays and downloads the complete campaign
configuration available to the current campaign reader. It contains no inline
transport secrets, but recipient, message, and attachment fields may contain
personal data. The UI therefore identifies it as sensitive expert output;
campaign access and export purpose remain the governing controls.
## Reference-composition acceptance
Campaign is ready to serve as the demonstration module only when all of the
following are repeatable in a pinned clean installation:
1. The maintained examples validate and build with Mail/Files present, and
Campaign still starts with each optional module absent.
2. A user can import recipients, select managed files, choose an authorized Mail
profile, validate, review, build, and queue without entering transport ids or
secrets manually.
3. Campaign JSON and all ordinary APIs reject/omit inline transport material;
legacy records are visible as migration-required and cannot execute.
4. SMTP success, temporary/permanent failure, connection loss, worker loss,
outcome unknown, retry, reconciliation, IMAP success, and IMAP failure have
tested, non-duplicating outcomes against the target environment.
5. Author, reviewer, sender/operator, reader, and administrator views use the
central component system and expose only task-relevant actions.
6. Reports and audit can reconstruct recipient/message/file/profile/attempt
evidence without exposing secrets or ordinary-reader infrastructure details.
7. Clean install, upgrade, backup/restore, module permutations, version
alignment, security audit, and target SMTP/IMAP tests pass.
8. This handbook and its adaptive Docs topics match the shipped UI wording and
distinguish implemented behavior from planned work.
## Explicitly planned, not yet claimed
The following are part of the selected reference journey but are not implied by
the current baseline:
- the final audited **test / single send / single resend** semantics;
- durable, idempotent Campaign report delivery through a Mail-owned outbox
([`govoplan-mail#17`](https://git.add-ideas.de/GovOPlaN/govoplan-mail/issues/17));
- a fully packaged one-command Campaign reference composition with production
policy presets and target-provider certification;
- function-bound Postbox delivery (stage 2 of the reference program);
- generic workflow-driven campaign transitions.
Each item needs an owning issue, implementation, failure tests, documentation,
and release evidence before the wording above can move from planned to current.