149 lines
7.4 KiB
Markdown
149 lines
7.4 KiB
Markdown
# 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.
|
|
|
|
## Deliverability Preflight
|
|
|
|
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. Validate the version with file checks enabled.
|
|
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.
|
|
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.
|
|
|
|
## Outcome Handling
|
|
|
|
- `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.
|
|
- `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.
|
|
- 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.
|
|
|
|
## 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. 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.
|
|
|
|
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,
|
|
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.
|
|
|
|
## Reporting Checks
|
|
|
|
- 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.
|