Transactional email templates: examples, HTML and best practices
Transactional email templates with HTML and React Email code for receipts, password resets, and alerts that work in Outlook. Each template uses table-based layout and inline CSS to render consistently across Gmail, Apple Mail, and Outlook without debugging client-specific bugs.
On this page
TL;DR: Transactional email templates require table-based layout and inline CSS because Outlook renders HTML through Microsoft Word's engine, which ignores flexbox, grid, and external stylesheets. Raw HTML works for simple sends. React Email lets you write React components whose compiled output is the same nested table structure, per Emlet's React Email analysis, and adds component reuse, prop validation, and CI/CD preview testing. Three production bugs appear most often: preheader text leaking into the visible body, dark mode color inversion, and missing values printing as the literal string "undefined" when a template interpolates them into a string. Test every template in Gmail, Apple Mail, and Outlook before shipping.
Your password reset email renders perfectly in Gmail and breaks in Outlook. The reason is Microsoft Word. Outlook desktop uses Word's rendering engine to display HTML, so flexbox, CSS grid, external stylesheets, and most modern layout techniques simply do not exist in that environment. Every transactional email you ship is a production incident waiting to happen if the template fails in a major client.
Below are production-ready transactional email templates for receipts, password resets, one-time passwords (OTPs), and account alerts in both raw HTML and React Email, with the client-compatibility constraint each code sample solves. You can send any of them through the send.io Transactional Email API, and the send.io integration guides cover managing reusable layouts with Email Templates. send.io doesn't change how Gmail, Apple Mail, or Outlook render your markup, so run each template through your own client tests first.
Designing transactional emails for maximum compatibility
Apple Mail uses WebKit and Gmail uses a proprietary rendering engine that sanitizes your CSS and strips unsupported properties, per Email on Acid. Outlook desktop uses Microsoft Word, per Emailens' Outlook CSS guide. Outlook is the outlier that breaks flexbox, CSS grid, external stylesheets, and most modern layout techniques.
Cross client email compatibility rules
Apple Mail, Gmail, and Outlook together account for the vast majority of email opens (about 96% combined: Apple Mail 76.56%, Gmail 16.10%, and Outlook desktop 3.48%), per Litmus's September 2026 email client market share report, so a template that breaks in any one of them fails a real share of your audience. Each engine strips or breaks a different set of CSS properties:
CSS feature | Gmail | Apple Mail | Outlook (classic desktop) |
|---|---|---|---|
Flexbox | Partial support (strips sub-properties) | Supported | Ignored |
CSS Grid | Stripped | Supported | Ignored |
External stylesheet | Stripped | Supported | Stripped |
Border-radius | Supported | Supported | Ignored |
Media queries | Supported | Supported | Ignored (Word rendering engine, including Outlook 2016, 2019, and 2021) |
The Outlook column describes classic desktop Outlook, which renders HTML through Word. Newer "Outlook (New)" renders closer to a standard browser engine, though CSS support, especially border-radius, still shows inconsistencies, so test it directly. Emailens' Outlook CSS guide documents flexbox and border-radius support in Outlook (New), but none of the sources checked here confirms grid support. Mailtester's Outlook CSS limitations guide covers only classic desktop Outlook, and it and Emailens' guide both confirm that classic Outlook does not support flexbox, grid, or external stylesheets. Because Outlook 2016, 2019, and 2021 all render through the Word engine, classic desktop Outlook also ignores media queries, so verify this row against Litmus's current CSS support tables. Google's Gmail CSS support documentation confirms that Gmail supports border-radius and media queries, and Email on Acid's Gmail vs. Apple Mail comparison confirms media query support in Apple Mail. Support varies by client version, so confirm each row in your own tests.
Use HTML tables for layout, inline CSS for styling, and no external stylesheets, per Outlook's CSS support limitations.
Essential HTML table patterns for emails
The foundational pattern is a centered 600px container table with role="presentation", per Bootstrap's email template guide.
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" style="padding: 20px 0;">
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0"
style="max-width: 600px; width: 100%; background-color: #ffffff;">
<tr>
<td style="padding: 40px 30px; font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 24px; color: #333333;">
<!-- Content goes here -->
</td>
</tr>
</table>
</td>
</tr>
</table>
Solving CSS compatibility issues
Use inline CSS as your primary styling method. Gmail supports embedded <style> blocks with most CSS selectors, attributes, and media queries, per Google's Gmail CSS support documentation. The caveat is that it discards the entire block if it hits anything it reads as an error, per Email on Acid, and Outlook ignores embedded CSS altogether. Apply background colors to table cells, not divs, and use web-safe font stacks. Outlook does not support border-radius or box-shadow because of its Word rendering engine, and it only partly supports margin, per Emailens' Outlook CSS guide. Test padding and background-size in Outlook yourself, because behavior varies by element and context.
Copy-paste templates for receipts and orders
Receipt emails pack a lot into one template: a branded header, order identifier, line items table, total row, and footer, all of which must render correctly across all three major clients.
Production-ready HTML receipt boilerplate
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 20px 0;">
<!-- 600px container: Outlook requires a fixed width on the inner table -->
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0"
style="max-width: 600px; width: 100%; background-color: #ffffff;">
<!-- Header -->
<tr>
<td style="padding: 30px; background-color: #1a1a2e; text-align: center;">
<img src="https://yourapp.com/logo.png" alt="YourApp" width="120" style="display: block; margin: 0 auto;">
</td>
</tr>
<!-- Order number -->
<tr>
<td style="padding: 30px 30px 10px; font-family: Arial, Helvetica, sans-serif; font-size: 22px; font-weight: bold; color: #1a1a2e;">
Order #1042
</td>
</tr>
<!-- Line items header -->
<tr>
<td style="padding: 10px 30px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
style="border-bottom: 2px solid #1a1a2e;">
<tr>
<td style="padding: 10px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; font-weight: bold; color: #666666;">Item</td>
<td align="center" style="padding: 10px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; font-weight: bold; color: #666666;">Qty</td>
<td align="right" style="padding: 10px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; font-weight: bold; color: #666666;">Total</td>
</tr>
</table>
</td>
</tr>
<!-- Line item row -->
<tr>
<td style="padding: 0 30px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
style="border-bottom: 1px solid #eeeeee;">
<tr>
<td style="padding: 12px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333;">Pro plan (monthly)</td>
<td align="center" style="padding: 12px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333;">1</td>
<td align="right" style="padding: 12px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333;">$49.00</td>
</tr>
</table>
</td>
</tr>
<!-- Total row -->
<tr>
<td style="padding: 20px 30px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="font-family: Arial, Helvetica, sans-serif; font-size: 16px; font-weight: bold; color: #1a1a2e;">Total</td>
<td align="right" style="font-family: Arial, Helvetica, sans-serif; font-size: 16px; font-weight: bold; color: #1a1a2e;">$49.00</td>
</tr>
</table>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="padding: 20px 30px 40px; font-family: Arial, Helvetica, sans-serif; font-size: 12px; color: #999999; text-align: center;">
Questions? Contact support@yourapp.com
</td>
</tr>
</table>
</td>
</tr>
</table>
Building receipts with React Email
React Email lets you write templates as React components using primitives like Container, Row, and Column, so you never hand-write the table-based compatibility layer yourself, per APIScout's React Email walkthrough.
import { Html, Head, Body, Container, Section, Row, Column, Text, Heading, Img } from '@react-email/components';
export default function Receipt({ orderNumber, items, total }) {
return (
<Html>
<Head />
<Body style={{ backgroundColor: '#f4f4f4' }}>
<Container style={{ maxWidth: '600px', margin: '0 auto', backgroundColor: '#ffffff' }}>
<Section style={{ backgroundColor: '#1a1a2e', padding: '30px', textAlign: 'center' }}>
<Img src="https://yourapp.com/logo.png" alt="YourApp" width="120" />
</Section>
<Section style={{ padding: '30px 30px 10px' }}>
<Heading style={{ fontSize: '22px', color: '#1a1a2e' }}>Order #{orderNumber}</Heading>
</Section>
{items.map((item) => (
<Section key={item.name} style={{ padding: '0 30px' }}>
<Row style={{ borderBottom: '1px solid #eeeeee' }}>
<Column><Text style={{ fontSize: '14px', color: '#333333' }}>{item.name}</Text></Column>
<Column style={{ textAlign: 'center' }}><Text style={{ fontSize: '14px', color: '#333333' }}>{item.qty}</Text></Column>
<Column style={{ textAlign: 'right' }}><Text style={{ fontSize: '14px', color: '#333333' }}>${item.price}</Text></Column>
</Row>
</Section>
))}
<Section style={{ padding: '20px 30px' }}>
<Row>
<Column><Text style={{ fontSize: '16px', fontWeight: 'bold' }}>Total</Text></Column>
<Column style={{ textAlign: 'right' }}><Text style={{ fontSize: '16px', fontWeight: 'bold' }}>${total}</Text></Column>
</Row>
</Section>
</Container>
</Body>
</Html>
);
}
The compiled output produces the same nested table structure you would otherwise hand-write and re-test in every client, as confirmed by Emlet's React Email analysis. Render the component to an HTML string and pass it as the message body using one of the send.io SDKs.
Coding responsive invoice line items
The line items table in this template uses three columns: item name, quantity, and total. This is the layout of the sample, not a documented standard. On narrow screens those columns get cramped, so the sample stacks them vertically below a 480px breakpoint. That value is an example, not a standard, because email breakpoints vary by implementation. Adjust it to where your own layout starts to cramp, and test it. Since media queries do not work in desktop Outlook, the fallback relies on the table collapsing naturally when the viewport is narrower than the fixed width.
<!-- Line item with mobile stacking -->
<tr>
<td style="padding: 0 30px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td class="stack" style="padding: 12px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333;">
2x Widget Pro
</td>
<td class="stack" align="right" style="padding: 12px 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333;">
$98.00
</td>
</tr>
</table>
</td>
</tr>
Add a <style> block for clients that support media queries (Gmail and Apple Mail):
<style>
@media only screen and (max-width: 480px) {
.stack { display: block !important; width: 100% !important; text-align: left !important; }
}
</style>
Optimizing email for dark mode
Gmail's mobile apps and Apple Mail can invert email colors automatically in dark mode: white backgrounds turn dark, dark text turns light, and brand colors shift without warning. Gmail's desktop web app does not switch color schemes, so every email renders in its default colors there. The fix combines two mechanisms. The meta tags <meta name="color-scheme" content="light dark"> and <meta name="supported-color-schemes" content="light dark"> declare that the email supports both light and dark color schemes, but they do not target dark mode on their own. The targeting happens in the override styles inside @media (prefers-color-scheme: dark), per Campaign Monitor's dark mode guide.
Apple Mail supports the media query for color overrides. Gmail does not support prefers-color-scheme in its apps or webmail, so its automatic inversion cannot be targeted with overrides, per Campaign Monitor's dark mode guide. Outlook on Windows auto-inverts colors but provides no targeting mechanism either, so defensive color choices are the only fix in both clients.
Client | Dark mode behavior | Fix |
|---|---|---|
Gmail (mobile apps) | Auto-inverts, no media query support | Defensive design |
Gmail (desktop web) | No color scheme switching. Renders default colors | None needed |
Apple Mail | Auto-inverts, supports media query | Media query with overrides |
Outlook (Windows) | Auto-inverts, no targeting support | Defensive design |
Source for the table: the Apple Mail and Gmail rows draw on Campaign Monitor's dark mode guide. Dark mode behavior changes with client and OS versions, so confirm each row in your own tests.
Secure authentication templates for quick deploy
Authentication emails are time-sensitive and security-critical. A password reset email that renders as broken HTML or delivers an expired token blocks the user from completing the reset, which can lead to a support request and erode trust. The templates in this section work as the message body for the send.io Transactional Email API.
Clean HTML password reset markup
The button uses the table cell pattern rather than an anchor with padding, because Outlook ignores padding on inline elements, per Email Bits' HTML email design guide, which describes the same table-based bulletproof button pattern.
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 20px 0;">
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0"
style="max-width: 600px; width: 100%; background-color: #ffffff;">
<tr>
<td style="padding: 40px 30px 20px; font-family: Arial, Helvetica, sans-serif; font-size: 22px; font-weight: bold; color: #1a1a2e;">
Reset your password
</td>
</tr>
<!-- The 30-minute expiry is an example value: set it to your reset token's actual lifetime -->
<tr>
<td style="padding: 0 30px 20px; font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 24px; color: #333333;">
We received a request to reset your password. Click the button below to choose a new one. This link expires in 30 minutes.
</td>
</tr>
<!-- Bulletproof button: table cell with background color, not anchor padding -->
<tr>
<td align="center" style="padding: 10px 30px 30px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" bgcolor="#2563eb" style="padding: 14px 32px; font-family: Arial, Helvetica, sans-serif; font-size: 16px; font-weight: bold;">
<a href="https://yourapp.com/reset?token=abc123" style="color: #ffffff; text-decoration: none; display: inline-block;">
Reset password
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td style="padding: 0 30px 40px; font-family: Arial, Helvetica, sans-serif; font-size: 13px; line-height: 20px; color: #999999;">
If you did not request a password reset, ignore this email. Your password will not change.
</td>
</tr>
</table>
</td>
</tr>
</table>
The bgcolor attribute on the <td> works alongside inline background-color because some older Outlook versions respect only the HTML attribute. Use the full 6-digit hex value for maximum compatibility across all clients. The 30-minute expiry in this sample is a placeholder, so match the copy to your token's actual lifetime.
Password reset using React Email
React Email's Button and Link components handle the compatibility patterns automatically.
import { Html, Head, Body, Container, Section, Text, Button, Heading } from '@react-email/components';
export default function PasswordReset({ resetUrl, expiryMinutes }) {
return (
<Html>
<Head />
<Body style={{ backgroundColor: '#f4f4f4' }}>
<Container style={{ maxWidth: '600px', margin: '0 auto', backgroundColor: '#ffffff' }}>
<Section style={{ padding: '40px 30px 20px' }}>
<Heading style={{ fontSize: '22px', color: '#1a1a2e' }}>Reset your password</Heading>
</Section>
<Section style={{ padding: '0 30px 20px' }}>
<Text style={{ fontSize: '16px', lineHeight: '24px', color: '#333333' }}>
This link expires in {expiryMinutes} minutes.
</Text>
</Section>
<Section style={{ padding: '10px 30px 30px', textAlign: 'center' }}>
<Button
href={resetUrl}
style={{
backgroundColor: '#2563eb',
color: '#ffffff',
padding: '14px 32px',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
}}
>
Reset password
</Button>
</Section>
</Container>
</Body>
</Html>
);
}
OTP verification email templates
OTP emails display a short numeric code the user types into your application. The sample below is one styling option, not a documented requirement: it sets the code in a monospace font stack with a 36px size and 8px of letter-spacing, centered in its own table cell. Monospace fonts are commonly recommended for OTP codes, and the spacing and size are adjustable choices meant to keep the digits legible when read off one screen and typed into another. Send the finished markup through the send.io Transactional Email API. If an AI agent has to read the code on the receiving end, the agent mailbox docs cover giving it its own inbox.
<tr>
<td align="center" style="padding: 20px 30px 30px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" style="padding: 16px 40px; background-color: #f0f0f5; font-family: 'Courier New', Courier, monospace; font-size: 36px; font-weight: bold; letter-spacing: 8px; color: #1a1a2e;">
847291
</td>
</tr>
</table>
</td>
</tr>
Securing reset tokens and expiry logic
Generate tokens with at least 128 bits of entropy, per the Security Guide, a page from a commercial password-recovery service. That guide attributes the figure to OWASP, but OWASP’s Forgot Password Cheat Sheet doesn’t specify an exact bit count and says only that tokens should be “sufficiently long.” Treat 128 bits as a vendor-suggested floor and confirm it against a primary source. Keep the expiry window short. Security guides such as Vaadata commonly recommend an expiry of no more than one hour after generation. The Security Guide gives a range of 15-60 minutes and separately suggests even shorter expiration times for high-risk accounts, without naming a specific number. Invalidate tokens after first use to prevent replay attacks.
Optimized code patterns for account event emails
Account event emails notify users about logins, security changes, and payment failures. They carry urgency and need visual distinction from receipts and newsletters. Send them through the send.io Transactional Email API, and follow the send.io guides to set up Event Webhooks for tracking delivery outcomes on each send.
Production-ready HTML alert markup
The alert banner pattern uses a colored table cell, with color communicating severity: red for security alerts, amber for warnings, blue for informational notices.
<tr>
<td style="padding: 20px 30px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding: 16px 20px; background-color: #fef2f2; border-left: 4px solid #dc2626; font-family: Arial, Helvetica, sans-serif; font-size: 14px; line-height: 22px; color: #991b1b;">
<strong>New sign-in detected.</strong> Your account was accessed from Chrome on macOS at 2:14 PM UTC. If this was not you, reset your password immediately.
</td>
</tr>
</table>
</td>
</tr>
The border-left property renders in Gmail and Apple Mail but not Outlook. The background color carries the visual weight in Outlook, so choose one that communicates severity without relying on the border.
React template for transactional alerts
In React Email, you pass the alert type as a prop and switch color schemes conditionally.
const alertColors = {
security: { bg: '#fef2f2', border: '#dc2626', text: '#991b1b' },
warning: { bg: '#fffbeb', border: '#f59e0b', text: '#92400e' },
info: { bg: '#eff6ff', border: '#3b82f6', text: '#1e40af' },
};
export default function AccountAlert({ type = 'info', message }) {
const colors = alertColors[type] || alertColors.info;
return (
<Section style={{
padding: '16px 20px',
backgroundColor: colors.bg,
borderLeft: `4px solid ${colors.border}`,
}}>
<Text style={{ fontSize: '14px', lineHeight: '22px', color: colors.text }}>
{message}
</Text>
</Section>
);
}
Best practices for email call-to-action (CTA) design
Use a table-based bulletproof button: a table with a single cell containing the link, with background-color and padding set on the cell, per Email Bits' HTML email design guide. Control the color through the bgcolor attribute on the <td> and the background-color style on the hyperlink, always using the full 6-digit hex value.
Limit each transactional email to one primary CTA tied to a single action, so customers don't miss it. This is general email design guidance, not a documented client requirement. Place it above the fold, where guides put the key offer or call to action within roughly the first 300-500px of the template, per Selzy's email template size guide. No single pixel cutoff is a standard, so treat that range as a guide. This is a design choice, not a client requirement. A receipt with a long line items table may need the CTA below the order summary instead.
Improving open rates via preheader logic
The preheader is the text that appears after the subject line in the inbox preview, a second subject line. When no preheader is set, many clients fall back to the first visible text in the body, which is often logo alt text or a navigation link. The exact fallback varies by client, so check how your target clients display the preview.
HTML markup for hidden preheaders
Hide preheader text using CSS properties like display:none and max-height:0 within a div or span at the very beginning of the body, per SendHTML's preheader guide, whose FAQ section also mentions opacity:0.
<body>
<div style="display: none; font-size: 1px; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all;">
Your order #1042 has been confirmed. Total: $49.00.
</div>
<!-- Rest of email content -->
</body>
React Email preheader implementation
React Email's Preview component renders a display:none preheader that the library pads with hidden characters to fill out the inbox snippet. That padding is an implementation detail of the library and isn't described in its official documentation, so check the installed version's source if you need the exact technique. The approach is problematic, because Google's email sender guidelines advise against using HTML and CSS to hide content in messages. If that risk matters for your sends, you can remove the preheader so inbox snippets show the first visible text instead.
import { Html, Head, Preview, Body } from '@react-email/components';
export default function Receipt({ orderNumber, total }) {
return (
<Html>
<Head />
<Preview>Order #{orderNumber} confirmed. Total: ${total}.</Preview>
<Body>
{/* Email content */}
</Body>
</Html>
);
}
5 steps to debug email layouts before shipping
Shipping a broken transactional email template is a production incident. These five steps catch bugs that only appear in specific clients, viewports, or data conditions.
1. Automate email rendering tests
Test every template change across clients before shipping. Litmus and Email on Acid generate screenshots from a single HTML upload.
# .github/workflows/email-templates.yml
name: Email template check
on: [push]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx react-email build
- run: npx react-email export
2. Prevent dark mode layout bugs
Test three things: logo visibility against dark backgrounds, button contrast (does the CTA maintain a 4.5:1 contrast ratio after inversion? That is the WCAG 2.x Level AA minimum for normal-size text, per the W3C's Understanding Success Criterion 1.4.3: Contrast (Minimum), and background behavior across clients. Keep your inline styles as the light-mode default and layer the dark overrides on top through classes in a <style> block, so clients that ignore the media query still render the light version:
<head>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<style>
@media (prefers-color-scheme: dark) {
.body-bg { background-color: #111418 !important; }
.card { background-color: #1c2128 !important; }
.text-body { color: #e6e6e6 !important; }
}
</style>
</head>
Apply the classes on top of the inline defaults (for example <td class="card" style="background-color: #ffffff;">) so Apple Mail swaps in the dark values while clients without media query support keep the inline color. Verify the overrides in Apple Mail first, since it is the only major client that honors them. Then open the email in Gmail's apps and Outlook on Windows with dark mode enabled to confirm the logo stays visible on the inverted background and the CTA text stays readable against its button color.
3. Validate mobile screen breakpoints
Test at 320px minimum width (iPhone SE, first generation). This is a common low-end test width, not a documented standard, so adjust it to the smallest viewport your own audience uses. Your 600px container holds its max-width and content reflows within it, but multi-column layouts need the stacking pattern. The sample stacks columns at 480px and renders the full layout above that width. Treat 480px as an example breakpoint, not a standard, and adjust it to where your own layout starts to cramp.
4. Test email link and CTA behavior
Click every link. Verify tracking parameters survive redirects, HTTPS URLs resolve, and the CTA renders as clickable in Outlook where only the text inside the table cell is clickable, not the cell padding around it. Size your anchor text accordingly.
5. Manage null values in email placeholders
When a template interpolates a missing or null value into a string, such as a JavaScript template literal or string concatenation, the output contains a literal "undefined" or "null". JSX omits null and undefined children, so the bug shows up where a prop is embedded in a string (a greeting, a subject line, an href) rather than rendered as a child. Templating engines such as Handlebars behave differently, so check how your engine handles missing values. Use nullish coalescing to provide fallbacks: const safeName = name ?? "Customer".
Every template above solves a specific client constraint: Word-engine tables for Outlook, inline CSS for Gmail, and defensive color choices for dark mode. Start with one of them, run it through Gmail, Apple Mail, and Outlook, then send it with the send.io Transactional Email API or one of the send.io SDKs. Compare the sample HTML against your current templates to see how much of your layout code you can replace, and follow the integration guides to store the versions that pass your client tests in Email Templates. If you work from an AI assistant such as Claude or Cursor, the MCP integration docs show how to connect it.
FAQs
Why do transactional emails still use tables for layout?
Outlook renders HTML through Microsoft Word's engine, which ignores flexbox, CSS grid, and float. Tables are the only layout mechanism that renders consistently across Gmail, Apple Mail, and Outlook.
Are external CSS files supported in transactional email templates?
No. Gmail strips <link> tags and Outlook ignores external stylesheets. All CSS must be inlined on individual elements using the style attribute.
How can dark mode be tested without sending real emails?
Use Litmus or Email on Acid to generate dark mode screenshots across clients. React Email's preview mode also supports toggling a dark mode class for local testing.
What's the difference between raw HTML and React Email templates?
Raw HTML requires hand-writing every table and inline style. React Email provides JSX components that generate the same client-compatible markup with less repetition and prop type checking. Both approaches produce HTML, so either one can be sent through the send.io Transactional Email API or stored as a reusable layout in Email Templates, as described in the send.io guides.
Is testing in Outlook necessary if most users are on Gmail?
Yes. Outlook desktop holds a single-digit share of email client market share (3.48%), per Litmus's September 2026 email client market share report, which still translates to thousands of broken renders at scale. A broken password reset email leaves affected users unable to complete the reset on their own. Whether that becomes a support ticket depends on your support process, so check how your team handles failed resets.
Key terms glossary
Transactional email: An automated, one-to-one email triggered by a user action or system event, such as a receipt, password reset, or login alert. send.io sends these through its Transactional Email API and SMTP Relay, which the send.io guides walk through.
Preheader text: The preview text that appears after the subject line in the inbox. It is set with a hidden div at the top of the email body and should summarize the email's content.
Inline CSS: CSS applied directly to an HTML element via the style attribute. The most reliable styling method for email templates because Gmail strips external stylesheets and Outlook ignores both external stylesheets and embedded <style> blocks.
Table-based layout: The practice of using nested HTML tables with role="presentation" for email structure. Required because Outlook's Word rendering engine does not support flexbox, grid, or float.
React Email: A component library that provides JSX primitives (Html, Body, Container, Section, Row, Column, Text, Button). Its compiled output is table-based HTML, the same nested table structure you would otherwise hand-write for email client compatibility, per Emlet's React Email analysis.
Dark mode media query: The CSS media query @media (prefers-color-scheme: dark) that lets you override colors when the user's email client is in dark mode. Apple Mail on iOS and macOS supports the query, but some colors still default to inverted regardless of inline CSS or !important, per Campaign Monitor's dark mode guide. Gmail does not support it in the apps or webmail.
Bulletproof button: A CTA button built as a table cell with bgcolor and padding, containing an anchor tag. The pattern renders as a clickable button in every email client, including Outlook, which ignores padding on anchor elements.
Rendering engine: The software component that parses HTML and CSS to display an email. Apple Mail uses WebKit, Gmail uses a proprietary engine, and Outlook desktop uses Microsoft Word, each with different CSS support levels.