(Story #2554) [WIP] Route QA email to a shared maildev inbox - #2555
Draft
herzog0 wants to merge 4 commits into
Draft
(Story #2554) [WIP] Route QA email to a shared maildev inbox#2555herzog0 wants to merge 4 commits into
herzog0 wants to merge 4 commits into
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
herzog0
force-pushed
the
teo/2554-maildev-k8s
branch
from
August 4, 2026 18:46
ba2d090 to
df43eae
Compare
herzog0
force-pushed
the
teo/2554-maildev-k8s
branch
from
August 4, 2026 19:26
980345f to
c4ee485
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ref #2554
Why
Non-production deployments send real email to real recipients.
config/settings.pyhardcodedEMAIL_BACKENDto the Mailgun Anymail backend for every environment exceptLOCAL_DEVELOPMENT, and bothvalues-stage-gke.yamlandvalues-cppal-dev-gke.yamlsupply live Mailgun credentials and a verified sender domain.That blocks QA work which needs to sign in as existing users with real contribution history, because the sign-in verification email is delivered to that user's actual inbox. More broadly, any transactional email triggered while testing reaches real recipients, consumes email quota, and generates bounces and spam signals against the sender domain.
Local development already solves this with the
maildevcontainer indocker-compose.yml. This brings the same catch-all inbox tostageandcppal-dev, reachable in a browser so QA does not need cluster credentials.What changed
config/settings.py- a newCATCH_ALL_EMAILflag (defaultfalse) makes an environment send through Django's SMTP backend toEMAIL_HOST/EMAIL_PORTinstead of Mailgun. Django raisesImproperlyConfiguredat startup if the flag is ever enabled whileX_DEPLOYMENT_ENVisproduction.The flag is opt-in per values file rather than derived from the environment name. Deriving it (from
X_DEPLOYMENT_ENV, or by pattern-matchingDJANGO_FQDN) would silently switch environments that have no maildev pod, turning working mail into connection errors, and would re-enable real sending if a hostname were ever renamed.kube/boost/templates/maildev.yaml(new) - everything behind amaildevInstallflag, following the existingredisInstall/celeryInstallconvention so production cannot pick it up:Deployment(pinnedmaildev/maildev:2.2.1,replicas: 1) and aClusterIPServiceexposing SMTP1025and HTTP1080.HTTPRoutepublishing the inbox at/maildev/on the environment'smainFqdn, plus aHealthCheckPolicyand aGCPBackendPolicy.Values files -
maildevInstall: trueandCATCH_ALL_EMAIL: "true"in the two QA files;maildevInstall: falseplusmaildevImageTagas chart defaults. Production is untouched. TheMAILGUN_*entries stay in the QA files but are inert, so reverting is a one-line change.Docs - new
docs/email.md(routing per environment, how to reach the inbox, how it is wired), plusCATCH_ALL_EMAIL/EMAIL_*entries indocs/env_vars.mdand an index line indocs/README.md.How the inbox is exposed
The route is added at the existing GKE Gateway, so the Google load balancer that already fronts the site gains one URL-map rule. It reuses the existing static IP and certificate: no new hostname, no DNS record, no certificate, no LoadBalancer, and no change to the app's nginx config. Traffic to
/maildev/never reaches Django, gunicorn or the app pods.stage: https://www.stage.boost.org/maildev/cppal-dev: https://www.cppal-dev.boost.org/maildev/Access is gated by maildev's own HTTP basic auth (
MAILDEV_WEB_USER/MAILDEV_WEB_PASS).Three details that are load-bearing, all verified against
maildev/maildev:2.2.1:MAILDEV_BASE_PATHNAME=/maildevmakes maildev serve itself under the prefix, so no URL rewriting is needed at the edge, and the socket.io endpoint moves under the same prefix where thePathPrefixrule already covers it.HealthCheckPolicytargets/maildev/healthz, the only path maildev exempts from basic auth. A health check against/returns 401, which the load balancer reads as an unhealthy backend and answers with 503.GCPBackendPolicyraisestimeoutSec. On Google load balancers the backend timeout is the maximum lifetime of a WebSocket connection rather than an idle timeout, so the 30 second default would sever the inbox's live-update socket every 30 seconds.Deploy notes
A
maildev-authSecret must exist in the namespace before deploying, or the pod will not start. It is not in the repo because this repository is public:The
values-cppal-dev-gke.yamlchange only takes effect once thecppalliance/website-v2-qafork'scppal-devbranch picks up this commit.Two things to confirm on the first deploy: that the Gateway controller auto-attached a NEG to the
maildevService (otherwise add acloud.google.com/negannotation), and that theGCPBackendPolicyCRD is present (kubectl get crd | grep gcpbackendpolicies;HealthCheckPolicyfrom the same GKE bundle is already used ingateway.yaml).Verification
helm templateacross all four values files:production: no maildev objects at all, Mailgun backend unchanged.stageandcppal-dev:Deployment,Service,HTTPRoute,HealthCheckPolicyandGCPBackendPolicyall render;HTTPRouteresolves towww-boost-stage/www-boost-devon the correct hostname;CATCH_ALL_EMAIL,EMAIL_HOSTandEMAIL_PORTreach all five mail-sending containers (boost/wsgi,boost/nginx,celery-worker,celery-beat) and the migrationsJob.values.yaml): no maildev objects.Settings behavior, exercised in the project image:
LOCAL_DEVELOPMENT=truemaildev:1025X_DEPLOYMENT_ENV=productionMAILGUN_*inANYMAILX_DEPLOYMENT_ENV=stageordevmaildev:1025, emptyANYMAILX_DEPLOYMENT_ENV=productionImproperlyConfiguredat startupEnd to end: with Django configured as the chart configures it and a real
maildev:2.2.1container as the SMTP host,send_mail(...)torealuser@example.comwas captured by maildev rather than delivered, and read back through the authenticated API. Basic auth, the auth-exempt health path, relative asset resolution under the prefix and a real101 Switching Protocolsupgrade at/maildev/socket.io/were all confirmed against the pinned image.pre-commit runpasses on all changed files.Not included
Known limitation
maildev applies basic auth as Express middleware, but its socket.io channel attaches to the raw HTTP server and bypasses that middleware, emitting full message payloads. The password gate deters casual access to the UI; it is not a security boundary for message contents. The URL should not be published. Accepted for a QA inbox.