Files
govoplan-campaign/docs/CAMPAIGN_DELIVERY_RUNBOOK.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

16 KiB
Raw Blame History

Campaign Delivery Runbook

This runbook covers controlled campaign delivery after a campaign version has been validated, built, reviewed, and locked.

Operating Modes

  • Local direct send: CELERY_ENABLED=false. Send now is available only for exact built runs within the effective synchronous limit. It preflights the complete batch before the first SMTP effect.
  • Worker send: CELERY_ENABLED=true with Redis/Celery workers running. Queueing publishes durable delivery tasks, and Review and send polls their persisted summary counters even after the initiating request has returned.
  • Mock send: use only for development review. It does not prove real SMTP/IMAP credentials or server policy.

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.
  • Confirm policy allows the SMTP host, envelope sender, recipients, and optional IMAP append target.
  • Run one campaign each for no attachment, one attachment, and password-protected ZIP before using production recipients.
  • Keep the report page open during tests; it is the operational source of truth for attempts, outcomes, and reconciliation.
  • Confirm the effective Send now recipient-job limit. The safe default is 25; use Queue for workers for ordinary batches or any run above that limit. System administrators can explicitly configure 0500 under Administration → SYSTEM → Campaign delivery; an explicit deployment ceiling remains binding, and TENANT policy may only narrow the inherited limit. This setting affects one synchronous request, not campaign size. It is audited and never sends messages; large interactive requests may encounter proxy timeouts.

Deliverability Preflight

The Mail server connection test checks that selected server and credential. It does not authorize the campaign's sender, recipients, or resource selection. SMTP runtime checks require the selected SMTP credential when policy forbids inheritance, independently of IMAP. Sent-folder append checks IMAP credentials independently; full campaign validation still checks both required selections. Preflight errors distinguish Mail profile/credential policy, SMTP configuration, authentication, and connectivity. Do not change TLS or credential policies merely because a campaign preflight failed. A preflight rejection leaves staged jobs uncommitted and starts no message delivery.

Before the first live send for a sender domain or mail-server profile:

  • Confirm the selected SMTP identity matches the visible From/envelope sender policy.
  • Confirm SPF, DKIM, and DMARC are handled by the sending infrastructure or documented as out of scope for the selected test environment.
  • Confirm rate limits are explicitly set for the expected provider and recipient volume.
  • Confirm bounce/reply/notification addresses are monitored by an operational mailbox or intentionally disabled.
  • Confirm large attachments and password-protected ZIPs are acceptable for the recipient systems.
  • Confirm owner transfer or policy changes force profile reselection and revalidation before live delivery.

Queue And Send

  1. Link all required managed files, then validate the version with file checks enabled. Locking waits for a fresh attachment preview and asks for explicit Link and lock confirmation when matches are not linked. A locked version cannot change attachment links: use an editable version and repeat validation, build and review rather than assuming unlinked files were included.
  2. Build the version and inspect all blocking review items.
  3. Queue only after the selected version is the intended immutable execution version. Select Queue for workers, then verify the committed and published counts.
  4. Use Send now only if the exact eligible count is non-zero and at or below the effective deployment/system/tenant limit shown on the page.
  5. In worker mode, verify queue counters move from queued/claimed/sending to a terminal SMTP state.
  6. If a synchronous request is used, keep its blocking progress dialog open. Only its small version-scoped persisted counters refresh; the workspace, recipient list, attachment preview and full summary stay unchanged. Read-only refresh failure retains the last counters and does not prove delivery failed. A disconnected request may still be executing; never repeat it blindly. Oversized initial runs are rejected before SMTP and directed to workers.
  7. Review the SMTP batch line. ready means DNS/connectivity/TLS/auth preflight succeeded. Connection and reconnect counts explain reuse. paused means a systemic transport failure stopped the remaining jobs before their SMTP effect; test/correct the Mail profile and explicitly resume the queue.

Outcome Handling

Each real Campaign job delivery is represented by a Core recovery-ledger operation before the worker claims the job or invokes Mail, Postbox, or print. Synchronous batches use the same boundary after their batch-wide preflight. Explicit test/resend actions and post-acceptance IMAP appends use separate action-fenced operations. Operations store only opaque IDs, digests, channel policy, and bounded status evidence. A verified acceptance becomes succeeded, a definitive pre-effect or provider rejection becomes rejected, and uncertain or stranded effects remain outcome_unknown or recovery_required in Ops. Campaign jobs, message actions, and channel-attempt records remain the business source of truth.

For SMTP and Sent-folder APPEND, Campaign also passes the stable job/action and attempt identifier into Mail. Mail establishes its own provider-bound recovery fence after profile authorization and policy checks but before network I/O. This nested ownership is intentional: Campaign proves its business transition, while Mail proves the transport effect. Neither layer replays a completed or unknown provider attempt merely to repair the other layer's state.

  • smtp_accepted: Do not retry. If IMAP append is enabled and pending, run or enqueue the append action.
  • failed_temporary: Retry explicitly after checking the error and retry count.
  • failed_permanent: Retry only if the operator has corrected the root cause and intentionally includes permanent failures.
  • paused after a systemic SMTP failure: do not resume until the shared Mail profile passes its connection test. Authentication, sender rejection, and unavailable connectivity affect the batch rather than one recipient.
  • outcome_unknown: Do not retry directly. Check SMTP logs, mailbox evidence, or provider control panels, then reconcile as accepted or not sent.
  • claimed or sending that does not progress: investigate the owning runtime. Duplicate worker handling leaves active state unchanged. Never infer from elapsed time alone that SMTP did not accept the message. Use the fenced Recover interrupted claim action described below when it is available.
  • 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

  • Choose "Accepted" only with external evidence that SMTP accepted the message. The job becomes protected from retry and may proceed to IMAP append.
  • Choose "Not sent" only when SMTP did not accept the message. The job becomes a temporary failure and can be selected by explicit retry.
  • Add a note that identifies the evidence used, for example SMTP log line, provider message ID, or operator ticket.

For pure-Mail SMTP and channel-specific IMAP operations, reconciliation updates the original Campaign attempt, matching Campaign recovery operation and audit record atomically under a fresh lease. A SMTP-only decision cannot resolve an entire compound Mail/Postbox/Print operation; that ledger remains separately unresolved until all of its effects are established. Campaign does not rewrite Mail-owned nested provider-effect operations: those remain Mail's separate evidence and operational responsibility. A failed audit or conflicting claim must leave the prior unknown state intact.

Recovery without workers

The Report offers explicit inline retry and continuation when workers are not configured. Retry uses campaigns:campaign:retry plus campaigns:campaign:send; continuation uses campaigns:campaign:queue plus campaigns:campaign:send. Both use the canonical immutable jobs and ordinary attempt ledger, not a separate one-message resend. Current Mail authorization, review/approval, execution integrity, retry limits and rate limits still apply. Each call is bounded by the effective synchronous recipient-job limit and reports remaining eligible work. Continue explicitly until none remains; it never selects accepted, excluded, active, uncertain or known failed jobs. Known failures have their own explicit retry action. These actions do not turn a long HTTP request into a background worker or guarantee exactly-once SMTP when a provider acknowledgement is lost.

For an abandoned active SMTP or IMAP claim, the report exposes recovery only after the original durable lease expires and the runtime registry proves that its owner stopped or was replaced. A stale heartbeat is insufficient. The opaque claim revision and original recovery evidence are rechecked under a fresh lease. Recovery records the effect as outcome unknown, never not sent. Then separately inspect external evidence and reconcile with a factual note before any retry. This requires campaigns:campaign:reconcile. If the original lease, evidence, or stopped-owner proof is missing, preserve the records and investigate through Ops; do not edit delivery rows or force a lease expiry in a real installation.

Progress totals and Sent-folder batching

For each channel, processed includes successful, failed, uncertain and cancelled outcomes. In progress is separate from pending, so the currently sending/appending message remains visible. Paused SMTP work is also separate. Excluded/non-requested channel work is outside the denominator. The endpoint requires campaign read and object access and returns no recipient addresses, message bodies, attachments or credentials. It is not the privacy-thresholded aggregate Reports view and does not grant that view recipient access.

Append-to-Sent targets the selected campaign version, not all historical versions. Each message remains a separately fenced, sequential IMAP APPEND. Mail reuses the authenticated session and resolved folder for at most 100 messages or 300 seconds by default, then rotates the connection. Current authorization, frozen transport revisions, credentials and recovery checks still run for every message. A stale idle connection is checked before another APPEND; an uncertain APPEND is never replayed. This removes repeated connect/login/folder-list round trips, not the time needed to upload each EML. It is not an atomic MULTIAPPEND transaction or parallel delivery.

SMTP and IMAP use the same progress dialog. An acknowledged operation remains successful even if loading its follow-up diagnostics fails: use Reload to refresh display, not to repeat the external effect.

Fault Injection Checklist

Use mock infrastructure first, then repeat against the non-production real test bed where possible:

  • SMTP temporary failure.
  • SMTP permanent failure.
  • Recipient refusal after partial SMTP acceptance.
  • Connection drop or worker interruption during SMTP.
  • IMAP append failure after SMTP acceptance.
  • Worker restart with queued, claimed, and sending jobs.

For the maintained loopback baseline, run dev/mail-testbed/run_campaign_acceptance.py. It proves the public Campaign path for SMTP acceptance, IMAP append, repeat-send blocking, an SMTP connection failure before transmission, an explicit SMTP authentication rejection, an explicit temporary 451 response after DATA, partial RCPT refusal, a connection loss after complete DATA, and an IMAP authentication rejection after SMTP acceptance. Its evidence is an allowlisted classification/count projection; raw provider diagnostics and transport/account identifiers are deliberately excluded.

The runner also terminates a dedicated OS process executing the registered Campaign send task after complete DATA, then invokes the task in a fresh process. Redelivery must leave the active claim unchanged and the endpoint must observe no second connection or DATA transaction. Only a subsequent explicit, fenced recovery with test-fixture stopped-owner and expired-lease proof may change the unfinished attempt to outcome_unknown. This covers the worker task/process boundary but not a broker or daemon.

Run dev/mail-testbed/run_celery_redelivery_acceptance.py for the maintained Redis/Celery delivery and broker redelivery boundary. It starts an isolated Redis Compose service and real Celery workers, kills the first solo worker after complete DATA while the late-ack task is unacknowledged, and requires the same task identity to reach a replacement worker after Redis visibility recovery. Passing evidence also requires unchanged active state on duplicate delivery, followed by explicit fenced recovery to outcome_unknown, an empty broker queue/unacked set, and exactly one SMTP connection and DATA transaction. Lease expiration is simulated only in the isolated fixture after its owner was stopped. Raw worker logs and task, database, endpoint, and credential identifiers are never retained.

That second runner proves local runner-supervised process replacement, not the production process manager. Repeat the worker-loss drill under the selected systemd, container, Kubernetes, or other production supervisor and the target Redis/SMTP infrastructure before deployment approval.

Shared Build Artifacts

Generated EML is stored through Core's configured object-storage backend under opaque Campaign-owned keys. The job records expected byte size, SHA-256 digest, and Message-ID. A worker on another node must retrieve and verify those values before attempting delivery.

  • Do not expose object keys to ordinary campaign users or copy them into business fields.
  • A build failure deletes objects written before the database transaction can commit.
  • Retention starts a job-fenced forward-recovery operation before deletion, commits metadata changes in the Campaign-owned boundary, and independently probes the original locator before reporting success. An unavailable backend leaves an outcome-unknown operation; a deletion/metadata mismatch becomes recovery-required in Ops.
  • A hard process loss between object creation and metadata commit can leave an orphan object. Use the operator-only, dry-run-first Campaign artifact reconciler documented in CAMPAIGN_BUILD_RECOVERY.md; it scans one bounded tenant-prefix page, enforces a minimum 24-hour grace period, protects active build fences, and rechecks committed EML and print-output references before deletion.
  • Restore Campaign rows, object storage, and the encryption key to one coordinated recovery point before resuming workers.

For a scaled-runtime drill, build on one API replica, consume from another worker, compare the stored evidence, and inject storage failures during build and retention.

Reporting Checks

  • The recipient-aware report shows every frozen To/Cc/Bcc address in authored order, with a primary-address fallback only for old rows without that snapshot. SMTP envelope evidence, not the old primary-only UI, establishes how many recipients were actually offered to the provider. SMTP and IMAP diagnostics use list filters and consistent translated labels.
  • Partial delivery must show accepted, failed, and unknown counts separately.
  • Excluded messages must show SMTP and IMAP as skipped, with skipped counts and filters separate from unattempted or failed delivery.
  • Accepted and unknown jobs must not appear in retry selections.
  • Reconciled accepted jobs must remain protected from resend.
  • Reconciled not-sent jobs must appear only as explicit retry candidates.
  • The final CSV export should include message id, resolved envelope headers, attachment evidence, EML reference/checksum, latest SMTP response/error, and latest IMAP folder/error before the campaign is considered operationally closed.