Skip to content

Instantly share code, notes, and snippets.

@panphora
Created September 11, 2025 01:23
Show Gist options
  • Select an option

  • Save panphora/7df06cea5499ca7ee48d43c01f70dbac to your computer and use it in GitHub Desktop.

Select an option

Save panphora/7df06cea5499ca7ee48d43c01f70dbac to your computer and use it in GitHub Desktop.
A guide for writing HTML emails

HTML Email Development Guide

Single-Column Layout Focus

This guide is optimized for single-column email layouts like the insight emails template in this repository. Single-column layouts are the most reliable, accessible, and mobile-friendly approach for email design.

Table of Contents

  1. Overview
  2. Essential Meta Tags
  3. Document Structure
  4. CSS Best Practices
  5. Layout Techniques
  6. Typography
  7. Images
  8. Buttons and CTAs
  9. Mobile Responsiveness
  10. Dark Mode Support
  11. Testing
  12. Compliance
  13. Common Pitfalls

Overview

HTML emails require a different approach than web development. Email clients have inconsistent CSS support, requiring table-based layouts and inline styles for maximum compatibility.

Key Principles

  • Use tables for layout, not divs
  • Inline CSS for critical styles
  • Single-column design for maximum reliability
  • Test across multiple clients
  • Keep it simple and lightweight
  • Assume nothing works until tested

Essential Meta Tags

DOCTYPE Considerations

While HTML5 DOCTYPE works in most cases, XHTML 1.0 Transitional offers the best compatibility with legacy email clients:

<!-- Most compatible (recommended for maximum support) -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">

<!-- Modern approach (good support) -->
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">

Complete Meta Tag Setup

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- IE rendering mode -->
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="x-apple-disable-message-reformatting">
  <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
  <meta name="color-scheme" content="light dark">
  <meta name="supported-color-schemes" content="light dark">
  <title>Your Email Title</title>
  
  <!--[if mso]>
  <xml>
    <o:OfficeDocumentSettings>
      <o:AllowPNG/>
      <o:PixelsPerInch>96</o:PixelsPerInch>
    </o:OfficeDocumentSettings>
  </xml>
  <style type="text/css">
    /* Force Outlook to use Arial */
    table, td, div, p, a { font-family: Arial, sans-serif !important; }
    * { mso-line-height-rule: exactly; }
  </style>
  <![endif]-->
</head>

Document Structure

Single-Column Template (Recommended)

This is the template structure used in this project - a simple, reliable single-column layout:

<body style="margin:0; padding:0; word-spacing:normal; background-color:#ffffff;">
  <!-- Hidden Preheader Text -->
  <div style="display:none; font-size:1px; color:#ffffff; line-height:1px; max-height:0; max-width:0; opacity:0; overflow:hidden;">
    Preheader text goes here (50-90 characters ideal)
    <!-- Add spacing to push out preview text -->
    &zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;
  </div>

  <!-- Main Wrapper -->
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
    <tr>
      <td align="center" style="padding: 20px 10px;">
        
        <!--[if mso]>
        <table role="presentation" align="center" cellpadding="0" cellspacing="0" border="0" width="600">
          <tr>
            <td align="left" valign="top" width="600">
        <![endif]-->
        
        <!-- Single-Column Email Container -->
        <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="margin: 0 auto; max-width: 600px;">
          <!-- Each content section gets its own row -->
          <tr>
            <td style="padding: 20px;">
              <h1 style="margin: 0; font-family: Arial, sans-serif; font-size: 24px; font-weight: bold;">Section 1</h1>
            </td>
          </tr>
          <tr>
            <td style="padding: 0 20px 20px 20px;">
              <p style="margin: 0; font-family: Arial, sans-serif; font-size: 16px;">Content stacks vertically in a single column.</p>
            </td>
          </tr>
          <!-- Continue stacking rows for each section -->
        </table>
        
        <!--[if mso]>
            </td>
          </tr>
        </table>
        <![endif]-->
        
      </td>
    </tr>
  </table>
</body>

Alternative Centering Methods

Using Center Tag

Some developers prefer the <center> tag for simpler centering:

<body style="margin:0; padding:0; background-color:#cccccc;">
  <center>
    <table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="600" style="width:600px; margin:0 auto;">
      <tr>
        <td style="background-color:#ffffff;">
          <!-- Content goes here -->
        </td>
      </tr>
    </table>
  </center>
</body>

Hybrid Max-Width Approach

For modern clients with Outlook fallback:

<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
  <tr>
    <td align="center" style="padding:0;">
      <!--[if mso]>
      <table role="presentation" width="600" cellpadding="0" cellspacing="0">
      <tr><td>
      <![endif]-->
      <div style="max-width:600px; margin:0 auto;">
        <!-- Single-column content -->
      </div>
      <!--[if mso]>
      </td></tr>
      </table>
      <![endif]-->
    </td>
  </tr>
</table>

This pattern uses max-width div for modern clients and MSO ghost tables for Outlook.

CSS Best Practices

Reset Styles

/* Client-specific reset styles */
body {
  margin: 0;
  padding: 0;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
  word-spacing: normal; /* Prevent word spacing issues */
}

table {
  border-collapse: collapse;
  mso-table-lspace: 0;
  mso-table-rspace: 0;
}

img {
  border: 0;
  height: auto;
  line-height: 100%;
  outline: none;
  text-decoration: none;
  -ms-interpolation-mode: bicubic;
}

/* Prevent WebKit and Windows mobile from changing default text sizes */
body, table, td, a {
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
}

/* MSO-specific line height fix */
* {
  mso-line-height-rule: exactly;
}

Inline CSS Priority

Always use inline CSS for critical styles:

<!-- Good -->
<td style="font-family: Arial, sans-serif; font-size: 16px; color: #333333;">
  Text content
</td>

<!-- Avoid -->
<td class="body-text">
  Text content
</td>

Avoid CSS Shorthand

Many email clients don't support CSS shorthand properties. Always use longhand:

<!-- Avoid shorthand -->
<td style="padding: 10px 20px; margin: 5px;">

<!-- Use longhand instead -->
<td style="padding-top: 10px; padding-right: 20px; padding-bottom: 10px; padding-left: 20px; margin-top: 5px; margin-right: 5px; margin-bottom: 5px; margin-left: 5px;">

<!-- Exception: background and font can sometimes work -->
<td style="background: #ffffff; font: 16px/24px Arial, sans-serif;">

Color Values

Always use 6-digit hex values for maximum compatibility:

<!-- Good -->
<td style="color: #ffffff; background-color: #000000;">

<!-- Avoid -->
<td style="color: #fff; background-color: rgb(0,0,0);">

Layout Techniques

Essential Table Attributes

Always include these attributes on layout tables:

<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
  <!-- Content -->
</table>
  • role="presentation": Tells screen readers this is for layout, not data
  • cellspacing="0": Removes unwanted spacing between cells
  • cellpadding="0": Removes unwanted padding inside cells
  • border="0": Removes visible borders
  • width: Define explicitly (percentage or pixels)

Single-Column Centered Container (Recommended)

This is the primary layout pattern used in this project - a centered, single-column container:

<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
  <tr>
    <td align="center">
      <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600">
        <tr>
          <td style="padding: 20px;">
            <!-- Your single-column content stacks here -->
            <p>First content block</p>
          </td>
        </tr>
        <tr>
          <td style="padding: 20px;">
            <!-- Next content block -->
            <p>Second content block</p>
          </td>
        </tr>
      </table>
    </td>
  </tr>
</table>

Benefits of single-column:

  • Works perfectly on all screen sizes
  • No complex responsive code needed
  • Consistent rendering across all email clients
  • Natural reading flow

Two-Column Layout (Not Used in This Project)

Note: This template uses a single-column layout for better reliability and mobile compatibility. The following multi-column example is included for reference only.

<!-- NOT RECOMMENDED for this project -->
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
  <tr>
    <td valign="top" width="50%">
      <!-- Left column content -->
    </td>
    <td valign="top" width="50%">
      <!-- Right column content -->
    </td>
  </tr>
</table>

Why we avoid multi-column: Single-column layouts are more reliable across all email clients, automatically mobile-friendly, and easier to maintain.

Spacer Elements & Dividers

The best way to add spacing between tables and rows (padding and margin don't work reliably on these):

<!-- Vertical spacer with accessibility -->
<tr>
  <td aria-hidden="true" height="30" style="font-size: 0px; line-height: 0px;">
    &nbsp;
  </td>
</tr>

<!-- More bulletproof spacer (for Outlook) -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
  <tr><td style="font-size:0; line-height:0; height:20px;">&nbsp;</td></tr>
</table>

<!-- Horizontal divider line -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">
  <tr>
    <td style="border-top:1px solid #e6e6e6; height:1px; line-height:1px; font-size:0;">&nbsp;</td>
  </tr>
</table>

<!-- Why these attributes matter:
  - aria-hidden="true": Hides the &nbsp; from screen readers
  - height/width: Sets the size of the spacer
  - font-size: 0px; line-height: 0px: Prevents inherited spacing
  - &nbsp;: Prevents collapse in some clients
-->

Hybrid Layout (Not Used in This Project)

Note: This template uses a single-column layout. Hybrid techniques are shown here for reference only.

Hybrid design uses inline-block, max-width, and ghost tables to stack columns without media queries:

<!-- NOT USED in this single-column template -->
<tr>
  <td>
    <!--[if mso]>
    <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
    <tr>
    <td width="300">
    <![endif]-->
    <div style="display:inline-block; width:100%; min-width:200px; max-width:300px;">
      Column 1
    </div>
    <!--[if mso]>
    </td>
    <td width="300">
    <![endif]-->
    <div style="display:inline-block; width:100%; min-width:200px; max-width:300px;">
      Column 2
    </div>
    <!--[if mso]>
    </td>
    </tr>
    </table>
    <![endif]-->
  </td>
</tr>

For this project: Stick with single-column layouts using simple stacked <tr> elements.

Typography

Important: Avoid H1-H6 Tags

Never use <h1> - <h6> tags in emails. Their default margins and sizes are wildly inconsistent across email clients. Instead, use styled <td>, <p>, or <span> elements:

<!-- Avoid -->
<h1 style="font-size:24px;">My Heading</h1>

<!-- Use instead -->
<td style="font-family: Arial, sans-serif; font-size: 24px; font-weight: bold; color: #222222; padding: 10px;">
  My Heading
</td>

Font Stacks

/* Web-safe fonts (always work) */
font-family: Arial, Helvetica, sans-serif;
font-family: Georgia, Times, 'Times New Roman', serif;
font-family: Verdana, Geneva, sans-serif;
font-family: 'Trebuchet MS', Arial, sans-serif;
font-family: Courier, monospace;

/* System font stack */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

Text Styling

<!-- Heading -->
<td style="font-family: Arial, sans-serif; font-size: 24px; line-height: 32px; font-weight: bold; color: #333333; mso-line-height-rule: exactly;">
  Heading Text
</td>

<!-- Body text -->
<td style="font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; color: #666666; mso-line-height-rule: exactly;">
  Body paragraph text
</td>

<!-- Link -->
<a href="https://example.com" style="color: #007bff; text-decoration: underline;">Link text</a>

Lists: Table-Based vs. Native HTML

Option 1: Table-Based Lists (Most Reliable)

Avoid <ul> and <ol> tags - their styling support is inconsistent. Use table-based "fake" lists that work perfectly in single-column layouts:

<!-- Bullet list using tables - ideal for single-column emails -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
  <tr>
    <td valign="top" width="20" style="width:20px; font-size:16px; line-height:24px;">&bull;</td>
    <td valign="top" style="font-family: Arial, sans-serif; font-size: 16px; line-height: 24px;">
      List item one
    </td>
  </tr>
  <tr>
    <td valign="top" width="20" style="width:20px; font-size:16px; line-height:24px;">&bull;</td>
    <td valign="top" style="font-family: Arial, sans-serif; font-size: 16px; line-height: 24px;">
      List item two
    </td>
  </tr>
</table>

<!-- Numbered list -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
  <tr>
    <td valign="top" width="20" style="width:20px; font-size:16px; line-height:24px;">1.</td>
    <td valign="top" style="font-family: Arial, sans-serif; font-size: 16px; line-height: 24px;">
      First item
    </td>
  </tr>
  <tr>
    <td valign="top" width="20" style="width:20px; font-size:16px; line-height:24px;">2.</td>
    <td valign="top" style="font-family: Arial, sans-serif; font-size: 16px; line-height: 24px;">
      Second item
    </td>
  </tr>
</table>

Option 2: Native Lists with Outlook Fix

If you prefer native HTML lists, use this pattern with MSO conditionals:

<!--[if mso]><div style="margin-left:-20px;"><![endif]-->
<ul style="margin:0; padding:0; list-style:disc; list-style-position:inside; font:14px/20px Arial, sans-serif; color:#444;">
  <li style="margin:0 0 0 20px;">Applicants: 1,926</li>
  <li style="margin:0 0 0 20px;">Qualified: 345 (18%)</li>
  <li style="margin:0 0 0 20px;">Interviews: 42</li>
</ul>
<!--[if mso]></div><![endif]-->

Note: Outlook adds extra left indent; the MSO wrapper offsets it. Table-based lists are still more reliable.

Auto-Detected Links

Some email clients auto-detect text (dates, addresses, phone numbers) and convert them to links. To style or disable these:

<style>
  /* Reset auto-detected links */
  a[x-apple-data-detectors],  /* iOS */
  .aBn,  /* Gmail */
  .unstyle-auto-detected-links a {
    border-bottom: 0 !important;
    cursor: default !important;
    color: inherit !important;
    text-decoration: none !important;
    font-size: inherit !important;
    font-family: inherit !important;
    font-weight: inherit !important;
    line-height: inherit !important;
  }
</style>

<p class="unstyle-auto-detected-links">
  Our office is at 123 Main St. Call us at 555-0123.
</p>

Prevent Text Wrapping

Use non-breaking spaces to keep words together and prevent widows/orphans:

<!-- Keep phone numbers together -->
Call&nbsp;us&nbsp;at&nbsp;555&#8209;0123

<!-- Prevent orphaned words -->
<p>This is a long sentence that should not have a&nbsp;widow.</p>

<!-- Keep names together -->
<p>Contact John&nbsp;Smith for more&nbsp;information.</p>

Images

Image Best Practices

  • Save images @2x: Create images at double size and scale down with HTML attributes for crisp display on high-DPI screens
  • Use PNG, JPG, or GIF: SVG has almost no support in email
  • Always include alt text: Essential for accessibility and when images are blocked
  • Add role="presentation" for decorative images
  • Host on CDN: Use absolute URLs (https://) for all images
  • Optimize file size: Keep images under 1MB, animated GIFs under 500KB
  • Set width AND height: HTML attributes prevent layout shift in Outlook

Responsive Images

<!-- @2x image scaled down -->
<img src="image-1200px.jpg" 
     alt="Descriptive alt text" 
     width="600" 
     height="400"
     style="display: block; width: 100%; max-width: 600px; height: auto;"
     border="0">

<!-- Decorative image -->
<img src="decoration.png"
     alt=""
     role="presentation"
     width="50"
     height="50"
     border="0"
     style="display: block;">

Background Images (with VML for Outlook)

Recommendation: Prefer solid background colors over images for reliability.

<!-- Simple solid background (recommended) -->
<td style="background:#f6f7fb; padding:20px;">
  Content here
</td>

<!-- Background image with VML fallback (use sparingly) -->
<!--[if gte mso 9]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:600px; height:400px;">
  <v:fill type="tile" src="https://cdn.example.com/background.jpg" color="#1a1a1a"/>
  <v:textbox inset="0,0,0,0">
<![endif]-->
<div style="background:url('https://cdn.example.com/background.jpg') center/cover no-repeat; background-color:#1a1a1a; padding:40px;">
  <!-- Content over background -->
  <h1 style="margin:0; font:700 24px/28px Arial, sans-serif; color:#fff;">Hero Text</h1>
</div>
<!--[if gte mso 9]>
  </v:textbox>
</v:rect>
<![endif]-->

Note: VML is complex and adds weight. Only use when a background image is essential.

Buttons and CTAs

Bulletproof Buttons

Standard Button with Hover States

<style>
  .button-td:hover,
  .button-td:focus {
    background: #0056b3 !important;
  }
  .button-a:hover,
  .button-a:focus {
    background: #0056b3 !important;
    border-color: #0056b3 !important;
  }
</style>

<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
  <tr>
    <td class="button-td" style="border-radius: 4px; background: #007bff;">
      <a class="button-a" href="https://example.com" 
         style="background: #007bff; border: 1px solid #007bff; font-family: Arial, sans-serif; font-size: 16px; line-height: 16px; text-decoration: none; padding: 14px 30px; color: #ffffff; display: block; border-radius: 4px;">
        Call to Action
      </a>
    </td>
  </tr>
</table>

Why duplicate styles? Desktop Outlook doesn't treat links as block-level elements, so we style both the <td> and <a> to ensure consistent button appearance.

Simpler Bulletproof Button

<!-- Minimal bulletproof button -->
<table role="presentation" cellpadding="0" cellspacing="0">
  <tr>
    <td bgcolor="#0b5fff" style="border-radius:4px;">
      <a href="https://example.com/report"
         style="display:inline-block; padding:12px 18px; font:700 14px/14px Arial, sans-serif; color:#fff; text-decoration:none;">
        Open Dashboard →
      </a>
    </td>
  </tr>
</table>

VML Button (for Outlook)

<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" 
             href="https://example.com" 
             style="height:48px; v-text-anchor:middle; width:200px;" 
             arcsize="10%" 
             stroke="f" 
             fillcolor="#007bff">
  <w:anchorlock/>
  <center style="color:#ffffff; font-family:Arial, sans-serif; font-size:16px; font-weight:bold;">
    Button Text
  </center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-->
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
  <tr>
    <td align="center" style="border-radius: 4px; background-color: #007bff;">
      <a href="https://example.com" style="display: inline-block; padding: 14px 30px; font-family: Arial, sans-serif; font-size: 16px; font-weight: bold; color: #ffffff; text-decoration: none; border-radius: 4px;">
        Button Text
      </a>
    </td>
  </tr>
</table>
<!--<![endif]-->

Mobile Responsiveness

Single-Column Advantage

With a single-column layout, mobile responsiveness is largely automatic. The main container naturally scales down on smaller screens. You primarily need media queries for fine-tuning padding and font sizes.

Media Queries for Single-Column Layouts

@media screen and (max-width: 600px) {
  /* Container naturally scales with single-column */
  .email-container {
    width: 100% !important;
    max-width: 100% !important;
  }
  
  /* Responsive padding */
  .mobile-padding {
    padding: 20px 15px !important;
  }
  
  /* Text sizing */
  .mobile-heading {
    font-size: 24px !important;
    line-height: 32px !important;
  }
  
  .mobile-text {
    font-size: 16px !important;
    line-height: 24px !important;
  }
  
  /* Hide on mobile */
  .mobile-hide {
    display: none !important;
  }
  
  /* Center align */
  .mobile-center {
    text-align: center !important;
  }
}

Fluid Images

<img src="image.jpg" 
     alt="Alt text" 
     width="600" 
     style="width: 100%; max-width: 600px; height: auto; display: block;"
     class="fluid-image">

Dark Mode Support

Color Scheme Meta Tags

<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">

Dark Mode Styles

@media (prefers-color-scheme: dark) {
  /* Background colors */
  .dark-bg {
    background-color: #1a1a1a !important;
  }
  
  /* Text colors */
  .dark-text {
    color: #ffffff !important;
  }
  
  .dark-muted {
    color: #cccccc !important;
  }
  
  /* Image adjustments */
  .dark-img {
    opacity: 0.8 !important;
  }
}

/* Three-way color technique */
:root {
  color-scheme: light dark;
}

Dark Mode Image Swapping

Since we can't recolor raster images, include separate images for light and dark modes:

<style>
  @media (prefers-color-scheme: dark) {
    .light-img {
      display: none !important;
    }
    .dark-img {
      display: inline-block !important;
    }
  }
</style>

<!-- Light mode image (visible by default) -->
<img src="logo-light.png" class="light-img" alt="Company Logo">

<!-- Dark mode image (hidden by default, shown in dark mode) -->
<!--[if !mso]><!-->
<img src="logo-dark.png" class="dark-img" style="display: none;" alt="Company Logo">
<!--<![endif]-->

<!-- Note: We hide the dark image from Outlook to prevent both showing -->

Dark Mode Targeting Specific Clients

/* Outlook.com dark mode */
[data-ogsc] .dark-bg {
  background-color: #1a1a1a !important;
}

/* Gmail dark mode */
u + .body .dark-bg {
  background-color: #1a1a1a !important;
}

Testing

Preflight Checklist

Before sending, validate:

  • HTML size is under 100KB
  • All links work and use absolute URLs
  • Images have alt text
  • Max width is 600-680px
  • Key content visible with images off
  • Readable at 375px width (mobile)
  • Dark mode contrast is acceptable
  • Plain text version exists
  • Unsubscribe link present

Email Testing Tools

  1. Litmus - Comprehensive email testing across 90+ clients
  2. Email on Acid - Preview and testing platform
  3. Mail Tester - Spam score testing
  4. Markup Validation - W3C validator for HTML
  5. Preview Tools - Built-in previews in ESPs

Testing Checklist

  • Desktop clients (Outlook 2013-2021, Apple Mail, Thunderbird)
  • Web clients (Gmail, Outlook.com, Yahoo, AOL)
  • Mobile clients (iOS Mail, Gmail app, Outlook app)
  • Dark mode rendering
  • Images off/blocked
  • Link functionality
  • Preheader text display (50-90 characters optimal)
  • Subject line length
  • Spam score
  • Accessibility (screen readers, alt text, semantic HTML)
  • Plain text version
  • Total email size (keep under 100KB)

Client-Specific Testing

<!-- Gmail specific -->
<style>
  u + .body .gmail-hide {
    display: none !important;
  }
</style>

<!-- Outlook conditional -->
<!--[if mso]>
  <style type="text/css">
    .outlook-specific {
      /* Outlook-only styles */
    }
  </style>
<![endif]-->

<!-- Apple Mail specific -->
<style>
  @supports (-webkit-overflow-scrolling:touch) {
    .apple-mail-specific {
      /* Apple Mail styles */
    }
  }
</style>

Accessibility Best Practices

Quick Wins for Accessible Emails

  • Font size: Body text ≥ 14px
  • Line height: ~1.4 (20px for 14px text)
  • Color contrast: Meet WCAG AA standards
  • Touch targets: Buttons/links ≥ 44px tall on mobile
  • Alt text: Descriptive for images, empty alt="" for decorative
  • Logical headings: Use h1→h2→h3 hierarchy (styled inline)
  • Real text: Never use images for critical information

Semantic HTML

Use semantic tags when possible (but style them inline):

<!-- Use semantic tags with reset styles -->
<p style="margin: 0 0 15px 0; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px;">
  Paragraph text
</p>

<strong style="font-weight: bold;">Important text</strong>
<em style="font-style: italic;">Emphasized text</em>

ARIA Attributes & Roles

<!-- Hide decorative elements from screen readers -->
<td aria-hidden="true">&nbsp;</td>

<!-- Mark layout tables as presentational -->
<table role="presentation">

<!-- Skip decorative images -->
<img src="decoration.png" alt="" role="presentation">

<!-- Descriptive alt text for content images -->
<img src="product.jpg" alt="Blue ceramic coffee mug with company logo">

<!-- Data tables need proper headers -->
<table role="table">
  <tr>
    <th scope="col">Metric</th>
    <th scope="col">Value</th>
  </tr>
  <tr>
    <td>Applicants</td>
    <td>1,926</td>
  </tr>
</table>

Plain Text Version

Always create a plain text version of your email. Benefits:

  • Helps avoid spam filters
  • Some clients only support plain text
  • Better accessibility for screen readers
  • User preference accommodation
Example Plain Text Email:
========================

Hi Sarah,

Hope you're doing well! Your November insights are ready.

Quick Wins:
- 11 hires in November
- 5 job offers extended
- 3 candidates in onboarding

View full report: https://example.com/report

Best,
Sam

To unsubscribe: https://example.com/unsubscribe
Our address: 123 Main St, City, State 12345

Compliance

CAN-SPAM Requirements

  1. Unsubscribe Link - Required and functional
  2. Physical Address - Postal address of sender
  3. Clear Sender - Accurate "From" name and email
  4. Subject Line - Not misleading
  5. Ad Disclosure - If applicable

GDPR Considerations

  • Consent documentation
  • Data processing transparency
  • Right to deletion
  • Privacy policy link

Example Footer

<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
  <tr>
    <td align="center" style="padding: 40px 20px; font-family: Arial, sans-serif; font-size: 12px; line-height: 18px; color: #666666;">
      <!-- Company info -->
      <p style="margin: 0 0 10px 0;">
        © 2024 Company Name. All rights reserved.
      </p>
      
      <!-- Address -->
      <p style="margin: 0 0 10px 0;">
        123 Main Street, City, State 12345
      </p>
      
      <!-- Links -->
      <p style="margin: 0;">
        <a href="{unsubscribe_url}" style="color: #666666;">Unsubscribe</a> | 
        <a href="{preferences_url}" style="color: #666666;">Update Preferences</a> | 
        <a href="{privacy_url}" style="color: #666666;">Privacy Policy</a>
      </p>
    </td>
  </tr>
</table>

Common Pitfalls

Avoid These Mistakes

  1. JavaScript - Not supported in any email client
  2. Forms - Limited support, link to web forms instead
  3. Video/Audio - Use animated GIFs or link to landing pages
  4. External CSS - Will be stripped by most clients
  5. CSS Grid/Flexbox/Float - No support, use tables
  6. Position absolute/fixed - Not reliable
  7. Margins for layout spacing - Outlook ignores; use padding on <td>
  8. SVG - Almost no support, use PNG/JPG
  9. Web fonts alone - Always include system font fallbacks
  10. CSS shorthand - Use longhand properties
  11. Relying on <style> tag - Some Gmail apps strip it
  12. Images for critical text - Use real text for key information

Do This / Not That

Do:

  • ✅ Tables for layout with inline styles
  • ✅ Specify image width/height attributes + CSS width:100%; height:auto
  • ✅ Use media queries only as enhancement
  • ✅ Keep width ≤ 600-680px
  • ✅ Test with images off
  • ✅ Use padding on <td> for spacing
  • ✅ Provide system font fallbacks
  • ✅ Include plain text version
  • ✅ Use absolute URLs for all links

Don't:

  • ❌ Rely on margins for spacing (Outlook ignores)
  • ❌ Use JavaScript, forms, or video tags
  • ❌ Depend on custom web fonts alone
  • ❌ Use CSS Grid, Flexbox, or floats
  • ❌ Exceed 102KB HTML size
  • ❌ Use CSS shorthand properties
  • ❌ Forget alt text on images
  • ❌ Use position: absolute/fixed

Email Size Limits & Performance

  • Gmail: Clips messages over 102KB (shows "[Message clipped] View entire message")
  • Outlook.com: 100KB recommended maximum
  • Yahoo: No specific limit but keep under 100KB
  • General rule: Keep HTML size under 100KB
  • Performance tips:
    • Remove unnecessary whitespace and comments
    • Use shorthand hex colors (#fff becomes #ffffff)
    • Host images on CDN, don't embed
    • Avoid redundant inline styles when possible
    • Test actual HTML size before sending

Image Best Practices

<!-- Always include -->
- Width and height attributes
- Alt text for accessibility
- Border="0" to prevent blue borders
- Display:block to prevent gaps
- Style="outline:none; text-decoration:none;"

<!-- Example -->
<img src="logo.png" 
     alt="Company Logo" 
     width="200" 
     height="50"
     border="0"
     style="display: block; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic;">

Special Characters

<!-- Use HTML entities -->
&copy;   <!-- © -->
&reg;    <!-- ® -->
&trade;  <!-- ™ -->
&ndash;  <!-- – -->
&mdash;  <!-- — -->
&nbsp;   <!-- Non-breaking space -->
&bull;   <!-- • -->
&rarr;   <!-- → -->

Advanced Techniques

Progressive Enhancement

<!-- Base experience for all clients -->
<table role="presentation" width="100%">
  <tr>
    <td style="padding: 20px; background-color: #f0f0f0;">
      Basic content
    </td>
  </tr>
</table>

<!-- Enhanced for modern clients -->
<style>
  @media screen and (-webkit-min-device-pixel-ratio: 0) {
    .enhanced-content {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      border-radius: 8px;
      box-shadow: 0 4px 6px rgba(0,0,0,0.1);
    }
  }
</style>

Animated GIFs

<!-- Fallback for clients that don't support animation -->
<img src="animated.gif" 
     alt="Animation description"
     width="600"
     style="display: block; width: 100%; max-width: 600px;">

<!-- Note: Keep file size under 1MB for best performance -->

Interactive Elements (Limited Support)

<!-- Hover effects (desktop only) -->
<style>
  .hover-button:hover {
    background-color: #0056b3 !important;
    transition: background-color 0.3s ease;
  }
</style>

<!-- CSS animations (limited support) -->
<style>
  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }
  
  .fade-in {
    animation: fadeIn 1s ease-in;
  }
</style>

Resources

Documentation

Templates

Tools

Additional Resources

Minimal Stats Block Example

Here's a simple, bulletproof stats section for single-column emails:

<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse; background:#fff;">
  <tr>
    <td style="padding:20px; font:14px/20px Arial, sans-serif; color:#222;">
      <h2 style="margin:0 0 12px; font:700 18px/22px Arial, sans-serif;">November Pipeline</h2>
      
      <!-- Stats table -->
      <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">
        <tr>
          <td style="padding:8px 0; border-bottom:1px solid #e6e6e6;">Applicants</td>
          <td style="padding:8px 0; border-bottom:1px solid #e6e6e6; text-align:right; font-weight:bold;">1,926</td>
        </tr>
        <tr>
          <td style="padding:8px 0; border-bottom:1px solid #e6e6e6;">Qualified</td>
          <td style="padding:8px 0; border-bottom:1px solid #e6e6e6; text-align:right; font-weight:bold;">345 (18%)</td>
        </tr>
        <tr>
          <td style="padding:8px 0;">Hires</td>
          <td style="padding:8px 0; text-align:right; font-weight:bold;">12</td>
        </tr>
      </table>
    </td>
  </tr>
</table>

Conclusion

Single-column HTML email development is the most reliable approach. Always:

  1. Start with proven single-column templates
  2. Stack content vertically in rows
  3. Inline all critical CSS
  4. Test with images off
  5. Keep HTML under 100KB
  6. Use tables for everything
  7. Test in Outlook, Gmail, and Apple Mail minimum

For this project: The single-column layout in email-template.html is already optimized. Use this guide to understand why certain patterns work and how to maintain them.

Remember: Single-column emails work everywhere, load fast, and are naturally mobile-friendly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment