Files
govoplan-campaign/dev/mail-testbed/README.md

5.6 KiB

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

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:

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 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.

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.

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

docker compose --env-file .env down -v