End-to-end suite
End-to-end suite
Beyond the unit/feature suite (fakes and Http::fake()), the package
carries a true end-to-end suite that runs against a real Postal 3.3.7
install — the API client, SMTP submission, webhooks and inbound mail are
all exercised against genuine Postal behaviour, including its real RSA
signatures.
composer test:e2e # ≈ e2e/run.sh — requires Docker
What it spins up
e2e/docker-compose.yml starts MariaDB, Postal (web + SMTP + worker) and
Mailpit as an SMTP sink. Postal
relays all outbound mail to the sink (POSTAL_SMTP_RELAYS), so
deliveries genuinely succeed and MessageSent webhooks genuinely fire.
Postal has no admin API, so e2e/seed.rb provisions the org, mail server,
API/SMTP credentials, verified domains, webhook and inbound route through
a rails runner inside the container. A tiny capture server
(e2e/capture/server.php) records Postal's signed webhook and
inbound-endpoint deliveries byte-for-byte.
What it proves
- API protocol — ping semantics, structured + raw sends, typed
expansions read back, real error envelopes (
InvalidServerAPIKey,MessageNotFound,NoRecipients,UnauthenticatedFromAddress) mapped to the right exceptions. - SMTP protocol — real handshake ping, authenticated submission that lands in the relay sink, API lookups on an smtp-type connection.
- Webhooks — a real, Postal-signed
MessageSentdelivery is captured; its signature is verified with the live JWKS key (viaJwkConverter), tamper-rejection is asserted, and the exact bytes are replayed through the package's own route: controller → verifier → job → store → typed event. - Inbound — a message is delivered into Postal over unauthenticated
SMTP to a routed domain, Postal posts it (signed) to the HTTP endpoint,
and the capture is replayed through
/postal/inboundend to end.
Running
./e2e/run.sh # full cycle: up → seed → test → down
KEEP=1 ./e2e/run.sh # keep the stack for inspection (Mailpit UI :18025)
SKIP_UP=1 ./e2e/run.sh # reuse a stack kept running earlier
The suite lives in tests/E2E as its own PHPUnit testsuite; every test
self-skips unless the POSTAL_E2E_* env vars are present, so it is inert
in normal composer test runs. CI runs it in the dedicated e2e
workflow on every push and pull request.