feat(senderidentity): classify provisioned SES identities with tags - #972
Merged
Conversation
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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScEpytuD7GvaXEssvJ2W32
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.
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:
e2a-managedsender-identity-v1isManagedIdentityreadse2a-envprod|stagingdeployment_nameconfige2a-purposecustomer|fixturestandard/demovsinternal/system)e2a-createde2a-expirese2a-userSendingIdentityState.Ownere2a-provisionerKeys 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.Envcould not be reused fore2a-env: it is documented asdevelopment/production, the validator rejects anything else, and both prod and staging setproduction. Hence a separatedeployment_name(env overrideE2A_DEPLOYMENT_NAME), unset by default so self-hosters simply get no env tag.tagValueAcceptedscreens every optional value against SES's charset and 256-char cap — without it an operator build string containing#would make SES reject the wholeCreateEmailIdentitycall, turning cosmetic metadata into a provisioning outage.e2a-serverande2a-staging-serveralready holdses:TagResource.AlreadyExistspaths andisManagedIdentityare 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/stagingin the prod and staging configs.🤖 Generated with Claude Code
https://claude.ai/code/session_01ScEpytuD7GvaXEssvJ2W32