Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/orange-jokes-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-email/render": patch
---

fix(render): strip NULL bytes from readStream output to prevent email truncation
5 changes: 4 additions & 1 deletion packages/render/src/node/read-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@ export const readStream = async (
});
}

return result;
// Strip NULL bytes (U+0000) that can appear when React's streaming renderer
// produces chunks that split multi-byte UTF-8 characters at chunk boundaries.
// The pretty() function already does this, but the default non-pretty path did not.
return result.replaceAll('\0', '');
};
37 changes: 37 additions & 0 deletions packages/render/src/node/render-node.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,43 @@ describe('render on node environments', () => {
*
* @see https://github.com/resend/react-email/issues/2353
*/
// Regression test for https://github.com/resend/react-email/issues/1667
// and https://github.com/resend/react-email/issues/1932
//
// React's streaming renderer can produce chunks that split multi-byte UTF-8
// characters at chunk boundaries, resulting in NULL bytes (U+0000) in the
// rendered output. Email clients treat NULL as a string terminator, causing
// emails to be truncated mid-content.
it('rendered output contains no NULL bytes with multi-byte characters', async () => {
const MultiByteTemplate = () => {
const paragraphs = Array(30)
.fill(null)
.map((_, i) => (
<p key={i}>
段落{i}:日本語のテキストを含むメールテンプレートのテストです。
Ärzte und Ünternehmen für Lösung und Grüße aus München. Тестовая
компания приветствует вас в нашем сервисе. 이메일 템플릿 테스트를
위한 한국어 텍스트입니다.
</p>
));

return (
<div>
<h1>テスト会社ABCははハハtestあ</h1>
{paragraphs}
</div>
);
};

const html = await render(<MultiByteTemplate />);

expect(html).not.toContain('\0');
expect(html).toContain('テスト会社ABCははハハtestあ');
expect(html).toContain('日本語のテキスト');
expect(html).toContain('Ünternehmen');
expect(html).toContain('Тестовая');
});

it('renders large emails without hydration markers', async () => {
const LargeEmailTemplate = () => {
const largeContent = Array(100)
Expand Down
Loading