Skip to content

feat(amplify-overtone): Idempotent email identities, brand-aligned templates, and CI fixes - #15

Merged
cabcookie merged 24 commits into
alphafrom
claude/fix-npm-dist-alpha-8J01M
Apr 8, 2026
Merged

cabcookie merged 24 commits into
alphafrom
claude/fix-npm-dist-alpha-8J01M

Conversation

@cabcookie

@cabcookie cabcookie commented Apr 5, 2026 •

Copy link
Copy Markdown
Member

Resolves #12

Summary

  • Idempotent email identity creation — New IdempotentEmailIdentity construct backed by a Lambda CustomResource that calls SESv2 CreateEmailIdentity and treats AlreadyExistsException as 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.
  • Lambda-backed CustomResource — Replaces AwsCustomResource with NodejsFunction + Provider + CustomResource for full lifecycle control (Create/Update/Delete). Encodes ownership in the physical resource ID (ses-identity:{email}:preexisted|created).
  • Construct wiring — AmplifyEmail now uses IdempotentEmailIdentity instead of the CDK EmailIdentity L2 for both sender identities and sandbox recipients.
  • Email template brand alignment — Update all four default templates (confirmation-code, password-reset, invite, getting-started) and the base renderer to match Overtone brand guidelines (colors, typography, spacing).
  • Fix amplify_outputs.json structure — Switch from appendToBackendOutputList to addBackendOutputEntry and rename output key to customEmailOutputs for a cleaner output structure.
  • CI pre-release validation — Add a guard step to the publish workflow that validates .changeset/pre.json exists on alpha/**/beta/** branches and does not exist on main.

Test plan

  • All 65 unit/construct tests pass (pnpm test)
  • Typecheck clean (pnpm typecheck)
  • Lint clean (pnpm lint)
  • Alpha published successfully (0.3.0-alpha.4)
  • Beta published successfully (0.3.0-beta.1)
  • Deploy a stack with pre-existing SES identities to verify idempotent creation
  • Verify stack teardown preserves pre-existing identities

🤖 Generated with Claude Code

claude added 7 commits April 5, 2026 10:31
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
@cabcookie cabcookie changed the title fix(ci): Pass --tag flag to changeset publish for pre-releases fix(amplify-overtone): Idempotent email identities, correct custom outputs, and CI pre-release validation Apr 6, 2026
cabcookie and others added 6 commits April 6, 2026 19:19
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>

@cabcookie cabcookie left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

cabcookie and others added 4 commits April 8, 2026 11:07
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
@cabcookie cabcookie changed the title fix(amplify-overtone): Idempotent email identities, correct custom outputs, and CI pre-release validation feat(amplify-overtone): Idempotent email identities, brand-aligned templates, and CI fixes Apr 8, 2026

@cabcookie cabcookie left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

cabcookie and others added 2 commits April 8, 2026 12:21
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>

@cabcookie cabcookie left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

cabcookie and others added 2 commits April 8, 2026 13:06
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>

@cabcookie cabcookie left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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>
@cabcookie
cabcookie changed the base branch from main to alpha April 8, 2026 12:54
…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>

@cabcookie cabcookie left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@cabcookie
cabcookie merged commit 7ba7392 into alpha Apr 8, 2026
@cabcookie
cabcookie deleted the claude/fix-npm-dist-alpha-8J01M branch April 8, 2026 13:36
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.

Improve email templates

2 participants