Merge pull request #240 from carlosframework/email
Tenant email sending for CARLOS: SMTP credentials issued by the platform, backed by SES multi-tenancy. Merged with the required check unable to run — GitHub Actions has been billing-dead since ~2026-08-16 and every run on this branch has died in under 5s with zero executed steps. The gate was reproduced in full locally against the merge result instead: `go build ./...`, `go vet ./...` and `go test ./... -count=1` green across 31 packages, `gofmt` clean, and all three cross-compile targets (darwin/arm64, darwin/amd64, linux/arm64) building. The one job neither that nor amadan's runner covers is `minio-contract`, which needs a container runtime and remains genuinely unverified for this branch. The branch was also pushed to the self-hosted amadan runner as a merge of main, which is what surfaced the role-gate defect fixed in f3483ca — all nine email doors 404'd for every caller on the merge result, invisible on the branch alone. See issuecomment-5386623767. The feature is INERT on merge, by design: cmd/console/main.go constructs the SES provisioner only when CARLOS_MAIL_REGION is set and the Route53 writer only when CARLOS_MAIL_PLATFORM_SUFFIX is set. Both are unset on every deployment today, so every email door 404s and no AWS call happens at boot. Merging changes nothing in production until a console is rolled with those variables.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
52 files changed,
+20111
−35
cmd/console/mailbackends_test.go+128 −0cmd/console/main.go+190 −1docs/superpowers/plans/2026-08-16-woodstar-cutover-runbook.md+37 −6docs/superpowers/plans/2026-08-19-tenant-email.md+5505 −0docs/superpowers/specs/2026-08-19-tenant-email-design.md+719 −0go.mod+9 −8go.sum+18 −16internal/console/apimail.go+1148 −0internal/console/apimail_test.go+1085 −0internal/console/appsettings.go+8 −0internal/console/appsettings_test.go+63 −0internal/console/deletion_test.go+104 −0internal/console/mailcounts.go+275 −0internal/console/mailcounts_test.go+458 −0internal/console/ownership.go+62 −0internal/console/server.go+48 −0internal/console/sesevents.go+694 −0internal/console/sesevents_test.go+677 −0internal/console/store.go+17 −0internal/console/svcroutes_test.go+37 −4internal/dnsrec/dnsrec.go+90 −0internal/dnsrec/dnsrec_test.go+75 −0internal/dnsrec/route53.go+165 −0internal/dnsrec/route53_test.go+35 −0internal/mail/aws.go+470 −0internal/mail/aws_test.go+351 −0internal/mail/dnsrecords.go+93 −0internal/mail/dnsrecords_test.go+107 −0internal/mail/domain.go+178 −0internal/mail/domain_test.go+107 −0internal/mail/domainclaim.go+209 −0internal/mail/events.go+128 −0internal/mail/events_test.go+163 −0internal/mail/mail.go+162 −0internal/mail/mail_test.go+105 −0internal/mail/memprovisioner.go+300 −0internal/mail/memprovisioner_test.go+133 −0internal/mail/policy.go+134 −0internal/mail/policy_test.go+201 −0internal/mail/provisioner.go+77 −0internal/mail/smtppass.go+43 −0internal/mail/smtppass_test.go+55 −0internal/mail/walk.go+1165 −0internal/mail/walk_domain_test.go+372 −0internal/mail/walk_domainclaim_test.go+624 −0internal/mail/walk_enable_test.go+423 −0internal/mail/walk_grant_test.go+86 −0internal/mail/walk_lifecycle_test.go+539 −0internal/mail/walk_verify_test.go+129 −0mail_cli.go+1245 −0mail_cli_test.go+861 −0main.go+4 −0
CI passed — run details
cmd/console/mailbackends_test.goLoading diff…
cmd/console/main.goLoading diff…
docs/superpowers/plans/2026-08-16-woodstar-cutover-runbook.mdLoading diff…
docs/superpowers/plans/2026-08-19-tenant-email.mdLoading diff…
docs/superpowers/specs/2026-08-19-tenant-email-design.mdLoading diff…
go.modLoading diff…
go.sumLoading diff…
internal/console/apimail.goLoading diff…
internal/console/apimail_test.goLoading diff…
internal/console/appsettings.goLoading diff…
internal/console/appsettings_test.goLoading diff…
internal/console/deletion_test.goLoading diff…
internal/console/mailcounts.goLoading diff…
internal/console/mailcounts_test.goLoading diff…
internal/console/ownership.goLoading diff…
internal/console/server.goLoading diff…
internal/console/sesevents.goLoading diff…
internal/console/sesevents_test.goLoading diff…
internal/console/store.goLoading diff…
internal/console/svcroutes_test.goLoading diff…
internal/dnsrec/dnsrec.goLoading diff…
internal/dnsrec/dnsrec_test.goLoading diff…
internal/dnsrec/route53.goLoading diff…
internal/dnsrec/route53_test.goLoading diff…
internal/mail/aws.goLoading diff…
internal/mail/aws_test.goLoading diff…
internal/mail/dnsrecords.goLoading diff…
internal/mail/dnsrecords_test.goLoading diff…
internal/mail/domain.goLoading diff…
internal/mail/domain_test.goLoading diff…
internal/mail/domainclaim.goLoading diff…
internal/mail/events.goLoading diff…
internal/mail/events_test.goLoading diff…
internal/mail/mail.goLoading diff…
internal/mail/mail_test.goLoading diff…
internal/mail/memprovisioner.goLoading diff…
internal/mail/memprovisioner_test.goLoading diff…
internal/mail/policy.goLoading diff…
internal/mail/policy_test.goLoading diff…
internal/mail/provisioner.goLoading diff…
internal/mail/smtppass.goLoading diff…
internal/mail/smtppass_test.goLoading diff…
internal/mail/walk.goLoading diff…
internal/mail/walk_domain_test.goLoading diff…
internal/mail/walk_domainclaim_test.goLoading diff…
internal/mail/walk_enable_test.goLoading diff…
internal/mail/walk_grant_test.goLoading diff…
internal/mail/walk_lifecycle_test.goLoading diff…
internal/mail/walk_verify_test.goLoading diff…
mail_cli.goLoading diff…
mail_cli_test.goLoading diff…
main.goLoading diff…