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