render: unstableToPlainText ignores mixed-case HTML attributes
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- node.js, react, typescript
- Domain
- backend
Research direction
Start by reading the shared conversion boundary and the lookups for href, start, and data-skip-in-text; the issue does not name file paths or tests. Normalize attribute names while preserving their values, then verify mixed-case attributes match lowercase behavior for links, ordered lists, and skip annotations. Done when those cases preserve the URL, numbering, and skipped text as expected.
Written by the indexing model from the issue text.
Description
What versions are you using? (if relevant)
Latest stable @react-email/render 2.2.0 and current canary branch 674d8ec2804b86be5d99156383fb1810179847bf both reproduce, with html5parser 3.0.0 and Node 24.13.0 (also reproduced on 22.16.0).
I also checked the published canary dist-tag, which currently resolves to 2.0.7-canary.0. That version predates unstableToPlainText and has no such export, so this feature-specific test is not applicable there. Its default converter handles the sample inputs correctly.
Describe the Bug
unstableToPlainText recognizes attribute names only when they are lowercase. Valid mixed-case HTML attributes lose their behavior: link URLs disappear from plain text, ordered-list start offsets become 1, and data-skip-in-text is not recognized. The default toPlainText handles these inputs correctly.
What is affected (leave empty if unsure)
Render Utility.
Link to the code that reproduces this issue
Self-contained reproduction below uses the public @react-email/render API; no provider credentials or email delivery are needed.
To Reproduce
Save this as repro.mjs:
import * as renderApi from '@react-email/render';
if (typeof renderApi.unstableToPlainText !== 'function') {
console.log('unstableToPlainText is not available in this version');
}
const cases = [
'<a HrEf="https://example.com/pay">Pay invoice</a>',
'<ol StArT="5"><li>Five</li><li>Six</li></ol>',
'<span DATA-SKIP-IN-TEXT="true">Preview</span>Body',
];
for (const html of cases) {
console.log(JSON.stringify(renderApi.toPlainText(html)));
if (typeof renderApi.unstableToPlainText === 'function') {
console.log(JSON.stringify(renderApi.unstableToPlainText(html)));
}
}
Run in a disposable directory:
npm init -y
npm install --save-exact @react-email/[email protected] [email protected] [email protected]
node repro.mjs
npm install --save-exact @react-email/[email protected]
node repro.mjs
Actual pairs on 2.2.0 (the older published canary prints the availability notice and stable results only):
"Pay invoice https://example.com/pay"
"Pay invoice"
" 5. Five\n 6. Six"
" 1. Five\n 2. Six"
"Body"
"PreviewBody"
Lowercasing only the attribute names makes the two converters agree in all three examples. The missing URL also occurs through render() with plainText: true, unstableTextConversion: true when a component includes the same HTML through dangerouslySetInnerHTML. Existing React-comment text in that rendering path is the separate issue #3864, not part of this report.
Expected Behavior
Attribute names are case-insensitive in HTML. These inputs should behave like their lowercase equivalents, preserving the URL, list numbering and skip annotation. Attribute values should keep their existing decoding/case behavior.
The parser preserves attribute names in its attributeMap, while the converter looks up only href, start and data-skip-in-text. Normalizing attribute names at the shared conversion boundary appears sufficient; no table-separator or default-converter change is needed. Attribute values should not be lowercased.
What's your node version? (if relevant)
24.13.0; also reproduced on 22.16.0.
Prepared with Codex using GPT-6.1 Sol at high reasoning.
- Dominant language
- TypeScript
- Stars
- 19.9k
- Forks
- 1.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 49
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from resend/react-email
-
render: unstableTextConversion outputs HTML comments and repeats link URLs in plain textPossibly taken @lakshya-dhariwal claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
resend/react-email#3864 ·
Maintainers usually reply within 1 day
-
<CodeInline> causes duplicated text in body previews in Gmail and other providers due to hacky Orange.fr workaroundPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
resend/react-email#3853 ·
Maintainers usually reply within 1 day
-
Markdown: HTML inside code blocks and inline code isn't escapedPossibly taken @KishanKanejiya23 claimed this 11 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
resend/react-email#3814 ·
Maintainers usually reply within 1 day
-
Tailwind stacked media variants leave nested CSS in rendered emailsPossibly taken @lakshya-dhariwal claimed this today. Open
Difficulty 3/5 1-2 days Newbie friendliness 35/100
resend/react-email#3874 ·
Maintainers usually reply within 1 day
-
Inspector Text panel omits Uppercase mark toggle (MARK_NAMES missing "uppercase")Possibly taken @svargas-dev claimed this 6 days ago. Open
Difficulty 1/5 Under an hour Newbie friendliness 22/100
resend/react-email#3849 ·
Maintainers usually reply within 1 day
All issues in resend/react-email
Similar issues
-
documentation good first issue roadmap
Difficulty 1/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
siyuan-note/siyuan#21324 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
dmno-dev/varlock#1279 · 1 comment ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
himynameisdave/bsky-profanity-counter#59 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100