Skip to content

Commit c2d8680

Browse files
docs: add comprehensive investigation of template HTML transformation issue
- Document root cause: dashboard auto-saves React Email transformed HTML - Identify source: React Email components add data-id attributes - Create Python reproduction script matching Node.js investigation - Propose 4 solutions with recommended fix - Confirm SDK is working correctly, issue is platform-side Related: #186, #187, PRODUCT-1427 Co-authored-by: Danilo Woznica <[email protected]>
1 parent f10bf39 commit c2d8680

3 files changed

Lines changed: 680 additions & 0 deletions

File tree

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
# Investigation: Template HTML Transformation and Content Duplication
2+
3+
**Related Issues:**
4+
- [resend-python#186](https://github.com/resend/resend-python/issues/186) - Templates.create() transforms raw HTML
5+
- [resend-python#187](https://github.com/resend/resend-python/issues/187) - Templates.update() appends HTML instead of replacing it
6+
- Linear: PRODUCT-1427 - Opening template rewrites HTML and duplicates content
7+
8+
**Investigation Date:** February 20, 2026
9+
10+
## Summary
11+
12+
The Resend dashboard template editor processes raw HTML through a React Email rendering pipeline and persists the transformed result back to storage **every time a template is opened**, even when no edits are made. This causes:
13+
14+
1. **HTML Transformation:** Original HTML gets bloated with React Email artifacts (data-id attributes, HTML comments, inline styles)
15+
2. **Content Duplication:** Subsequent updates via API cause content to be duplicated in the editor
16+
3. **Unexpected Behavior:** HTML sent via API differs from what's retrieved after dashboard viewing
17+
18+
## Root Cause Analysis
19+
20+
### The Problem Flow
21+
22+
1. **Create Template via API** (3,267 chars of HTML)
23+
```python
24+
resend.Templates.create({
25+
"name": "test-template",
26+
"html": "<html>...</html>" # Raw HTML
27+
})
28+
```
29+
30+
2. **GET /templates/{id}** → Returns identical HTML (3,267 chars) ✅
31+
32+
3. **Open Template in Dashboard** (no edits, just view it)
33+
- Dashboard renders HTML through React Email pipeline
34+
- React Email components add artifacts:
35+
- `data-id="__react-email-column"` attributes
36+
- `<!--$-->` and `<!--/$-->` comment markers
37+
- Injected inline styles
38+
- Transformed HTML structure
39+
- **Dashboard saves transformed HTML back to storage**
40+
41+
4. **GET /templates/{id}** → HTML now ~21,000+ chars ❌
42+
43+
5. **Update via API with original HTML**
44+
```python
45+
resend.Templates.update({
46+
"id": template_id,
47+
"html": "<html>...</html>" # Same original HTML
48+
})
49+
```
50+
51+
6. **Open Dashboard Again** → Content appears duplicated ❌
52+
53+
### Source of Artifacts
54+
55+
Investigation of the [react-email](https://github.com/resend/react-email) repository reveals React Email components hardcode `data-id` attributes:
56+
57+
**packages/column/src/column.tsx:**
58+
```typescript
59+
export const Column = React.forwardRef<HTMLTableCellElement, ColumnProps>(
60+
({ children, style, ...props }, ref) => {
61+
return (
62+
<td {...props} data-id="__react-email-column" ref={ref} style={style}>
63+
{children}
64+
</td>
65+
);
66+
},
67+
);
68+
```
69+
70+
**packages/markdown/src/markdown.tsx:**
71+
```typescript
72+
<div data-id="react-email-markdown">
73+
{/* content */}
74+
</div>
75+
```
76+
77+
When raw HTML is rendered through React Email (even just for viewing), these components inject their attributes and transform the HTML structure.
78+
79+
## Evidence
80+
81+
### Reproduction Script
82+
83+
A [Node.js reproduction script](https://gist.github.com/drish/5352959c0dca9abd96141b301ea317a1) demonstrates:
84+
85+
- API behavior is correct (PATCH replaces HTML exactly as sent)
86+
- Transformation only occurs after opening template in dashboard
87+
- Issue affects both Python SDK and Node.js SDK identically
88+
- **Conclusion: Platform/dashboard issue, not SDK-specific**
89+
90+
### Example Transformation
91+
92+
**Original HTML:**
93+
```html
94+
<table>
95+
<tr>
96+
<td>Cell 1</td>
97+
<td>Cell 2</td>
98+
</tr>
99+
</table>
100+
```
101+
102+
**After Dashboard View:**
103+
```html
104+
<!--$--><table>
105+
<tr>
106+
<td data-id="__react-email-column" style="...">Cell 1</td>
107+
<td data-id="__react-email-column" style="...">Cell 2</td>
108+
</tr>
109+
</table><!--/$-->
110+
```
111+
112+
## Impact
113+
114+
### User-Reported Problems
115+
116+
1. **HTML Bloat:** Templates grow 6-7x in size after dashboard viewing
117+
2. **Content Duplication:** Updates cause content to appear multiple times
118+
3. **Unexpected Behavior:** HTML sent via API ≠ HTML retrieved after dashboard interaction
119+
4. **Loss of Control:** Users lose ability to maintain exact HTML structure
120+
121+
### Affected Users
122+
123+
- Users creating templates via API with custom HTML
124+
- Users expecting API to preserve exact HTML formatting
125+
- Users with CSS that relies on specific HTML structure
126+
- Users with dark mode or complex styling (as reported in issues)
127+
128+
## Proposed Solutions
129+
130+
### Solution 1: Prevent Auto-Save on View (Recommended)
131+
132+
**Location:** Dashboard template editor code (private repository)
133+
134+
**Fix:** Only persist HTML changes when user explicitly saves, not on template view/load.
135+
136+
```typescript
137+
// Current (problematic):
138+
function onTemplateLoad(template) {
139+
const transformed = renderThroughReactEmail(template.html);
140+
saveToStorage(transformed); // ❌ Don't do this
141+
displayInEditor(transformed);
142+
}
143+
144+
// Proposed:
145+
function onTemplateLoad(template) {
146+
const transformed = renderThroughReactEmail(template.html);
147+
displayInEditor(transformed); // ✅ Only display, don't save
148+
}
149+
150+
function onUserSave(editorContent) {
151+
saveToStorage(editorContent); // ✅ Only save on explicit user action
152+
}
153+
```
154+
155+
**Benefits:**
156+
- Preserves original HTML until user makes intentional changes
157+
- Fixes both transformation and duplication issues
158+
- No SDK changes required
159+
160+
### Solution 2: Add "plainText" Mode to Dashboard
161+
162+
**Location:** Dashboard template editor code
163+
164+
**Fix:** When loading templates created via API (not through React Email editor), display in plain HTML mode without React Email transformation.
165+
166+
```typescript
167+
function onTemplateLoad(template) {
168+
if (template.createdViaAPI && !template.usesReactEmailComponents) {
169+
displayAsPlainHTML(template.html); // No transformation
170+
} else {
171+
const transformed = renderThroughReactEmail(template.html);
172+
displayInEditor(transformed);
173+
}
174+
}
175+
```
176+
177+
**Benefits:**
178+
- Preserves API-created templates
179+
- Allows React Email templates to work as expected
180+
- Clear distinction between template types
181+
182+
### Solution 3: Add Render Options to React Email
183+
184+
**Location:** react-email repository
185+
186+
**Fix:** Add option to skip adding `data-id` attributes during rendering.
187+
188+
```typescript
189+
export const Column = React.forwardRef<HTMLTableCellElement, ColumnProps>(
190+
({ children, style, includeDataId = true, ...props }, ref) => {
191+
const dataIdProp = includeDataId ? { 'data-id': '__react-email-column' } : {};
192+
return (
193+
<td {...props} {...dataIdProp} ref={ref} style={style}>
194+
{children}
195+
</td>
196+
);
197+
},
198+
);
199+
```
200+
201+
**Benefits:**
202+
- Allows clean HTML output when needed
203+
- Maintains editor functionality when attributes are included
204+
- Flexible for different use cases
205+
206+
**Drawbacks:**
207+
- Requires changes across multiple React Email components
208+
- May affect editor features that rely on these attributes
209+
210+
### Solution 4: Template Versioning
211+
212+
**Location:** Dashboard backend
213+
214+
**Fix:** Store both original and transformed versions, serve appropriate version based on request source.
215+
216+
```typescript
217+
interface Template {
218+
id: string;
219+
name: string;
220+
originalHtml: string; // Preserved as submitted via API
221+
transformedHtml?: string; // For dashboard editor use
222+
// ...
223+
}
224+
```
225+
226+
**Benefits:**
227+
- Preserves original HTML for API consumers
228+
- Allows dashboard to work with transformed version
229+
- Non-breaking change
230+
231+
**Drawbacks:**
232+
- Increased storage requirements
233+
- More complex synchronization logic
234+
235+
## Recommended Action Plan
236+
237+
1. **Immediate Fix (Critical):** Implement Solution 1 - Stop auto-saving transformed HTML on template view
238+
2. **Short-term:** Implement Solution 2 - Add plain HTML mode for API-created templates
239+
3. **Long-term:** Consider Solution 4 - Template versioning for robust handling of both API and UI workflows
240+
241+
## SDK Considerations
242+
243+
The Python SDK (and all other SDKs) are working correctly. No SDK changes are required. The issue is entirely in the dashboard/backend platform code.
244+
245+
### Workaround for Users (Until Fix)
246+
247+
Currently, users should:
248+
1. Create/update templates via API
249+
2. **Avoid opening templates in dashboard** if HTML preservation is critical
250+
3. Use the Resend API directly for all template operations
251+
252+
## Testing Recommendations
253+
254+
After implementing fixes, verify:
255+
256+
1. ✅ Template created via API → HTML unchanged after dashboard view
257+
2. ✅ Template updated via API → No content duplication
258+
3. ✅ Template created in dashboard → React Email features work correctly
259+
4. ✅ Template edited in dashboard → Changes saved correctly
260+
5. ✅ API GET returns exact HTML that was PUT/PATCH
261+
262+
## Related Code Repositories
263+
264+
- **resend-python:** SDK confirmed working correctly
265+
- **resend-node:** SDK confirmed working correctly
266+
- **react-email:** Contains components that add data-id attributes
267+
- **resend-openapi:** API specification (does not indicate transformation behavior)
268+
- **Dashboard (private):** Contains problematic auto-save logic
269+
270+
## Verification
271+
272+
To verify the fix works:
273+
274+
```bash
275+
# Run the reproduction script from the gist
276+
node reproduction-script.js
277+
278+
# Expected results after fix:
279+
# - Original HTML length: 3267 chars
280+
# - After dashboard view: 3267 chars (unchanged)
281+
# - After update: 3267 chars (unchanged)
282+
# - HTML identical to original: true (always)
283+
```
284+
285+
## Contributors
286+
287+
- **Investigation:** João (drish) - identified root cause
288+
- **Reproduction:** Node.js script demonstrating issue
289+
- **Analysis:** Cursor AI Agent - traced issue to React Email component attributes and dashboard auto-save behavior

TEMPLATE_ISSUE_README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Template HTML Transformation Issue
2+
3+
## Quick Links
4+
5+
- **Full Investigation Report:** [INVESTIGATION_TEMPLATE_HTML_TRANSFORMATION.md](./INVESTIGATION_TEMPLATE_HTML_TRANSFORMATION.md)
6+
- **Python Reproduction Script:** [examples/investigation_template_transformation.py](./examples/investigation_template_transformation.py)
7+
- **Related Issues:** [#186](https://github.com/resend/resend-python/issues/186), [#187](https://github.com/resend/resend-python/issues/187)
8+
9+
## TL;DR
10+
11+
**Problem:** Opening a template in the Resend dashboard causes the HTML to be transformed through React Email and saved back to storage, even when no edits are made. This results in:
12+
- HTML bloat (6-7x size increase)
13+
- Content duplication on subsequent updates
14+
- Loss of exact HTML formatting
15+
16+
**Root Cause:** Dashboard auto-saves React Email-transformed HTML (with `data-id` attributes and comments) instead of preserving the original HTML.
17+
18+
**Status:** Confirmed platform issue, not an SDK bug. The Python SDK is working correctly.
19+
20+
**Workaround:** Avoid opening templates in the dashboard if HTML preservation is critical. Use API-only operations.
21+
22+
## Running the Investigation
23+
24+
To reproduce the issue yourself:
25+
26+
```bash
27+
export RESEND_API_KEY="re_your_api_key"
28+
python examples/investigation_template_transformation.py
29+
```
30+
31+
The script will:
32+
1. Create a template with complex HTML (3,267 chars)
33+
2. Verify HTML is preserved after creation
34+
3. Prompt you to open the template in dashboard
35+
4. Show HTML has been transformed (~21,000+ chars)
36+
5. Demonstrate content duplication on updates
37+
38+
## Proposed Solutions
39+
40+
See the [full investigation report](./INVESTIGATION_TEMPLATE_HTML_TRANSFORMATION.md#proposed-solutions) for detailed solutions, including:
41+
42+
1. **Stop auto-saving transformed HTML** (recommended immediate fix)
43+
2. **Add plain HTML mode** for API-created templates
44+
3. **Make React Email data-id attributes optional**
45+
4. **Template versioning** to store both original and transformed HTML
46+
47+
## For Resend Team
48+
49+
The fix needs to be implemented in the dashboard/platform code (likely a private repository). The specific area to investigate:
50+
51+
```typescript
52+
// Dashboard template editor (pseudo-code)
53+
function onTemplateLoad(template) {
54+
const transformed = renderThroughReactEmail(template.html);
55+
saveToStorage(transformed); // ❌ This line is the problem
56+
displayInEditor(transformed);
57+
}
58+
```
59+
60+
Should be:
61+
62+
```typescript
63+
function onTemplateLoad(template) {
64+
const transformed = renderThroughReactEmail(template.html);
65+
displayInEditor(transformed); // ✅ Display only, don't auto-save
66+
}
67+
```
68+
69+
## SDK Impact
70+
71+
**No SDK changes are required.** All SDKs (Python, Node.js, etc.) are functioning correctly. The issue is entirely in the platform/dashboard layer.

0 commit comments

Comments
 (0)