Skip to content

fix(preview-server): update existing Resend templates instead of creating duplicates#3278

Open
tazwar9t62 wants to merge 7 commits intoresend:canaryfrom
tazwar9t62:fix/3194-resend-template-upsert
Open

fix(preview-server): update existing Resend templates instead of creating duplicates#3278
tazwar9t62 wants to merge 7 commits intoresend:canaryfrom
tazwar9t62:fix/3194-resend-template-upsert

Conversation

@tazwar9t62
Copy link
Copy Markdown

@tazwar9t62 tazwar9t62 commented Apr 15, 2026

Summary

Fixes #3194.

The Resend integration in the preview server always created a new template on every upload. That made repeated uploads produce duplicate templates instead of updating the original one.

This PR changes the upload flow to behave like an upsert:

  • use a deterministic Resend alias per template
  • update an existing template when a match is found
  • only create a new template when no existing template can be matched

Changes

  • added a dedicated Resend template upsert helper in packages/preview-server/src/utils/resend-template-upsert.ts
  • added focused tests for alias generation and matching behavior in packages/preview-server/src/utils/resend-template-upsert.spec.ts
  • updated packages/preview-server/src/actions/export-single-template.ts to use the upsert flow instead of calling resend.templates.create() directly
  • updated packages/preview-server/src/components/toolbar/resend.tsx so both single upload and bulk upload use the same normalized template identity
  • updated the toolbar messaging so the UI distinguishes between template creation and template update

Why

Previously the preview server always called resend.templates.create(), so every upload created a new dashboard template.

This implementation introduces a stable alias for each template and uses it to find and update the same template on future uploads. It also includes a fallback for older templates created before aliases were used, including older single-upload names that included the file extension.

That keeps new uploads stable while still allowing existing templates to be migrated into the new flow.

Verification

Ran:

  • pnpm --filter @react-email/preview-server exec vitest run src/utils/resend-template-upsert.spec.ts
  • pnpm exec biome check packages/preview-server/src/utils/resend-template-upsert.ts packages/preview-server/src/utils/resend-template-upsert.spec.ts packages/preview-server/src/actions/export-single-template.ts packages/preview-server/src/components/toolbar/resend.tsx

Results:

  • focused tests passed
  • formatting and lint checks passed on touched files

Note:

  • pnpm --filter @react-email/preview-server exec tsc --noEmit still reports existing package-wide type resolution issues unrelated to this patch

Summary by cubic

Prevents duplicate Resend templates by upserting on upload. Uses a stable alias and a paginated lookup to update existing templates, and updates the toolbar to show when a template was created vs updated.

  • Bug Fixes
    • Added upsertResendTemplate to generate a deterministic alias and update existing templates; only creates when no match is found (with fallback to legacy names, including those with extensions).
    • Implemented paginated template lookup and selected the oldest exact name match when the alias isn’t present; tightened result narrowing for CI.
    • Switched single and bulk uploads to use normalized template names via removeFilenameExtension and the upsert flow; pass legacyName for migration.
    • Updated UI messaging to distinguish “created” vs “updated” and surfaced the operation in results; added focused tests for alias generation, matching, and pagination.

Written for commit aebd54e. Summary will update on new commits.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 15, 2026

@tazwar9t62 is attempting to deploy a commit to the resend Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 15, 2026

🦋 Changeset detected

Latest commit: aebd54e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@react-email/preview-server Patch
react-email Patch
@react-email/editor Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 15, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@react-email/preview-server@3278

commit: aebd54e

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 4 files

Confidence score: 3/5

  • There is a concrete production risk in packages/preview-server/src/utils/resend-template-upsert.ts: new SDK calls (templates.get, templates.list, templates.update) may fail if current Resend API keys lack the required permissions, which could break template upsert behavior at runtime.
  • The fallback lookup in packages/preview-server/src/utils/resend-template-upsert.ts only inspects the first 100 templates; without pagination, larger accounts may miss existing templates and create duplicates.
  • Given one high-severity, high-confidence operational risk plus a medium data-consistency risk, this looks mergeable with caution rather than a low-risk merge.
  • Pay close attention to packages/preview-server/src/utils/resend-template-upsert.ts - verify API key scopes for new template operations and add pagination to prevent duplicate template creation.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/preview-server/src/utils/resend-template-upsert.ts">

<violation number="1" location="packages/preview-server/src/utils/resend-template-upsert.ts:162">
P1: Custom agent: **API Key Permission Check SDK Methods**

This change introduces new Resend SDK operations (`templates.get`, `templates.list`, `templates.update`). Confirm production API keys include permissions for template read/update operations, not just create, to prevent post-deploy permission failures.</violation>

<violation number="2" location="packages/preview-server/src/utils/resend-template-upsert.ts:188">
P2: Fallback lookup only checks the first 100 templates with no pagination, so existing templates outside that page won’t be found and a duplicate can be created for large accounts.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

resend,
}: UpsertResendTemplateOptions): Promise<UpsertResendTemplateResult> => {
const alias = getResendTemplateAlias(name);
const existingTemplateResponse = await resend.templates.get(alias);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Custom agent: API Key Permission Check SDK Methods

This change introduces new Resend SDK operations (templates.get, templates.list, templates.update). Confirm production API keys include permissions for template read/update operations, not just create, to prevent post-deploy permission failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/preview-server/src/utils/resend-template-upsert.ts, line 162:

<comment>This change introduces new Resend SDK operations (`templates.get`, `templates.list`, `templates.update`). Confirm production API keys include permissions for template read/update operations, not just create, to prevent post-deploy permission failures.</comment>

<file context>
@@ -0,0 +1,250 @@
+  resend,
+}: UpsertResendTemplateOptions): Promise<UpsertResendTemplateResult> => {
+  const alias = getResendTemplateAlias(name);
+  const existingTemplateResponse = await resend.templates.get(alias);
+
+  if (existingTemplateResponse.data?.id) {
</file context>

Comment thread packages/preview-server/src/utils/resend-template-upsert.ts Outdated
Comment thread .changeset/four-zebras-hear.md Outdated
@tazwar9t62 tazwar9t62 requested a review from gabrielmfern April 15, 2026 10:33
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.

Resend integration always creates duplicate templates instead of updating existing ones

2 participants