carlos / platform Public

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>
Paul Campbell pushed by paul@keymail.dev fe1a31abdf52c06475f24deea090ee8486c09d14 parent 8fb52b2parent f3483ca
52 files changed, +20111 −35
  • cmd/console/mailbackends_test.go +128 −0
  • cmd/console/main.go +190 −1
  • docs/superpowers/plans/2026-08-16-woodstar-cutover-runbook.md +37 −6
  • docs/superpowers/plans/2026-08-19-tenant-email.md +5505 −0
  • docs/superpowers/specs/2026-08-19-tenant-email-design.md +719 −0
  • go.mod +9 −8
  • go.sum +18 −16
  • internal/console/apimail.go +1148 −0
  • internal/console/apimail_test.go +1085 −0
  • internal/console/appsettings.go +8 −0
  • internal/console/appsettings_test.go +63 −0
  • internal/console/deletion_test.go +104 −0
  • internal/console/mailcounts.go +275 −0
  • internal/console/mailcounts_test.go +458 −0
  • internal/console/ownership.go +62 −0
  • internal/console/server.go +48 −0
  • internal/console/sesevents.go +694 −0
  • internal/console/sesevents_test.go +677 −0
  • internal/console/store.go +17 −0
  • internal/console/svcroutes_test.go +37 −4
  • internal/dnsrec/dnsrec.go +90 −0
  • internal/dnsrec/dnsrec_test.go +75 −0
  • internal/dnsrec/route53.go +165 −0
  • internal/dnsrec/route53_test.go +35 −0
  • internal/mail/aws.go +470 −0
  • internal/mail/aws_test.go +351 −0
  • internal/mail/dnsrecords.go +93 −0
  • internal/mail/dnsrecords_test.go +107 −0
  • internal/mail/domain.go +178 −0
  • internal/mail/domain_test.go +107 −0
  • internal/mail/domainclaim.go +209 −0
  • internal/mail/events.go +128 −0
  • internal/mail/events_test.go +163 −0
  • internal/mail/mail.go +162 −0
  • internal/mail/mail_test.go +105 −0
  • internal/mail/memprovisioner.go +300 −0
  • internal/mail/memprovisioner_test.go +133 −0
  • internal/mail/policy.go +134 −0
  • internal/mail/policy_test.go +201 −0
  • internal/mail/provisioner.go +77 −0
  • internal/mail/smtppass.go +43 −0
  • internal/mail/smtppass_test.go +55 −0
  • internal/mail/walk.go +1165 −0
  • internal/mail/walk_domain_test.go +372 −0
  • internal/mail/walk_domainclaim_test.go +624 −0
  • internal/mail/walk_enable_test.go +423 −0
  • internal/mail/walk_grant_test.go +86 −0
  • internal/mail/walk_lifecycle_test.go +539 −0
  • internal/mail/walk_verify_test.go +129 −0
  • mail_cli.go +1245 −0
  • mail_cli_test.go +861 −0
  • main.go +4 −0

CI passed — run details

cmd/console/mailbackends_test.go

Loading diff…

cmd/console/main.go

Loading diff…

docs/superpowers/plans/2026-08-16-woodstar-cutover-runbook.md

Loading diff…

docs/superpowers/plans/2026-08-19-tenant-email.md

Loading diff…

docs/superpowers/specs/2026-08-19-tenant-email-design.md

Loading diff…

go.mod

Loading diff…

go.sum

Loading diff…

internal/console/apimail.go

Loading diff…

internal/console/apimail_test.go

Loading diff…

internal/console/appsettings.go

Loading diff…

internal/console/appsettings_test.go

Loading diff…

internal/console/deletion_test.go

Loading diff…

internal/console/mailcounts.go

Loading diff…

internal/console/mailcounts_test.go

Loading diff…

internal/console/ownership.go

Loading diff…

internal/console/server.go

Loading diff…

internal/console/sesevents.go

Loading diff…

internal/console/sesevents_test.go

Loading diff…

internal/console/store.go

Loading diff…

internal/console/svcroutes_test.go

Loading diff…

internal/dnsrec/dnsrec.go

Loading diff…

internal/dnsrec/dnsrec_test.go

Loading diff…

internal/dnsrec/route53.go

Loading diff…

internal/dnsrec/route53_test.go

Loading diff…

internal/mail/aws.go

Loading diff…

internal/mail/aws_test.go

Loading diff…

internal/mail/dnsrecords.go

Loading diff…

internal/mail/dnsrecords_test.go

Loading diff…

internal/mail/domain.go

Loading diff…

internal/mail/domain_test.go

Loading diff…

internal/mail/domainclaim.go

Loading diff…

internal/mail/events.go

Loading diff…

internal/mail/events_test.go

Loading diff…

internal/mail/mail.go

Loading diff…

internal/mail/mail_test.go

Loading diff…

internal/mail/memprovisioner.go

Loading diff…

internal/mail/memprovisioner_test.go

Loading diff…

internal/mail/policy.go

Loading diff…

internal/mail/policy_test.go

Loading diff…

internal/mail/provisioner.go

Loading diff…

internal/mail/smtppass.go

Loading diff…

internal/mail/smtppass_test.go

Loading diff…

internal/mail/walk.go

Loading diff…

internal/mail/walk_domain_test.go

Loading diff…

internal/mail/walk_domainclaim_test.go

Loading diff…

internal/mail/walk_enable_test.go

Loading diff…

internal/mail/walk_grant_test.go

Loading diff…

internal/mail/walk_lifecycle_test.go

Loading diff…

internal/mail/walk_verify_test.go

Loading diff…

mail_cli.go

Loading diff…

mail_cli_test.go

Loading diff…

main.go

Loading diff…