Release govoplan-campaign v0.1.28: stabilize saving, review and delivery recovery
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-09-08 01:32:26 +02:00
parent 1b32427813
commit c51fc180fb
111 changed files with 6905 additions and 980 deletions
+104 -13
View File
@@ -30,9 +30,24 @@ been validated, built, reviewed, and locked.
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
@@ -50,18 +65,25 @@ Before the first live send for a sender domain or mail-server profile:
## Queue And Send
1. Validate the version with file checks enabled.
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/tenant limit shown on the page.
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 Review and send open: it polls the
durable counters while the request runs. A rejection occurs before SMTP and
directs oversized runs to workers.
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
@@ -97,9 +119,10 @@ unknown provider attempt merely to repair the other layer's state.
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: treat as a worker interruption.
Re-run worker handling or reconcile if SMTP may already have accepted the
message.
- `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.
@@ -117,6 +140,65 @@ unknown provider attempt merely to repair the other layer's state.
- 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
@@ -141,17 +223,21 @@ 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. The unfinished durable attempt must become `outcome_unknown` and the
endpoint must observe no second connection or DATA transaction. This covers
the worker task/process boundary but not a broker or daemon.
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 durable `outcome_unknown`, an empty broker queue/unacked set, and
exactly one SMTP connection and DATA transaction. Raw worker logs and task,
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
@@ -190,6 +276,11 @@ 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.