From adb71c7f60240d74dbc448fcd8ccf627e44d0cb0 Mon Sep 17 00:00:00 2001 From: Yufei Kang Date: Thu, 12 Feb 2026 12:13:49 +0900 Subject: [PATCH 1/2] fix(render): strip NULL bytes from readStream output to prevent email truncation --- packages/render/src/node/read-stream.ts | 5 ++- packages/render/src/node/render-node.spec.tsx | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/render/src/node/read-stream.ts b/packages/render/src/node/read-stream.ts index d58e59b8e8..8c7219a8af 100644 --- a/packages/render/src/node/read-stream.ts +++ b/packages/render/src/node/read-stream.ts @@ -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', ''); }; diff --git a/packages/render/src/node/render-node.spec.tsx b/packages/render/src/node/render-node.spec.tsx index fd9ecb455a..65f8a4d30e 100644 --- a/packages/render/src/node/render-node.spec.tsx +++ b/packages/render/src/node/render-node.spec.tsx @@ -150,6 +150,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) => ( +

+ 段落{i}:日本語のテキストを含むメールテンプレートのテストです。 + Ärzte und Ünternehmen für Lösung und Grüße aus München. Тестовая + компания приветствует вас в нашем сервисе. 이메일 템플릿 테스트를 + 위한 한국어 텍스트입니다. +

+ )); + + return ( +
+

テスト会社ABCははハハtestあ

+ {paragraphs} +
+ ); + }; + + const html = await render(); + + 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) From cccf6c36a89f7ece53b76fb6477cd73b47bf9a3a Mon Sep 17 00:00:00 2001 From: kang Date: Thu, 12 Feb 2026 13:29:10 +0900 Subject: [PATCH 2/2] Strip NULL bytes from readStream output Fixes the readStream output by stripping NULL bytes to prevent email truncation. --- .changeset/orange-jokes-juggle.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/orange-jokes-juggle.md diff --git a/.changeset/orange-jokes-juggle.md b/.changeset/orange-jokes-juggle.md new file mode 100644 index 0000000000..1f99c3a3c2 --- /dev/null +++ b/.changeset/orange-jokes-juggle.md @@ -0,0 +1,5 @@ +--- +"@react-email/render": patch +--- + +fix(render): strip NULL bytes from readStream output to prevent email truncation