Skip to content

feat(senderidentity): classify provisioned SES identities with tags - #972

Merged
jiashuoz merged 2 commits into
mainfrom
feat/ses-identity-tags
Aug 30, 2026
Merged

feat(senderidentity): classify provisioned SES identities with tags#972
jiashuoz merged 2 commits into
mainfrom
feat/ses-identity-tags

Conversation

@jiashuoz

Copy link
Copy Markdown
Member

Groundwork for automatic cleanup of leaked sender identities. Today every e2a-provisioned SES identity carries exactly one tag, e2a-managed: sender-identity-v1, so "is this safe to delete?" can only be answered by joining against this server's Postgres ledger. Prod and staging share one AWS account and region, which means nothing in AWS distinguishes a staging test fixture from a customer's production sending domain.

Adds six classification tags at create time:

Key Value Derived from
e2a-managed sender-identity-v1 unchanged — still the ownership anchor isManagedIdentity reads
e2a-env prod | staging new optional deployment_name config
e2a-purpose customer | fixture owner's account class (standard/demo vs internal/system)
e2a-created RFC3339 UTC provider clock — SES reports no creation time, so age is otherwise unknowable from AWS
e2a-expires RFC3339 UTC fixtures only: created + TTL. A customer identity never gets one
e2a-user user id SendingIdentityState.Owner
e2a-provisioner build string which code wrote these tags

Keys and value vocabularies are hardcoded constants, because the writer here and the future reaper that will be granted delete authority on the strength of these tags must be unable to drift apart. Every value is derived at runtime.

Tagging fails open; deletion will fail closed. An underivable value omits its one tag and never fails a provision — safe only because a missing tag must make the future reaper leave the identity alone. Worst case is an identity a human has to classify, never one deleted by mistake.

Notes:

  • cfg.Env could not be reused for e2a-env: it is documented as development/production, the validator rejects anything else, and both prod and staging set production. Hence a separate deployment_name (env override E2A_DEPLOYMENT_NAME), unset by default so self-hosters simply get no env tag.
  • tagValueAccepted screens every optional value against SES's charset and 256-char cap — without it an operator build string containing # would make SES reject the whole CreateEmailIdentity call, turning cosmetic metadata into a provisioning outage.
  • No IAM change needed: both e2a-server and e2a-staging-server already hold ses:TagResource.
  • Adoption/AlreadyExists paths and isManagedIdentity are untouched. No reaper/deprovision behavior changes — that is the follow-up PR.

Tests: tags_test.go (12-case table over the full tag map, plus adoption-path, worker-stamping, lookup-failure, and charset cases) and two config cases, written failing first. go build ./... clean, go test ./... green repo-wide, gofmt clean.

Companion ops change (one line per file, after this merges): deployment_name: prod / staging in the prod and staging configs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ScEpytuD7GvaXEssvJ2W32

jiashuoz and others added 2 commits August 29, 2026 20:54
Deciding whether an SES sending identity is safe to delete currently
requires a correct join against this server's Postgres ledger. That is
exactly the join a cleanup pass cannot make when it is pointed at an AWS
account whose database is gone, restored from a stale backup, or simply
not the one that provisioned the identity — and getting it wrong deletes
a paying customer's sending identity. Stamp the answer onto the resource
itself instead, so the question is answerable from AWS alone.

Every created identity now carries, alongside the unchanged e2a-managed
ownership anchor: e2a-env (which deployment), e2a-purpose (customer vs.
fixture, derived from the owner's usage account class), e2a-created,
e2a-expires (fixtures only — there is no time after which deleting a
customer's identity is correct), e2a-user, and e2a-provisioner.

Keys and value vocabularies are hardcoded closed enums so the writer here
and the reaper that will later read them cannot drift apart. Values are
all derived at runtime, and construction is best-effort: a value that
cannot be derived — no deployment name configured, a failed account-class
lookup, an ownerless ledger row, a build string SES would reject — omits
that one tag and never fails a provision. Tagging fails OPEN because the
deletion decision it feeds fails CLOSED: an untagged identity is one the
reaper must leave alone, so a dropped tag costs a human classification,
not a deleted customer domain.

Adoption is untouched and still writes the ownership tag alone —
back-dating a creation stamp onto an identity e2a did not create would be
inventing the evidence the reaper is meant to trust. This PR grants no
delete authority; that lands separately.

e2a-env needs a new config field: `env` is a development/production mode
switch and both the hosted prod and staging deployments run
env: "production", so it provably cannot tell them apart. An unrecognized
deployment_name logs once and reads as unset — nothing about serving mail
depends on it, so a typo must not stop a boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScEpytuD7GvaXEssvJ2W32
@jiashuoz
jiashuoz merged commit 4b727b2 into main Aug 30, 2026
29 checks passed
@jiashuoz
jiashuoz deleted the feat/ses-identity-tags branch August 30, 2026 04:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant