175 lines
7.6 KiB
Markdown
175 lines
7.6 KiB
Markdown
# Campaign SMTP/IMAP Test Bed
|
|
|
|
This test bed provides dedicated non-production SMTP/IMAP infrastructure for
|
|
campaign delivery checks. It uses GreenMail and must never be configured with
|
|
production recipients or credentials.
|
|
|
|
## Start
|
|
|
|
```bash
|
|
cd /mnt/DATA/git/govoplan-campaign/dev/mail-testbed
|
|
cp .env.example .env
|
|
docker compose --env-file .env up -d
|
|
```
|
|
|
|
Default endpoints:
|
|
|
|
- SMTP: `127.0.0.1:3025`, plain transport
|
|
- IMAP: `127.0.0.1:3143`, plain transport
|
|
- GreenMail API/UI endpoint: `127.0.0.1:38080`
|
|
|
|
## Smoke Test
|
|
|
|
Run the transport smoke from the core virtual environment after installing the
|
|
campaign and mail modules:
|
|
|
|
```bash
|
|
cd /mnt/DATA/git/govoplan-campaign/dev/mail-testbed
|
|
set -a
|
|
. ./.env
|
|
set +a
|
|
/mnt/DATA/git/govoplan-core/.venv/bin/python run_transport_smoke.py
|
|
```
|
|
|
|
The smoke sends and appends three messages:
|
|
|
|
- no attachment
|
|
- one normal attachment
|
|
- one password-protected AES ZIP attachment
|
|
|
|
It verifies SMTP authentication, IMAP authentication, folder listing, delivery
|
|
to `INBOX`, and append-to-Sent behavior.
|
|
|
|
If the smoke is started immediately after `docker compose up -d`, GreenMail may
|
|
bind the SMTP/IMAP ports before the services are fully ready. The smoke retries
|
|
login and folder setup for `GOVOPLAN_MAIL_TEST_READY_TIMEOUT_SECONDS`.
|
|
|
|
## Campaign Acceptance
|
|
|
|
The transport smoke proves the Mail adapters. The Campaign acceptance runner
|
|
proves the public composition: it creates an isolated temporary Core database,
|
|
creates a Mail-owned encrypted profile through the API, materializes the
|
|
credential-free [`greenmail-delivery`](../../examples/greenmail-delivery/campaign.json)
|
|
fixture with only that profile reference, validates/builds it, sends the exact
|
|
generated EML through Campaign once, appends it once, and cross-checks Campaign
|
|
report/audit state with one unique-subject message in the GreenMail INBOX and
|
|
Sent folders. Provider mailbox verification is not a byte-for-byte comparison
|
|
after provider-side header or storage transformations.
|
|
|
|
```bash
|
|
cd /mnt/DATA/git/govoplan-campaign/dev/mail-testbed
|
|
set -a
|
|
. ./.env
|
|
set +a
|
|
/mnt/DATA/git/govoplan/.venv/bin/python run_campaign_acceptance.py \
|
|
--evidence /tmp/govoplan-campaign-greenmail-evidence.json
|
|
```
|
|
|
|
The default run also uses controlled loopback protocol endpoints to prove that
|
|
an SMTP connection loss before transmission is temporary, an explicit SMTP
|
|
authentication rejection is permanent, a final `451` response after DATA is
|
|
temporary, one accepted and one refused RCPT command is retained as partial
|
|
envelope acceptance, a connection loss after complete DATA is frozen as
|
|
`outcome_unknown`, and an IMAP authentication rejection after SMTP acceptance
|
|
leaves the send accepted while the append fails. A
|
|
second ordinary send must be rejected before another provider effect.
|
|
|
|
The worker drill queues one job, starts the registered
|
|
`govoplan.campaigns.send_email` task body in a dedicated OS process, waits until
|
|
the controlled endpoint has received complete DATA, terminates that process,
|
|
and starts the same task body in a fresh process. It proves the durable
|
|
`sending`/unfinished-attempt boundary is recovered as `outcome_unknown`
|
|
without a second SMTP connection or DATA transaction. This is a real process
|
|
and task-boundary interruption, but it does not start a Celery daemon, Redis
|
|
broker, or broker redelivery; `celery_broker_redelivery` therefore remains
|
|
`false` in the evidence.
|
|
|
|
The bounded JSON contains no endpoint, account, address, credential, profile,
|
|
campaign, version, or job identifiers. It records module versions, the fixture
|
|
hash, normalized classifications/counts, the Mail-profile boundary, required
|
|
audit actions, provider mailbox increments, and coverage flags. Runtime version
|
|
declarations identify the exercised composition; they do not claim that the
|
|
sources are clean, tagged, signed, or release-provenanced. The evidence names
|
|
them `declared_module_versions` and keeps `source_artifact_provenance` false;
|
|
exact commit/artifact provenance belongs to the package and release gate.
|
|
|
|
This runner is restricted to literal loopback IP addresses and the synchronous
|
|
Campaign delivery mode. Hostnames such as `localhost` and every non-loopback
|
|
address fail before profile creation, avoiding a DNS change between validation
|
|
and connection. It is local target-like evidence, not approval of an
|
|
institution's SMTP/IMAP service. The controlled post-DATA, temporary-response,
|
|
partial-refusal, and task-process interruption drills are local effect-level
|
|
proof, not proof of a target provider's behavior or Redis/Celery broker
|
|
redelivery. Use `--success-only` only when testing the success journey without
|
|
the local failure endpoints.
|
|
|
|
## Redis/Celery Redelivery Acceptance
|
|
|
|
Run the maintained broker/worker-loss acceptance separately from the GreenMail
|
|
journey:
|
|
|
|
```bash
|
|
cd /mnt/DATA/git/govoplan-campaign/dev/mail-testbed
|
|
set -a
|
|
. ./.env
|
|
set +a
|
|
/mnt/DATA/git/govoplan/.venv/bin/python run_celery_redelivery_acceptance.py \
|
|
--evidence /tmp/govoplan-campaign-celery-redelivery-evidence.json
|
|
```
|
|
|
|
The runner creates a unique Compose project, starts only its loopback-bound,
|
|
AOF-enabled Redis service, creates an isolated temporary GovOPlaN database,
|
|
and starts a real Celery worker subscribed to `send_email`. A controlled SMTP
|
|
server holds the transaction after complete DATA and before the final response.
|
|
The runner kills that solo worker with the task still unacknowledged and starts
|
|
a replacement worker. After the configured Redis visibility timeout, the same Celery task identity must be redelivered.
|
|
The replacement must turn the durable
|
|
unfinished attempt into `outcome_unknown`, acknowledge the task, drain the
|
|
broker queue/unacked records, and leave the SMTP endpoint at exactly one
|
|
connection and one DATA transaction.
|
|
|
|
Only bounded counts, classifications, and booleans are retained. Worker logs,
|
|
task IDs, database identifiers, endpoints, credentials, and raw diagnostics are
|
|
kept in the temporary runtime and deleted. The evidence proves the local Redis
|
|
transport, real Celery process boundary, runner-supervised replacement, and
|
|
Campaign's duplicate-effect guard. It deliberately keeps production daemon supervision,
|
|
target-provider behavior, and source-artifact provenance false.
|
|
It does not claim that systemd, Kubernetes, another container orchestrator, or
|
|
an institution's Redis/SMTP deployment behaves identically.
|
|
|
|
The default run requires Docker CLI/Compose/daemon access and permission to
|
|
pull `redis:7-alpine`; the Celery workers execute from the current Python
|
|
environment. The isolated Compose project and volume are removed on exit.
|
|
`GOVOPLAN_CAMPAIGN_TEST_REDIS_VISIBILITY_TIMEOUT_SECONDS` defaults to three
|
|
seconds only to make this destructive local drill finish promptly; it is not a
|
|
production recommendation.
|
|
|
|
Starting the maintained test bed requires a working Docker CLI, Compose plugin,
|
|
daemon/socket access, and permission to pull `greenmail/standalone:2.1.9`. The
|
|
Campaign runner needs only the already-running loopback endpoints; it neither
|
|
starts Docker nor claims that it did.
|
|
|
|
## Use With A Campaign
|
|
|
|
Use the same settings in a campaign mail profile:
|
|
|
|
- SMTP host `127.0.0.1`, port `3025`, security `plain`
|
|
- IMAP host `127.0.0.1`, port `3143`, security `plain`
|
|
- username and password from `.env`
|
|
- append folder from `GOVOPLAN_MAIL_TEST_SENT_FOLDER`
|
|
|
|
When changing the mailbox, keep `GOVOPLAN_MAIL_TEST_USER` equal to
|
|
`${GOVOPLAN_MAIL_TEST_LOCAL_PART}@${GOVOPLAN_MAIL_TEST_DOMAIN}` because the
|
|
compose file creates the GreenMail account from local part, password, and
|
|
domain while login uses the full email address.
|
|
|
|
Then run the controlled sending checklist from
|
|
`docs/CAMPAIGN_DELIVERY_RUNBOOK.md` for no attachment, one attachment, and
|
|
password-protected ZIP campaign variants.
|
|
|
|
## Stop
|
|
|
|
```bash
|
|
docker compose --env-file .env down -v
|
|
```
|