skills/content/template-design/SKILL.md
Build HTML email templates that render everywhere. Use when designing email layouts, fixing Outlook rendering, implementing dark mode, adding accessibility, or choosing a templating framework.
npx skillsauth add chunkydotdev/email-skills template-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build HTML email templates that render correctly across every major email client.
email-copywriting - writing email content that people actually readspam-filter-avoidance - content patterns that trigger spam filtersemail-compliance - legal requirements including unsubscribe linksab-testing - testing different template variantsEmail HTML is not web HTML. There are no standards for how email clients render HTML and CSS. Every client does it differently, and the worst offender - Outlook on Windows - uses Microsoft Word's rendering engine, not a browser engine. This means you're building for a fragmented landscape where the rules of web development don't apply.
The core principle: code for the worst client, enhance for the best.
Start every email template with this skeleton:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Email Subject Here</title>
<!--[if mso]>
<nso:officedocumentsettings>
<o:allowpng/>
<o:pixelsperinch>96</o:pixelsperinch>
</nso:officedocumentsettings>
<![endif]-->
<style>
/* Reset styles and media queries go here */
</style>
</head>
<body style="margin: 0; padding: 0; width: 100%; background-color: #f4f4f4;">
<!-- Email content -->
</body>
</html>
Key points:
xmlns:v, xmlns:o) are required for Outlook to handle vector graphics and layout settings properly.<meta name="color-scheme"> tells clients that support it (Apple Mail, Outlook on Mac) that your email has both light and dark mode styles.<!--[if mso]> conditional comment targets Outlook's Word rendering engine specifically. Use it to fix Outlook-only layout issues.<title> to your subject line - some clients display it in preview tabs.Outlook on Windows uses Word's rendering engine, which does not support display: flex, display: grid, float, or even proper div-based layouts. Tables are the only reliable way to achieve multi-column layouts across all clients.
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 20px 0;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="background-color: #ffffff; max-width: 600px;">
<tr>
<td style="padding: 40px 30px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #333333;">
<!-- Content here -->
</td>
</tr>
</table>
</td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="max-width: 600px;">
<tr>
<td style="padding: 0;">
<!--[if mso]>
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600">
<tr>
<td valign="top" width="290">
<![endif]-->
<div style="display: inline-block; width: 100%; max-width: 290px; vertical-align: top;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td style="padding: 10px;">
<!-- Left column content -->
</td>
</tr>
</table>
</div>
<!--[if mso]>
</td>
<td valign="top" width="290">
<![endif]-->
<div style="display: inline-block; width: 100%; max-width: 290px; vertical-align: top;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td style="padding: 10px;">
<!-- Right column content -->
</td>
</tr>
</table>
</div>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</table>
This is the "hybrid" or "ghost table" technique. The outer div elements with display: inline-block stack naturally on small screens, while the MSO conditional comments provide a fixed-width table layout specifically for Outlook.
role="presentation" on every layout table. This tells screen readers the table is for layout, not data. Critical for accessibility.<table> for actual data display without removing role="presentation". If you're showing tabular data (pricing, order items), use semantic table markup without the role attribute.width attribute and max-width style. The attribute is for Outlook (which ignores CSS width on tables), the style is for everything else.These work in all major clients (Gmail, Outlook, Apple Mail, Yahoo):
| Property | Notes |
|----------|-------|
| color | Use hex values, not rgb() or hsl() |
| background-color | Same - hex only for maximum compatibility |
| font-family | System fonts only for reliable rendering |
| font-size | Use px, not em or rem |
| font-weight | bold or numeric values |
| font-style | normal, italic |
| text-align | left, center, right |
| text-decoration | none, underline |
| line-height | Use unitless values or px |
| padding | Works on td elements; write out each side separately |
| border | Works on td and table |
| width, height | On td, table, img |
| vertical-align | On td |
| Property | Status in Outlook | Workaround |
|----------|-------------------|------------|
| display: flex | Ignored | Use tables |
| display: grid | Ignored | Use tables |
| float | Partially works, unreliable | Use tables |
| margin | Partially works on block elements, ignored on td | Use padding on td cells |
| border-radius | Ignored | Use VML for rounded corners, or accept square corners in Outlook |
| background-image | Ignored on td/div | Use VML backgrounds (see below) |
| max-width | Ignored on table | Set both width attribute and max-width style |
| gap | Not supported | Use padding/margin on child elements |
| CSS shorthand (padding: 10px 20px) | Partially works | Write each side separately: padding-top, padding-right, etc. |
Gmail strips <style> blocks in some contexts (non-Gmail addresses viewed in Gmail, AMP emails) and is aggressive about removing CSS it doesn't recognize:
@import rulesposition and related propertiesgrid-template-columns, etc.)flex-direction, justify-content, align-items)--variable-name)calc() in any propertyGmail also truncates emails larger than 102KB of HTML. If your email is clipped, recipients won't see the footer, unsubscribe link, or CTA at the bottom. Minify your HTML and keep images hosted externally.
Many email clients strip <style> tags entirely. Always apply critical styles inline. Use <style> blocks as progressive enhancement only - for things like media queries and hover states that only work in clients that support <style>.
<!-- Do this -->
<td style="padding: 20px; font-family: Helvetica, Arial, sans-serif; font-size: 16px; color: #333333;">
Content here
</td>
<!-- Not this -->
<td class="content">Content here</td>
Write out CSS properties individually. Avoid shorthand:
<!-- Do this -->
<td style="padding-top: 20px; padding-right: 30px; padding-bottom: 20px; padding-left: 30px;">
<!-- Not this (shorthand is unreliable in some clients) -->
<td style="padding: 20px 30px;">
Over 70% of emails are opened on mobile devices. If your email isn't mobile-friendly, most recipients will delete it immediately.
The most reliable approach combines fluid widths with the ghost table technique shown above. Content flows naturally on small screens without requiring media queries (which Gmail doesn't support for non-Google addresses).
<!-- Outer wrapper: 100% width, centered inner container -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<!-- Inner container: fixed max-width, fluid on mobile -->
<div style="max-width: 600px; margin: 0 auto;">
<!--[if mso]>
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0">
<tr><td>
<![endif]-->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding: 20px; font-size: 16px; line-height: 1.5;">
Content scales fluidly
</td>
</tr>
</table>
<!--[if mso]>
</td></tr></table>
<![endif]-->
</div>
</td>
</tr>
</table>
Use media queries for clients that support them (Apple Mail, iOS Mail, Outlook for Mac, some Yahoo). Don't rely on them as your only responsive strategy.
<style>
@media only screen and (max-width: 600px) {
.mobile-full-width {
width: 100% !important;
max-width: 100% !important;
}
.mobile-padding {
padding-left: 20px !important;
padding-right: 20px !important;
}
.mobile-hide {
display: none !important;
max-height: 0 !important;
overflow: hidden !important;
}
.mobile-text-center {
text-align: center !important;
}
.mobile-font-size {
font-size: 18px !important;
line-height: 26px !important;
}
}
</style>
More than 80% of users have dark mode enabled on at least one device. Your emails need to handle it.
There are three categories:
Add these meta tags in <head>:
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
And this CSS:
<style>
:root {
color-scheme: light dark;
}
</style>
<style>
/* For clients that support prefers-color-scheme */
@media (prefers-color-scheme: dark) {
.email-body {
background-color: #1a1a1a !important;
}
.email-content {
background-color: #2d2d2d !important;
color: #e0e0e0 !important;
}
.heading {
color: #ffffff !important;
}
.link {
color: #6eb5ff !important;
}
}
/* Outlook.com / Outlook App dark mode (uses data attributes) */
[data-ogsc] .email-body {
background-color: #1a1a1a !important;
}
[data-ogsc] .email-content {
background-color: #2d2d2d !important;
color: #e0e0e0 !important;
}
</style>
#FDFDFD and #1a1a1a instead.| Client | prefers-color-scheme | [data-ogsc] | Auto-inversion |
|--------|----------------------|---------------|----------------|
| Apple Mail | Yes | No | Yes (can override) |
| iOS Mail | Yes | No | Yes (can override) |
| Outlook (Mac) | Yes | No | Yes |
| Outlook.com | No | Yes | Partial |
| Outlook (Windows) | No | No | No |
| Gmail (web) | No | No | No |
| Gmail (Android) | No | No | Partial |
| Gmail (iOS) | No | No | Partial |
| Yahoo Mail | No | No | Partial |
Web fonts have limited email support - they work in Apple Mail, iOS Mail, and Outlook on Mac, but not in Gmail, Outlook on Windows, or Yahoo. Always specify a full fallback stack.
/* Sans-serif (recommended for most emails) */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
/* Serif (for editorial / newsletter style) */
font-family: Georgia, 'Times New Roman', Times, serif;
/* Monospace (for code, order numbers) */
font-family: 'Courier New', Courier, monospace;
If you want to try web fonts, use @import or <link> in <head> with a full fallback stack. Clients that don't support web fonts will use the fallback.
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
</style>
<!-- Then use it with fallbacks -->
<td style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
Note: Gmail strips @import entirely. Only use web fonts if you accept that most Gmail users will see the fallback.
| Element | Desktop | Mobile | |---------|---------|--------| | Body text | 16px | 16px (minimum) | | Headings (H1) | 28-32px | 24-28px | | Headings (H2) | 22-24px | 20-22px | | Preheader/small text | 12-14px | 14px | | Button text | 16-18px | 16-18px |
padding-bottom on <td> elements or margin-bottom on <p> tags (test margin behavior across clients).alt text. Many clients block images by default. Alt text ensures recipients understand the email even without images.width and height attributes. This prevents layout shifting when images load.display: block on images to prevent the gap that appears below images in some clients.<img src="https://cdn.example.com/hero.jpg"
alt="Product launch announcement"
width="600"
height="300"
style="display: block; max-width: 100%; height: auto; border: 0;"
/>
For sharp images on retina displays, export images at 2x the display size and constrain with width and height attributes:
<!-- Image is 1200x600 actual, displayed at 600x300 -->
<img src="https://cdn.example.com/[email protected]"
alt="Product hero image"
width="600"
height="300"
style="display: block; max-width: 100%; height: auto; border: 0;"
/>
| Format | Use for | Email support | |--------|---------|---------------| | JPEG | Photos, complex images | Universal | | PNG | Logos, graphics with transparency | Universal | | GIF | Simple animations, icons | Universal (animated GIFs play in most clients except Outlook on Windows, which shows the first frame) | | SVG | - | Poor support - avoid in email | | WebP | - | Partial support - avoid for now |
Outlook ignores CSS background-image. Use VML (Vector Markup Language) for Outlook with a CSS fallback for everything else:
<td style="background-image: url('https://cdn.example.com/bg.jpg'); background-color: #2d3748; background-size: cover; background-position: center;">
<!--[if mso]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:600px; height:300px;">
<v:fill type="frame" src="https://cdn.example.com/bg.jpg" color="#2d3748" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div style="padding: 40px; color: #ffffff;">
Content over the background image
</div>
<!--[if mso]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
Spam filters can't read text inside images. Image-heavy emails look suspicious because spammers historically used image-only emails to bypass keyword-based filters.
Rules of thumb:
alt text to images - it counts as readable text for some spam filters and helps recipients with images disabled.Bulletproof buttons work everywhere, including Outlook. The standard approach uses a table with padding:
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" style="border-radius: 6px; background-color: #2563eb;">
<a href="https://example.com/action"
target="_blank"
style="display: inline-block; padding: 14px 32px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 600; color: #ffffff; text-decoration: none; border-radius: 6px;">
Get Started
</a>
</td>
</tr>
</table>
For Outlook, which ignores border-radius and sometimes clips padding on links, add VML:
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
href="https://example.com/action"
style="height:48px; v-text-anchor:middle; width:200px;"
arcsize="12%"
strokecolor="#2563eb"
fillcolor="#2563eb">
<w:anchorlock/>
<center style="color:#ffffff; font-family:Helvetica,Arial,sans-serif; font-size:16px; font-weight:bold;">
Get Started
</center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-->
<a href="https://example.com/action" style="display: inline-block; padding: 14px 32px; background-color: #2563eb; color: #ffffff; text-decoration: none; border-radius: 6px; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 600;">
Get Started
</a>
<!--<![endif]-->
Email accessibility matters - roughly 15% of the global population has some form of disability. The European Accessibility Act (EAA), effective in 2025, expands legal requirements for digital accessibility.
Use semantic heading structure. Put an <h1> in your email for the main topic. Screen reader users navigate by headings - 72% of emails tested lack proper heading structure.
Add role="presentation" to layout tables. Without it, screen readers announce "table with X rows and Y columns" for every layout table.
Write meaningful alt text. Not "image1.jpg" - describe what the image shows and why it matters. For decorative images, use alt="" (empty, not missing).
Use sufficient color contrast. WCAG requires 4.5:1 contrast ratio for body text and 3:1 for large text (18px+ or 14px+ bold). 51% of emails fail this.
Don't rely on color alone. Links should be underlined, not just colored differently. Error states should include text, not just red.
Set the lang attribute on the <html> tag so screen readers use the correct pronunciation.
Use real text, not images of text. Screen readers can't read text in images. It also breaks in dark mode, can't be resized, and is invisible when images are blocked.
Logical reading order. Screen readers follow the HTML source order, not the visual layout. Make sure your HTML reads in a sensible order when stripped of all styling.
<!-- Do this - descriptive link text -->
<a href="https://example.com/report" style="color: #2563eb; text-decoration: underline;">
View your monthly report
</a>
<!-- Not this - screen reader says "click here" with no context -->
<a href="https://example.com/report">Click here</a>
Production email platforms lint templates before sending. Common automated checks include:
{{company}} in the template but not defined) are errors. Declared-but-unused variables are warnings.href="http://..." (not HTTPS) is an error. Mixed content triggers security warnings and damages trust.Platforms like molted.email enforce these rules automatically during template creation and block sends when critical lint rules fail.
Email HTML goes through sanitization before sending to strip potentially dangerous content:
p, br, a, b, i, em, strong, u, ul, ol, li, h1-h6, table, thead, tbody, tr, td, th, img, div, span, blockquote, pre, code<script>, <iframe>, event handlers (onclick, onload, etc.), hidden elements (display:none, visibility:hidden, font-size:0), invisible Unicode characters, data URIshttps: and mailto: protocols are allowed in href and src attributes. javascript: and data: URIs are stripped.If you're writing email templates by hand, consider a framework that handles the cross-client complexity for you.
MJML is the most mature email framework. It uses custom tags (<mj-section>, <mj-column>, <mj-text>) that compile to production-ready HTML with all the table-based layout, MSO conditionals, and inline styles handled automatically.
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text font-size="20px" color="#333333">
Hello {{name}}
</mj-text>
<mj-button background-color="#2563eb" href="https://example.com">
Get Started
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Strengths: Battle-tested responsive output, built-in components, large community. Weaknesses: Custom markup language (not HTML or JSX), output HTML is verbose.
React Email lets you build templates with JSX components. Good developer experience if your team already uses React.
import { Html, Head, Body, Container, Text, Button } from '@react-email/components';
export default function WelcomeEmail({ name }) {
return (
<Html>
<Head />
<Body style={{ backgroundColor: '#f4f4f4' }}>
<Container style={{ maxWidth: '600px', margin: '0 auto' }}>
<Text style={{ fontSize: '20px', color: '#333' }}>
Hello {name}
</Text>
<Button href="https://example.com" style={{ backgroundColor: '#2563eb', color: '#fff', padding: '14px 32px' }}>
Get Started
</Button>
</Container>
</Body>
</Html>
);
}
Strengths: Familiar React/JSX syntax, component reuse, TypeScript support, live preview dev server. Weaknesses: Outlook rendering can be poor for complex layouts - some developers report highly unsatisfactory results in Outlook for multi-column designs. Test thoroughly before shipping to B2B audiences.
Maizzle uses Tailwind CSS to build emails. It compiles Tailwind classes to inline styles and handles email-specific transforms.
Strengths: Tailwind developer experience, fine-grained control over output HTML. Weaknesses: Steeper learning curve, smaller community than MJML.
| Situation | Recommendation | |-----------|---------------| | B2B audience (Outlook matters) | MJML or hand-coded | | React team, mostly consumer audience | React Email | | Tailwind team | Maizzle | | Simple transactional emails | React Email or hand-coded | | Complex marketing templates | MJML | | Maximum control over output | Hand-coded with a boilerplate |
Test every template in at least these clients, which cover the vast majority of email opens:
<style> tags for non-Google addresses, aggressive CSS filteringUsing div-based layouts without table fallbacks. Looks great in Apple Mail, completely broken in Outlook. Always use tables for structure, or at minimum the ghost table technique with MSO conditionals.
Relying on <style> blocks for critical styles. Gmail strips them for non-Google recipients. Any style that affects readability must be inline.
Forgetting the plain text version. Some recipients (and spam filters) prefer or require plain text. Always generate a text alternative. Even if it's a simplified version, it's better than nothing.
Images without alt text. When images are blocked (many corporate environments do this by default), recipients see broken image icons with no context. Always include descriptive alt text.
Sending image-only emails. Spam filters flag these aggressively. You need at least 500 characters of real text.
Not testing in Outlook. "It works in Chrome" means nothing for email. Outlook on Windows uses Word's engine, which is a completely different rendering context.
Using CSS shorthand. padding: 10px 20px may work in some clients but fail in others. Write padding-top: 10px; padding-right: 20px; padding-bottom: 10px; padding-left: 20px; for reliability.
Pure white/black backgrounds without dark mode consideration. Apple Mail auto-inverts #FFFFFF and #000000. Use #FDFDFD and #1a1a1a to keep control over your colors in dark mode.
Exceeding Gmail's 102KB limit. If your HTML exceeds 102KB, Gmail clips the email with a "View entire message" link. Recipients miss the footer, unsubscribe link, and often the primary CTA. Minify HTML, use external images, and strip unnecessary code.
Using margin for spacing on table cells. Outlook ignores margin on <td> elements. Use padding on <td> instead, or create empty spacer <td> elements.
data-ai
Choose and configure an email service provider. Use when setting up email for a new project, comparing providers, migrating between providers, or adding failover.
development
Set up SPF, DKIM, and DMARC email authentication. Use when configuring a new sending domain, debugging spam/rejection issues, adding email providers, or preparing for Google/Yahoo/Microsoft bulk sender requirements.
development
Design and send transactional emails. Use when building password resets, receipts, shipping notifications, account alerts, or separating transactional from marketing streams.
development
Build welcome and activation email sequences. Use when designing signup flows, driving users to key actions, converting trials to paid, or reducing early churn.