Skip to main content

Email Typography and Web Fonts: Stacks, Metrics, and Client-Safe Type Scales

Build type systems that survive email clients: metric-matched fallback stacks, MSO line-height control, mobile font inflation, and a scale that renders identically everywhere.

Typography is where email design most often quietly falls apart. The layout survives, the colours survive, and then a headline set in a webfont renders in Times New Roman two sizes too large, pushing a two-line heading onto three lines and breaking the spacing of everything beneath it. This section covers how type is actually resolved in email clients, and how to build a scale that produces the same rhythm whether or not your font loads.

The problem is that email has no single font-loading model. One client downloads a webfont, one uses a locally installed copy if it happens to exist, one ignores @font-face completely, and one rewrites your font size on the way to the screen. A type system that assumes any of those behaviours in isolation will break in the others.

How Type Is Resolved, Client by Client

Four distinct resolution paths exist, and every font declaration you write travels down one of them.

WebKit clients — Apple Mail on macOS and iOS Mail — support @font-face with a remote src, resolved from the embedded style block. They are the only mainstream email clients where a genuine webfont reliably arrives.

Gmail strips remote @font-face sources but honours local(), so a family already installed on the device resolves; anything else falls through to the next entry in the stack. In practice this means Gmail shows your brand font to the small population who happen to have it installed and the fallback to everyone else.

The Word engine in Outlook 2016 through 2021 on Windows ignores @font-face entirely and, worse, ignores the rest of a font-family stack once it encounters a name it does not recognise — falling back to Times New Roman rather than to the next entry. This is the failure mode that produces serif headings in an otherwise sans-serif design.

Server-side rewriters such as Outlook.com and Yahoo keep the declaration but rewrite selectors around it, so a font applied through a class may or may not survive; a font applied inline always does.

Font resolution paths by client family WebKit downloads the font, Gmail resolves only local copies, the Word engine ignores the rule and falls back to a serif default, and rewriters keep inline declarations only. One Declaration, Four Outcomes WebKit Apple Mail, iOS Mail downloads the WOFF from the style block brand font shown as designed Gmail web and mobile apps strips remote src, honours local() only brand font if installed, else the fallback Word engine Outlook 2016-2021 ignores @font-face and abandons the stack Times New Roman — not your fallback Rewriters Outlook.com, Yahoo rewrites class-based rules unpredictably inline declarations always survive
The Word engine's fallback is the dangerous one: it does not walk your stack, it jumps straight to a serif default.

Core Implementation: A Stack That Degrades Predictably

The fix for the Word engine is an MSO conditional that overrides the family before the engine ever sees the name it will choke on. Everything else is served by a carefully ordered stack.

<style>
  /* Layer 1 — the webfont, for the clients that will actually fetch it.
     local() first avoids a network round-trip when the family is installed
     (Gmail resolves this and nothing else). */
  @font-face {
    font-family: 'BrandSans';
    src: local('BrandSans'),
         url('https://cdn.example.com/brandsans-400.woff2') format('woff2'),
         url('https://cdn.example.com/brandsans-400.woff') format('woff');
    font-weight: 400;
    font-style: normal;
    font-display: swap;   /* Apple Mail: paint the fallback, swap on arrival */
  }

  /* Layer 2 — the stack every other client walks. Ends in a generic family so
     no client is left choosing for itself. */
  .body-text {
    font-family: 'BrandSans', -apple-system, 'Segoe UI', Roboto,
                 'Helvetica Neue', Arial, sans-serif;
  }
</style>

<!--[if mso]>
<style>
  /* Outlook 2016-2021 (Word engine): it cannot render BrandSans and will fall
     back to Times New Roman rather than to the next stack entry, so name a
     font it definitely has. Arial is present on every Windows install. */
  .body-text, td, th, p, h1, h2, h3, a, span {
    font-family: Arial, sans-serif !important;
  }
</style>
<![endif]-->

The local() entry first is not a micro-optimisation. It is the only reason a Gmail recipient with the family installed sees the brand font at all, and it saves a network fetch for the WebKit clients where the font is already present.

The other half of the fix is choosing a fallback whose metrics match the webfont. When the fallback is wider or has a different x-height, the swap reflows text the reader is already looking at, and every line-break decision changes — a headline that fitted on two lines in your design occupies three in the fallback. Comparing cap height and average character width between the brand font and each candidate fallback, then picking the closest, keeps the layout stable in both states. The mechanics of that comparison are covered in detail under web font fallbacks for Apple Mail.

Line Height Is the Second Failure

Font family gets the attention; line height causes as many broken layouts. The Word engine computes leading from the font's own metrics and ignores a unitless line-height, so a value like 1.5 produces whatever the engine feels like — usually tighter than intended, occasionally much looser.

<!-- Set line-height in absolute units AND give the Word engine its own
     directive. mso-line-height-rule:exactly stops it recomputing from the
     font metrics; without it, the px value below is treated as a minimum. -->
<td style="font-family:Arial,sans-serif;
           font-size:16px;
           line-height:24px;
           mso-line-height-rule:exactly;">
  Body copy that keeps a 24px rhythm in Outlook 2016-2021 as well as everywhere else.
</td>

Two rules follow from this. Always express line-height in pixels rather than as a unitless multiplier, and always pair it with mso-line-height-rule: exactly on any element carrying text. The multiplier is more maintainable on the web precisely because browsers recompute it; in email that recomputation is the bug.

Unitless versus exact line height in the Word engine A unitless multiplier lets the Word engine recompute leading from font metrics, compressing the paragraph, while an exact pixel rule preserves the intended rhythm. The Same Paragraph, Two Leadings line-height: 1.5 The Word engine recomputes leading from the font metrics — lines compress and the vertical rhythm no longer matches design. line-height: 24px + mso rule The exact rule pins the leading, so the rhythm survives every engine.
Absolute leading plus the MSO exact rule is the only combination that produces the same vertical rhythm in every client.

Mobile Font Inflation

Both iOS and the Gmail Android app enlarge text they judge too small to read, and the enlargement is not proportional — it applies per block, so one paragraph grows and its neighbour does not, breaking a carefully built scale.

The controls are per-platform. -webkit-text-size-adjust: 100% on the body container stops iOS resizing. Android's WebView respects text-size-adjust, but the more reliable protection is simply never setting a body size below 14px, since inflation is triggered by a size threshold rather than by anything you can disable. The same applies to a responsive layout's mobile breakpoint: increase type at narrow widths deliberately rather than letting the client do it for you.

Client Webfont support Line-height handling Inflation behaviour
Apple Mail (macOS) Full @font-face Honours all units None
iOS Mail Full @font-face Honours all units Enlarges below ~13px unless text-size-adjust is set
Gmail (web) local() only Honours all units None
Gmail (Android app) local() only Honours all units Enlarges small text per block
Outlook 2016/2019/2021 None — falls back to serif Requires mso-line-height-rule None
Outlook 365 (WebView2) Full @font-face Honours all units None
Samsung Email local() only Honours all units Enlarges small text

Building the Scale

A type scale for email should be shorter than one for the web — four or five steps, all in absolute pixels, each paired with an exact line height. Fewer steps means fewer places for a client to disagree with you, and absolute values remove the recomputation that causes drift.

Set the scale once as a set of design tokens and apply them inline through your templating layer rather than through classes, so the rewriters cannot strip them. The token approach also keeps the scale consistent across templates, which is what shared component libraries are for.

  1. Define the scale in absolute pixels — for example 28/20/16/14/12 — and pair each size with a fixed line height.
  2. Pick the fallback by metrics, not by aesthetics, and verify a two-line headline stays two lines in both states.
  3. Attach the MSO conditional override naming a font present on every Windows install.
  4. Set mso-line-height-rule: exactly on every element that carries text, not only on paragraphs.
  5. Apply sizes inline through the template layer so server-side rewriters cannot drop them.
  6. Guard against inflation with -webkit-text-size-adjust and a 14px floor for body copy.
  7. Verify in both font states — with the webfont loaded and with it blocked — as a standing item in your rendering QA pass.

Debugging: Named Symptoms, Cause, and Fix

Symptom: headings render in a serif face in Outlook on Windows only. Cause: the Word engine hit an unknown family name and fell back to Times New Roman instead of walking the stack. Fix: add the MSO conditional block naming Arial or another guaranteed font, and confirm it targets headings as well as body elements.

Symptom: line spacing is tighter in Outlook than everywhere else. Cause: a unitless line-height, recomputed from font metrics. Fix: convert to pixels and add mso-line-height-rule: exactly.

Symptom: one paragraph is noticeably larger than the rest on an Android phone. Cause: per-block text inflation triggered by a size below the client's readability threshold. Fix: raise the body size to at least 14px; the enlargement is not disabled by any property on that platform.

Symptom: the headline wraps to three lines for some recipients and two for others. Cause: a fallback font with different metrics from the webfont. Fix: choose a metrically closer fallback, or set the headline size so the longest expected string fits on two lines in the widest candidate.

Symptom: font sizes disappear entirely in Outlook.com. Cause: sizes applied through a class in the embedded style block, which the rewriter dropped. Fix: apply type inline at the element level; keep the style block for the @font-face rule and media queries only.

Validation and Deployment Checklist

Measurement, Colour, and the Rest of the Type System

Family, size and leading are the properties people remember to set. Three more decide whether the result is actually readable in an inbox, and each has an email-specific constraint that does not exist on the web.

Measure — the number of characters on a line — is fixed by your container width rather than by a CSS property, because email layouts are built at a known pixel width instead of a fluid one. At the conventional 600px content width with 16px body copy, a single-column paragraph runs to roughly 75 characters, which is at the upper end of comfortable. Adding horizontal padding is the lever: 24px on each side brings the measure down to a more readable range without changing the type size. Where a template uses two columns, each column is around 35 characters wide, which is too narrow for body copy and should be reserved for labels and short values. This is one of the places where a responsive layout's stacking behaviour matters for typography rather than for structure: a two-column row that stacks on mobile goes from an unreadably narrow measure to a comfortable one, so the mobile rendering is often the better-typeset of the two.

Colour and contrast interact with type size through the WCAG thresholds. Body copy needs 4.5:1 against its background; text at 18.66px bold or 24px regular qualifies as large and needs only 3:1. The practical consequence in email is that muted secondary text — the grey used for timestamps, disclaimers and footer copy — is the single most common accessibility failure, because the shade chosen to look unobtrusive against white usually lands between 3:1 and 4.5:1. Fix it by darkening the muted colour rather than by enlarging the text, since footers are rarely a place where larger type is wanted. The same pairing has to hold in dark mode, where a muted grey that reads correctly on white becomes low-contrast against a dark surface; the paired-value approach described under dark mode email CSS is what keeps both states compliant.

Weight and emphasis are constrained by what the fallback family actually contains. A brand font may ship six weights; Arial has two. A design that distinguishes a heading from a subheading by weight alone therefore collapses into a single appearance in the Word engine, where both render as bold. Build hierarchy from size and spacing first, with weight as a secondary signal, so the structure survives when the weight distinction does not. The same reasoning rules out relying on a light weight for emphasis of any kind: font-weight: 300 renders as regular wherever the family lacks that weight, which is most of the time.

Line measure at common email widths A 600 pixel single column runs to about 75 characters, adding 24 pixel padding brings it to about 66, and a two-column split drops each column to about 35. Measure Is Set by the Container, Not by CSS Approximate characters per line at 16px body copy 600px, no padding — about 75 characters loose 600px with 24px padding — about 66 characters ideal column — about 35 column — about 35 too narrow
Padding is the only practical measure control in a fixed-width layout, and two-column rows are too narrow for body copy at any size.

The three properties compound. A two-column row containing 14px muted grey text at a 35-character measure is simultaneously too narrow, too small and too low-contrast — and each of those is individually within the range a designer might reasonably choose. Reviewing them together, rather than one property at a time, is what catches the combination.

Frequently Asked Questions

Is it worth shipping a webfont at all when so few clients load it?

It depends on your audience mix. Apple Mail and iOS Mail together account for a large share of consumer opens, and those clients do render the font — so a brand that matters visually gets real value. What is never worth it is designing for the webfont state: build the layout so it is correct in the fallback, then treat the webfont as an enhancement that a majority of recipients will not see.

Why does the Word engine fall back to a serif rather than to the next font in my stack?

Because it does not implement font-family as a prioritised list in the way browsers do. When it encounters a family it cannot resolve, it stops evaluating and applies its own document default, which is a serif face. This is why the conditional override is mandatory rather than defensive — no ordering of the stack can produce the right result on its own.

Can I use variable fonts in email?

Only in the WebKit clients, and the file-size cost is usually not justified. A variable font carries every weight in one file, which is efficient on the web where you use many; email templates typically need two, so two static WOFF2 files are smaller than one variable font. The weights also have to be declared as separate @font-face rules for clients that do not understand variable axes.

Should I set font size on the table cell or on a wrapper element?

On the cell. Inheritance is unreliable in email — several clients reset font properties on table elements — so the safe pattern is to declare family, size and line height on every td that contains text, rather than relying on a parent to pass them down. The repetition is verbose by hand, which is exactly why it belongs in a component or partial.

How do I test what the fallback actually looks like?

Block the font's domain in your preview environment, or temporarily point the src at a URL that returns a 404. Both force the fallback path in clients that would otherwise fetch the file, so you see the layout the majority of your recipients get. Doing this as a deliberate second pass catches metric mismatches that never appear in a normal preview.

Do email clients support font-display?

Apple Mail honours it, which is the only place it matters, since it is the only client fetching a remote font. Use swap so the fallback paints immediately and the webfont replaces it on arrival — the alternative, invisible text while the font downloads, is a worse experience in a message the reader opened deliberately.


← Back to Mastering Email HTML & CSS