feat(amplify-overtone): Idempotent email identities, brand-aligned templates, and CI fixes - #15
Merged
Merged
Conversation
The publish workflow was running `pnpm changeset publish` without a --tag flag for alpha and beta branches. This meant npm dist-tags were not updated correctly — the alpha tag stayed stuck on the first published version instead of advancing with each new pre-release. Derive the dist-tag from the branch name and pass it explicitly so `npm publish --tag alpha` or `--tag beta` is used. Co-Authored-By: Claude <noreply@anthropic.com> https://claude.ai/code/session_015H8yNLg63q5Pb8YQxP75hF
Changeset pre mode already manages the npm dist-tag via pre.json. Passing --tag explicitly conflicts with it, causing the error: "Releasing under custom tag is not allowed in pre mode". Only pass --tag as a fallback when pre mode is not active. Co-Authored-By: Claude <noreply@anthropic.com> https://claude.ai/code/session_015H8yNLg63q5Pb8YQxP75hF
Enforce invariants rather than working around missing state: - main: fail if pre.json exists (must exit pre-release mode first) - alpha/beta branches: fail if pre.json is missing (must enter pre mode) Changeset pre mode handles the dist-tag internally, so the publish step no longer needs to pass --tag explicitly. Co-Authored-By: Claude <noreply@anthropic.com> https://claude.ai/code/session_015H8yNLg63q5Pb8YQxP75hF
…puts
appendToBackendOutputList stores entries under numeric keys ("0", "1"),
producing amplify_outputs.json like {"0": "{\"custom\":{...}}"} instead
of the expected {custom: {email: {...}}}. Since EmailFactory is a
singleton, addBackendOutputEntry is the correct method — it sets the
entry directly without numeric key wrapping.
Also simplify the integration test validator to expect the clean
structure instead of working around the numeric key format.
Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_015H8yNLg63q5Pb8YQxP75hF
…kill Document that versioning happens locally on pre-release branches (CI only runs publish), pre.json is required on alpha/beta branches, and --tag must not be passed when in changeset pre mode. Co-Authored-By: Claude <noreply@anthropic.com> https://claude.ai/code/session_015H8yNLg63q5Pb8YQxP75hF
Deployments fail when SES email identities already exist in the account/region (e.g. shared sandbox recipients or sender addresses created by another stack). Replace the CDK EmailIdentity L2 construct with an IdempotentEmailIdentity custom resource for email-address identities (Mode 1 senders and sandbox recipients). The custom resource calls SESv2 CreateEmailIdentity and treats AlreadyExistsException as success. On delete it calls DeleteEmailIdentity and ignores NotFoundException. Domain identities (Modes 2 & 3) keep the L2 construct because DKIM token intrinsics are needed for DNS record creation. Co-Authored-By: Claude <noreply@anthropic.com> https://claude.ai/code/session_015H8yNLg63q5Pb8YQxP75hF
Add CloudFormation custom resource handler for managing SES email identities. The handler encodes whether an identity pre-existed in the physical resource ID, allowing the Delete handler to skip deletion of pre-existing identities. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…CustomResource Tracks whether SES identities pre-existed and skips deletion on stack teardown if so. Adds explicit onUpdate handler for robustness. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests all lifecycle events (Create, Update, Delete) with aws-sdk-client-mock, including pre-existence detection, no-op updates, conditional deletion, and error propagation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoids collision with other custom output entries under the AWS::Amplify::Custom backend output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tests TypeScript strict indexed access flags `commandCalls()[0]` as possibly undefined. Add optional chaining to satisfy the type checker. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all 4 email templates to use the Overtone brand palette: - CTA buttons: #e8734a (orange) → #A78BFA (lavender accent) - Body text: #333 → #1C1C1C (graphite) - Muted text: #888 → #6B6B6B (secondary text) - Code block background: #f4f4f7 → #FFFFFF with #E5E5E0 border - Page background: #f4f4f7 → #FFFFFF - Footer/header border: #eee → #E5E5E0 - Font stack: add Merriweather Sans as primary Also fix plain text / HTML content parity: - confirmation-code: plain text now includes the intro sentence - password-reset: plain text now includes the full request paragraph - invite: plain text now includes the CTA instruction sentence - getting-started: plain text now includes the full second paragraph Co-Authored-By: Claude <noreply@anthropic.com> https://claude.ai/code/session_015H8yNLg63q5Pb8YQxP75hF
The AWS::Amplify::Custom output schema requires the payload key to be 'customOutputs' — it is enforced by @aws-amplify/backend-output-schemas. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Update handler now always calls CreateEmailIdentity even when the email hasn't changed, re-creating identities that were deleted externally. A DeployToken property forces CloudFormation to trigger Update on every deploy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Combine version-management and release-management into a single versioning-and-releases skill. Update branch model from per-feature alpha/beta branches (alpha/**, beta/**) to single integration branches (alpha, beta). Update publish.yml branch triggers and conditionals to match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nt AccessDenied on replacement During CloudFormation replacements (email property change), the IAM policy is updated to scope to the new email before the Delete handler runs for the old one. A per-identity ARN causes AccessDenied when deleting the old identity. Use identity/* wildcard instead. Co-Authored-By: Claude <noreply@anthropic.com>
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.
Resolves #12
Summary
IdempotentEmailIdentityconstruct backed by a Lambda CustomResource that calls SESv2CreateEmailIdentityand treatsAlreadyExistsExceptionas success. Prevents deployment failures when SES identities already exist in the account/region (e.g. sandbox recipients shared across stacks). On delete, only removes identities this stack originally created.AwsCustomResourcewithNodejsFunction+Provider+CustomResourcefor full lifecycle control (Create/Update/Delete). Encodes ownership in the physical resource ID (ses-identity:{email}:preexisted|created).AmplifyEmailnow usesIdempotentEmailIdentityinstead of the CDKEmailIdentityL2 for both sender identities and sandbox recipients.amplify_outputs.jsonstructure — Switch fromappendToBackendOutputListtoaddBackendOutputEntryand rename output key tocustomEmailOutputsfor a cleaner output structure..changeset/pre.jsonexists onalpha/**/beta/**branches and does not exist onmain.Test plan
pnpm test)pnpm typecheck)pnpm lint)0.3.0-alpha.4)0.3.0-beta.1)🤖 Generated with Claude Code