fix(preview-server): update existing Resend templates instead of creating duplicates#3278
fix(preview-server): update existing Resend templates instead of creating duplicates#3278tazwar9t62 wants to merge 7 commits intoresend:canaryfrom
Conversation
|
@tazwar9t62 is attempting to deploy a commit to the resend Team on Vercel. A member of the Team first needs to authorize it. |
🦋 Changeset detectedLatest commit: aebd54e The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
commit: |
There was a problem hiding this comment.
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.tsonly 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); |
There was a problem hiding this comment.
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>
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:
Changes
packages/preview-server/src/utils/resend-template-upsert.tspackages/preview-server/src/utils/resend-template-upsert.spec.tspackages/preview-server/src/actions/export-single-template.tsto use the upsert flow instead of callingresend.templates.create()directlypackages/preview-server/src/components/toolbar/resend.tsxso both single upload and bulk upload use the same normalized template identityWhy
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.tspnpm 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.tsxResults:
Note:
pnpm --filter @react-email/preview-server exec tsc --noEmitstill reports existing package-wide type resolution issues unrelated to this patchSummary 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.
upsertResendTemplateto generate a deterministic alias and update existing templates; only creates when no match is found (with fallback to legacy names, including those with extensions).removeFilenameExtensionand the upsert flow; passlegacyNamefor migration.operationin results; added focused tests for alias generation, matching, and pagination.Written for commit aebd54e. Summary will update on new commits.