A document that looks right in the editor often looks wrong in an email client. Margins vanish, a table collapses, a font falls back to Times, and a dark-mode mail app inverts a colour the writer chose on purpose. Email clients render HTML with a much older engine than the browser the editor runs in, and each of them has its own gaps. Exporting to email is a separate job from exporting to a web page, and it needs its own rules.
There is no single email renderer
Desktop Outlook uses the Word rendering engine. Gmail strips the document head and most stylesheet rules. Apple Mail and many mobile clients render closer to a browser, but they still differ in how they treat flex and grid. Test with the clients your readers actually use, and keep a short list of the ones that matter. Treat any other client as a fallback: the message must still be readable there, even if it is not styled.
Inline the styles, and keep them simple
Many clients ignore a <style> block in the head, or keep only some of its rules. The safe approach is to copy each style onto the element it applies to, as an inline style attribute. The export step should do that, so the writer never has to think about it:
function inlineStyles(root: HTMLElement, rules: Record<string, Partial<CSSStyleDeclaration>>) {
for (const [selector, styles] of Object.entries(rules)) {
root.querySelectorAll<HTMLElement>(selector).forEach((el) => {
for (const [prop, value] of Object.entries(styles)) {
// Keep the writer's own inline styles: they were set on purpose.
if (el.style.getPropertyValue(prop) === "") {
el.style.setProperty(prop, String(value));
}
}
});
}
}The function does not overwrite a style the writer set directly on an element, so a colour chosen on one word survives the export. It also keeps the rules few. Shorthand properties such as margin are well supported, while grid, position: absolute and CSS variables are not, and they should be avoided in email output altogether.
Use tables for layout, and nothing clever
Email layout is still built with tables. A two-column block is a table with two cells, and a spacer is a cell with a height. It looks old-fashioned in the source, and it is the only layout that survives Outlook. Set width and cellpadding as attributes as well as in styles, because some clients read only the attributes. Avoid float for anything that must stay in place, since Outlook drops it.
Images need explicit sizes and alt text
Many clients block images until the reader allows them, and the layout must still make sense without them. Give each image a width and height attribute, so the space is reserved, and give each one an alt text that says what the image shows. A chart with no alt text becomes an empty box, and a reader cannot tell what it was. Use absolute URLs. A relative path in an email points nowhere once the message leaves the sender.
Dark mode changes colours you did not pick
Some clients invert the background and the text colour automatically. A pale highlight can turn into a dark block with unreadable text, and a logo with a transparent background can disappear against a dark panel. Choose background colours that work in both modes, avoid pure white and pure black, and test the export in a dark client as well as a light one.
Always produce a plain text alternative
Some readers see only the plain text part of a message, and spam filters weigh it when they score a message. Generate it from the same document, with headings marked by line breaks and links written out with their addresses. A message that has an HTML part and no plain text part is more likely to be filtered out.
A short test list
- Export a document with a two-column block and check it stays in two columns in Outlook and in Gmail.
- Open the export with images blocked and confirm each image has a visible size and a readable description.
- Open the export in a dark-mode client and confirm no text becomes unreadable.
- Check that a colour set on one word in the editor appears in the email.
- Read the plain text part on its own and confirm headings, links and lists still make sense.
RichTextEditor is a perpetual-licence JavaScript editor — one purchase, self-hosted, no metered editor loads. Download the evaluation or see how it compares.