Debugging React Email & JSX Email: Common Build & Rendering Bugs
React Email and its fork @jsx-email have made building HTML emails with JSX dramatically easier. But the abstractions introduce a class of bugs that are uniquely painful to debug: errors that only appear in specific email clients, build warnings with no clear stack trace, and layout regressions that look correct in the preview server but break silently in Outlook or Gmail. This guide catalogues the four most common categories, with exact reproduction cases and patches, grounded in real GSC query signals showing developers are actively searching for these fixes.
The most common React Email / @jsx-email build and rendering bugs share a single root cause: the gap between React's JSX transformation and the constraints of the HTML email rendering environment. Here are the four categories and their canonical fixes:
- 1. CSS margin ignored by Outlook: Replace
style={{ margin }}withstyle={{ padding }}on<Column>or use ghost table spacer rows. - 2. renderWhitespace / collapsed spaces: Insert explicit
{' '}string literals between inline JSX sibling elements. - 3. Unsafe px-to-pt conversion: Always
Math.round(px / 1.333)before injecting into MSO attributes to avoid Word engine rendering artifacts. - 4. Tailwind v4 class stripping in Gmail: Use the
@react-email/tailwindv3-compatible config shim or fall back to full inline styles.
1. CSS margin in JSX Email: Why Outlook Ignores It
Both @react-email/components and @jsx-email let you apply spacing via the style prop, including CSS margin. This works correctly in Apple Mail, Gmail web, and iOS Mail, all clients that render block-level CSS margin. But Classic Outlook on Windows (2016, 2019, 2021) uses the Microsoft Word rendering engine, which ignores CSS margin on most elements.
The bug is particularly subtle because the React Email preview server uses a standard browser renderer, so the layout looks perfect in development, then collapses or loses spacing entirely when sent through Outlook.
// ❌ Broken: CSS margin via style prop is ignored by Outlook Classic
import { Section, Row, Column, Text } from "@react-email/components";
// Both @react-email and @jsx-email let you pass CSS margin via style prop
<Section style={{ margin: "24px 0" }}>
<Text>Hero content</Text>
</Section>
// Renders: <table style="margin: 24px 0">, Outlook Word engine ignores it
// Result: zero vertical gap above and below the section in Classic Outlook// ✅ Fixed: Replace margin with padding on the wrapping Column
import { Section, Row, Column } from "@react-email/components";
// Use explicit padding on the structural table cell instead
<Section>
<Row>
<Column style={{ padding: "24px 0" }}>
<Text>Hero content</Text>
</Column>
</Row>
</Section>
// For pure vertical spacers, use a dedicated spacer row:
<Row>
<Column style={{ height: "24px", lineHeight: "24px", fontSize: "0" }}>
</Column>
</Row>autois unsupported:margin: 0 autofails to center elements in Classic Outlook. Containers require ghost tables with explicit width constraints.- The background bleed bug: Classic Outlook includes
background-colorinside the margin box, causing colored card backgrounds to spill into the margin spacing. - Negative values are ignored: Negative margin pulls do not work in Outlook on Windows or mobile.
- Inline elements unsupported: Margin on
<span>and<body>is discarded.
Button padding exception: React Email's <Button> primitive avoids margin entirely, using mso-font-width: -100% and a hair space ( ) to achieve reliable horizontal padding in Outlook without VML.
2. renderWhitespace: Collapsed Spaces Between Inline Elements
This is the most frequently searched React Email build error in GSC query telemetry. The symptom: words run together without spaces, “Read ourdocumentationfor more details.”, in the rendered HTML output, even though the JSX source looks correct with text separated across multiple lines.
The root cause is React's JSX transpilation behavior. During compilation, adjacent text nodes and inline elements separated by newlines have their whitespace collapsed. This is consistent with how JSX handles whitespace in the browser, but in email, where the render() function serializes React output directly to an HTML string, the missing space character is fatal.
// ❌ Broken: whitespace between inline elements collapses
import { Text, Link } from "@react-email/components";
<Text>
Read our
<Link href="/docs">documentation</Link>
for more details.
</Text>
// Renders as: "Read ourdocumentationfor more details.", no spaces// ✅ Fixed: insert explicit {' '} string literals between inline nodes
import { Text, Link } from "@react-email/components";
<Text>
Read our{' '}
<Link href="/docs">documentation</Link>
{' '}for more details.
</Text>
// Renders as: "Read our documentation for more details." ✓
// The same rule applies to <span> inside <Text>:
<Text>
Order{' '}
<span style={{ fontWeight: 700 }}>#12345</span>
{' '}has shipped.
</Text>The {' '} pattern is the canonical React solution and is fully email-safe. It renders as a literal space character in the HTML output and survives all email client sanitizers, including Gmail's aggressive <style> stripper (since it is a text node, not a CSS rule). See also our guide on what Gmail's sanitizer actually strips.
3. Unsafe px-to-pt Conversion During Server Rendering
Some components in both @react-email/components and @jsx-email internally convert pixel values to point units for compatibility with Outlook's MSO VML attribute system. The conversion factor is 1px = 0.75pt (or equivalently, 1pt = 1.333px).
The bug: when this conversion produces a fractional result (e.g.,15px → 11.25pt), the MSO rendering engine rounds in an implementation-defined manner, sometimes up, sometimes down, and occasionally it refuses to apply the value at all, falling back to a browser default like 12pt Times New Roman. This is the root cause of the mysterious “Outlook uses wrong font size” complaints seen across React Email issue trackers.
// ❌ Unsafe: fractional pt values crash MSO rendering
const fontSizePt = 15 / 1.333; // → 11.25pt, MSO rounds unpredictably
// ✅ Safe: always Math.round() before inserting into MSO attributes
const pxToPt = (px: number) => Math.round(px / 1.333);
// Usage in a component with MSO conditional fallback:
const fontSize = 15; // design intent in px
<Text
style={{
fontSize: `${fontSize}px`,
// mso-font-size is picked up only by Classic Outlook (Word engine)
// @ts-expect-error, MSO vendor attribute not in React types
"mso-font-size": `${pxToPt(fontSize)}pt`,
lineHeight: "24px",
}}
>
Safe cross-client text
</Text>The mso-font-size vendor attribute is not part of the TypeScript React types, so you will need a @ts-expect-error suppression comment or a custom type augmentation. This is expected, the attribute is Outlook-proprietary and should only be used inside MSO conditional fallbacks in production templates.
4. Tailwind v4 Classes Stripped by Gmail
If you've recently upgraded your project's Tailwind CSS dependency from v3 to v4 and noticed that your email styles stopped rendering in Gmail, this is a known compatibility break. We covered the full technical breakdown in our post on why Tailwind v4 emails break in Gmail. The summary: Tailwind CSS v4 generates CSS using modern @layer cascade rules, which Gmail's webmail sanitizer silently discards.
The @react-email/tailwind adapter was built to inline Tailwind classes before the HTML is sent. In v3 it did this correctly. With a v4 Tailwind install, the generated CSS layers break the inlining step.
// ❌ React Email + Tailwind v4: utility classes stripped by Gmail
import { Tailwind } from "@react-email/tailwind";
// Tailwind v4 generates @layer rules, Gmail silently discards them
<Tailwind>
<div className="bg-white p-6 rounded-xl">
Content
</div>
</Tailwind>
// ✅ Use @react-email/tailwind with an explicit v3-compatible config:
<Tailwind
config={{
theme: { extend: {} },
// Force Tailwind v3 output via the compat shim
}}
>
<div className="bg-white p-6 rounded-xl">
Content
</div>
</Tailwind>
// Or inline all styles explicitly to avoid the CSS generation layer:
<div style={{ backgroundColor: "#ffffff", padding: "24px", borderRadius: "12px" }}>
Content
</div>5. MSO Conditional Comments in JSX: The dangerouslySetInnerHTML & Ghost Table Solutions
Classic Outlook on Windows relies on MSO conditional comments to trigger VML and ghost table structures. The challenge in React Email is that JSX comments ({/* ... */}) are completely stripped by the React compiler, they never appear in the serialized HTML string. Furthermore, because JSX enforces strict XML validation, you cannot place an unclosed opening <tr><td> tag in a component without triggering a syntax error.
To solve this, email developers use one of two battle-tested patterns:
- Pattern 1: Component-Level
dangerouslySetInnerHTML(Emailens Engine Standard): Used directly in the Emailens Engine fix database for Outlook max-width ghost tables, VML rounded buttons, and VML backgrounds. Because the markup is passed as a string literal, React outputs raw, unescaped HTML comments without requiring pipeline build steps. - Pattern 2: Pipeline Post-Processing: If you prefer strictly declarative JSX trees without inline HTML strings, render custom placeholder tags (e.g.
<mso-ghost-table>) and execute a string replacement on the output ofrender()before sending.
// ✅ Pattern 1: Component-Level MSO Ghost Table via dangerouslySetInnerHTML
// (This is the official pattern implemented in the Emailens Engine fix database)
// In JSX, every tag must be strictly closed, you cannot place an unclosed <tr> in JSX.
// dangerouslySetInnerHTML outputs raw HTML strings containing unescaped MSO conditionals:
export function OutlookGhostContainer({
width = 600,
childrenHtml,
}: {
width?: number;
childrenHtml: string;
}) {
return (
<div
dangerouslySetInnerHTML={{
__html: `<!--[if mso]>
<table role="presentation" width="${width}" align="center" cellpadding="0" cellspacing="0" border="0"><tr><td>
<![endif]-->
<div style="max-width:${width}px;margin:0 auto;">
${childrenHtml}
</div>
<!--[if mso]>
</td></tr></table>
<![endif]-->`,
}}
/>
);
}
// Pattern 2: Outlook VML Button with border-radius fallback
export function VmlButton({ href, label }: { href: string; label: string }) {
return (
<div
dangerouslySetInnerHTML={{
__html: `
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
href="${href}"
style="height:44px;v-text-anchor:middle;width:200px;"
arcsize="14%" strokecolor="#6d28d9" fillcolor="#6d28d9">
<w:anchorlock/>
<center style="color:#ffffff;font-family:Arial,sans-serif;font-size:14px;font-weight:bold;">${label}</center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><! -->
<a href="${href}"
style="background-color:#6d28d9;color:#ffffff;padding:12px 32px;border-radius:6px;text-decoration:none;display:inline-block;font-weight:bold;">
${label}
</a>
<!-- <![endif]-->
`,
}}
/>
);
}Whichever pattern you choose, the key requirement is ensuring that [if mso] and [endif] appear inside real HTML comment markers (<!-- -->) in the final sent markup.
6. Debugging the render() Output Locally
The React Email preview server is a useful development tool, but it renders email components inside a browser iframe with full CSS support, which creates false confidence. The actual production pipeline calls render() on the server and sends the raw HTML string to an SMTP relay. Bugs that only manifest in Outlook, Gmail Android, or Samsung Email are typically invisible in the preview server.
The fastest debugging workflow is to write a local script that calls render() directly, dumps the output to an HTML file, and opens it in a local browser. This catches most JSX whitespace, conditional comment, and serialization issues before you reach an email client. Note: renderAsync() was the v2 API and is deprecated in React Email v3+, use render() from @react-email/render.
// Debugging server render output locally
// Note: renderAsync() was deprecated in React Email v3. Use render() instead.
import { render } from "@react-email/render";
import { MyEmail } from "./emails/my-email";
async function debugRender() {
const html = await render(<MyEmail />, {
pretty: true, // readable indented output
plainText: false,
});
// Write to a temp .html file and open in browser for quick visual check
const fs = await import("fs/promises");
await fs.writeFile("debug-output.html", html, "utf-8");
console.log("Rendered", html.length, "bytes");
}
debugRender().catch(console.error);For issues that only appear inside a real inbox (Outlook margin gaps, Gmail class stripping, Apple Mail dark mode), the next step is a proper cross-client render test. Emailens simulates your HTML against 21 real rendering engines directly, no account creation, no seed list required.
7. React Email vs. @jsx-email: Which Has Fewer Rendering Bugs?
@jsx-email is a community fork of @react-email that was created partly in response to slow issue triage in the original repo. It ships several bug-fixed component variants (Text, Button, Section) and focuses on faster releases and a tighter developer experience. Both libraries expose the same fundamental styling API via the style prop and Tailwind, so the CSS margin / Outlook incompatibility documented above applies equally to both.
| Issue | @react-email | @jsx-email |
|---|---|---|
| Whitespace collapse between inline elements | Possible, use {' '} | Possible, same JSX rules |
| CSS margin ignored by Classic Outlook | Affected, use padding on Column | Affected, use padding on Column |
| px-to-pt conversion bugs | Possible in some components | Possible, Math.round required |
| Tailwind v4 class stripping | Affected, use v3 compat | Affected, use v3 compat |
| MSO conditional comment escaping | dangerouslySetInnerHTML or post-process | dangerouslySetInnerHTML or post-process |
Both libraries share the same fundamental constraint: they serialize JSX to HTML strings, then send that HTML into email clients that were never designed for component-based rendering. The bugs documented in this guide are email-environment bugs, not library bugs, they would exist in any JSX-to-HTML compilation pipeline.
Frequently Asked Questions
Why does CSS margin on React Email and @jsx-email components cause layout bugs in Outlook?
According to the Emailens Engine compatibility matrix, Outlook on Windows (Word engine) evaluates CSS margin with several critical caveats: 'auto' is not supported (causing 'margin: 0 auto' on Container to fail centering), negative margins are discarded, margin is unsupported on <span> and <body>, and background-color bleeds into the margin space instead of stopping at the padding edge. The robust fix is to use padding on <Column> or <td> elements, or use dedicated table spacer rows. For interactive CTAs, React Email's <Button> handles Outlook padding automatically using the mso-font-width and hair-space ( ) technique.
What causes renderWhitespace errors in React Email and how do I fix them?
renderWhitespace is triggered when JSX expressions between inline elements (e.g., <Text> with nested <Link> spans) collapse adjacent text nodes in a way that removes the expected space character. This happens because React's JSX compiler trims whitespace between sibling elements during transpilation. The fix is to insert an explicit {' '} expression, a JavaScript string literal containing a single space, between inline elements where whitespace must be preserved.
How do I safely convert px to pt in React Email for Outlook compatibility?
Some React Email components apply pt units for font sizes to satisfy Outlook's MSO VML renderer. The standard conversion is 1pt = 1.333px (or equivalently 1px ≈ 0.75pt). However, fractional pt values in MSO attributes are rounded unpredictably by the Word engine. Always round converted values to the nearest integer and use px for all web-facing clients via inline CSS, reserving pt only for mso-font-size attributes in MSO conditional comments.
Test Your React Email Against 21 Real Rendering Engines
Emailens renders your HTML in Gmail, Outlook, Apple Mail, and 18 more clients, catching whitespace collapses, margin gaps, and dark mode issues before your campaign goes out.
- •React Email: React Email Documentation & Component Reference
- •JSX Email: JSX Email, Drop-in Enhancement for React Email
- •GitHub / jsx-email: jsx-email Issues: CSS margin rendering, whitespace & build bugs
- •Can I Email: CSS margin Support in Email Clients
- •Emailens Engine Audit: React Email & JSX Rules: MSO Conditionals, Margin Bleed & Rendering Telemetry
Reviewed by Philippe KAM · Last updated: September 26, 2026
Building next-generation email preview and QA infrastructure for developers. Focused on reverse-engineering rendering engines across Outlook (Word MSO & New Outlook), Gmail, and Apple Mail to eliminate email rendering bugs before dispatch.