How to embed custom fonts in html email templates
- Step 1Subset to the exact characters the template uses — Email copy is short — you rarely need full Latin coverage. Subset your brand font to Latin Basic + numerals + the punctuation your template actually contains using [font-subsetter](/font-tools/font-subsetter) (or a precise glyph set via [character-whitelist-builder](/font-tools/character-whitelist-builder)). A tight subset can land at 2-4 KB after WOFF2 + base64.
- Step 2Use WOFF2 only — never stack formats — Inline one WOFF2 weight. Every extra inlined format (WOFF, TTF) adds another +33% to an email that's fighting a clip limit, and the clients that render inlined fonts all support WOFF2. Convert to WOFF2 first with [ttf-to-woff2](/font-tools/ttf-to-woff2) if you only have a TTF.
- Step 3Encode the WOFF2 — Drop the subset WOFF2 onto [font-to-base64](/font-tools/font-to-base64) and Process. You get a complete `@font-face` block — quoted single-line data URI, `format("woff2")`, `font-display: swap`. The base64 stays on one line, which is what email-client and any downstream CSS handling want.
- Step 4Put the @font-face in a <style> in <head> — Drop the block inside a `<style>` tag in the email's `<head>`. Rename the family from the filename stem to your brand name and, if it's a single weight, add `font-weight`/`font-style` yourself — the encoder emits only family, src, and font-display.
- Step 5Reference the font with a system fallback everywhere — Use a fallback stack on every element that uses the font, including inline `style=""` attributes for client compatibility: `font-family: "Brand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;`. Outlook desktop strips the `@font-face` and shows the fallback — design so the email still reads like itself.
- Step 6Send a real-client test before the broadcast — Inbox previews vary by client and OS. Send the email to actual Apple Mail, iOS Mail, Gmail (app and web), and Outlook desktop accounts — or a testing service — and confirm both the inlined font and the fallback look right. Check total message size stays under the provider clip threshold.
Inlined @font-face support across email clients
General support for a base64-inlined @font-face in a <style> block. Behaviour shifts with client version and OS; always test before a broadcast.
| Client | Inlined @font-face? | Notes |
|---|---|---|
| Apple Mail (macOS) | Yes | Reliable WebKit rendering of inlined @font-face |
| iOS Mail | Yes | Same WebKit engine; the most consistent target |
| Outlook on Windows (desktop) | No | Word HTML engine strips every @font-face — fallback only |
| Outlook for Mac / Outlook.com | Often | Better than desktop; still test per version |
| Gmail (app + web) | Partial | Renders custom fonts in some contexts; treat the fallback as the baseline |
| Android default mail | Varies | Depends on the app and WebView version; test the ones your audience uses |
Size math for an email font
Why aggressive subsetting matters: providers clip large messages, and base64 adds a fixed third on top of the font.
| Stage | Typical size | Note |
|---|---|---|
| Full brand WOFF2 (all weights) | 60-150 KB | Far too big to inline in email |
| One weight, full Latin, WOFF2 | 15-30 KB | Still heavy once base64'd |
| One weight, tight subset, WOFF2 | 2-4 KB | The target for email |
| Same subset after base64 (+33%) | ~3-5 KB | What actually rides in the @font-face |
| Gmail HTML clip threshold | ~102 KB total | Your whole message, not just the font — budget accordingly |
What the encoder gives you vs what email needs
The tool emits a minimal block; email best practice adds a couple of things on top.
| Element | Encoder emits? | For email |
|---|---|---|
font-family (from filename) | Yes | Rename to your brand name |
| Quoted single-line data URI | Yes | Exactly right — keep it one line |
font-display: swap | Yes | Fine; shows fallback then swaps |
font-weight / font-style | No | Add per weight you inline |
| System fallback stack | No | Add in your CSS / inline styles — mandatory for Outlook desktop |
Cookbook
Email-ready patterns. The base64 payload is shown as <BASE64> — the encoder emits the full single-line string in the quoted url("...").
The minimal email @font-face with fallback
ExampleA <style> block in <head> plus a fallback on every styled element. This is the safe baseline — Outlook desktop sees the fallback, everyone else sees Brand.
<head>
<style>
@font-face {
font-family: "Brand";
font-weight: 400;
src: url("data:font/woff2;base64,<BASE64>") format("woff2");
font-display: swap;
}
</style>
</head>
<body>
<td style="font-family:'Brand',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:16px">
Welcome aboard.
</td>
</body>Two weights, inlined and distinguished
ExampleIf you must inline regular and bold, encode each subset weight separately and give each its own font-weight so the client maps bold correctly.
@font-face {
font-family: "Brand";
font-weight: 400;
src: url("data:font/woff2;base64,<BASE64_REGULAR>") format("woff2");
font-display: swap;
}
@font-face {
font-family: "Brand";
font-weight: 700;
src: url("data:font/woff2;base64,<BASE64_BOLD>") format("woff2");
font-display: swap;
}Outlook-aware: give desktop a clean fallback
ExampleOutlook desktop strips @font-face, so the email must look intentional in the system font. Use a fallback that matches your brand's tone (a serif fallback for a serif brand, etc.).
/* serif brand, serif fallback for stripped clients */
td, p, h1 {
font-family: "BrandSerif", Georgia, Cambria,
"Times New Roman", Times, serif;
}
/* sans brand, sans fallback */
td, p, h1 {
font-family: "BrandSans", -apple-system,
BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
}Subset to exactly the template's characters
ExampleSubsetting before encoding is what keeps the email small. Feed the subsetter only the glyphs the email uses, then encode the result.
Workflow:
1. List the characters the template renders
(headline + body + numerals + punctuation)
2. font-subsetter (latin) OR character-whitelist-builder
with that exact set -> Brand-subset.woff2 (~3 KB)
3. font-to-base64 on Brand-subset.woff2
-> @font-face block, ~4 KB base64
4. paste into <style>, add font-weight + fallbackSize check before sending
ExampleConfirm the whole message stays under the provider clip threshold. The font is only part of the budget.
Message budget (Gmail clips around ~102 KB of HTML): HTML markup + inline CSS ....... ~12 KB inlined @font-face (subset) .... ~4 KB ------------------------------------------- total .......................... ~16 KB OK If total approaches ~102 KB, Gmail shows [Message clipped] and a 'View entire message' link.
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Outlook on Windows desktop strips @font-face entirely
Stripped by Word engineOutlook desktop renders through Microsoft Word's HTML engine, which removes every @font-face rule — inline or external. There is no workaround that gets your brand font into Outlook desktop; even VML tricks don't restore web fonts. Design the fallback so the email reads like itself, and accept that a meaningful share of B2B recipients will see the system font.
Gmail clips the message and the font with it
Clipped — fails over ~102 KBGmail clips messages over roughly 102 KB of HTML, showing [Message clipped] with a link. If your inlined font pushes the total past that, content below the clip — possibly including the styled section — is hidden until the recipient expands it. Subset hard and inline one weight so the font is a few KB, not tens.
Full Latin coverage is overkill and bloats the email
Avoid — subsetInlining a full-Latin WOFF2 (15-30 KB, ~20-40 KB after base64) wastes most of the budget on glyphs the email never shows. Subset to the exact characters with font-subsetter or character-whitelist-builder first — a tight subset is an order of magnitude smaller.
Stacking WOFF + TTF fallbacks multiplies the size
AvoidAdding WOFF and TTF copies to the same @font-face for 'compatibility' triples the inlined bytes — and the clients that render inlined fonts all support WOFF2, so the extra formats never get used. Inline WOFF2 only; the non-rendering clients (Outlook desktop) wouldn't use any format anyway.
Same font-family for multiple weights without font-weight
Faux-bold renderingIf you inline regular and bold under one font-family but omit font-weight, the client can't distinguish them and may synthesise faux-bold from the regular outlines. The encoder doesn't emit font-weight — add it to each block (400, 700) so the client maps real weights.
Family name comes from the filename, not the brand
By designThe encoder names the family from the uploaded filename stem, so Brand-subset.woff2 becomes "Brand-subset". Rename it to your brand name in the @font-face and in every font-family reference, or the references won't match. A one-line edit before you paste.
Tracking a font load in email is not possible
By design / privacyEmail clients block tracking pixels and external resources, and an inlined base64 font fires no request at all — so there's no analytics signal for whether the font rendered. That opacity is also the privacy upside: no font CDN sees your recipients. Verify rendering with real-client tests, not telemetry.
Inline style attributes are needed, not just the <style> block
Client-dependentSome clients strip or ignore <style> blocks but honour inline style="" attributes. Best practice in email is to set font-family (with the fallback) inline on each text element in addition to the @font-face in <head>, so the family reference survives clients that drop the style block.
A line-wrapped base64 payload can break in downstream CSS tooling
Keep it single-lineThe encoder emits the base64 on one line, which is correct. If your email build runs the CSS through a minifier like lightningcss, a wrapped payload can be dropped entirely. Keep the base64 contiguous through your whole email build pipeline.
Apple Mail dark mode can recolor text unexpectedly
Client behaviour — not a font bugApple Mail may apply its own dark-mode color transforms to email text, which can wash out a carefully chosen brand color regardless of the font. This isn't a base64 issue, but it affects how your inlined font reads — test in dark mode and set explicit colors. For dark-mode type tuning generally, see dark-mode-font-adjuster.
Frequently asked questions
Why doesn't Outlook on Windows render my embedded font?
Outlook desktop uses Microsoft Word's HTML rendering engine, which strips every @font-face rule — inline or external. There is no reliable workaround. The fix is to design a strong system-font fallback so the email still looks intentional, and accept that desktop-Outlook recipients see the fallback. Apple Mail, iOS Mail, and Gmail's apps do render inlined fonts.
What's the size budget for an email font?
Tight. Many providers clip messages around 100-102 KB of HTML (Gmail's clip is ~102 KB), and that's the whole message, not just the font. Subset aggressively and inline one WOFF2 weight so the base64'd font is roughly 3-5 KB. Full Latin coverage is almost always overkill for short email copy.
Should I include WOFF or TTF fallbacks?
No — inline WOFF2 only. TTF and WOFF data URIs are ~33% larger than the binary, and stacking 2-3 formats explodes the email size against a clip limit. Every email client that renders inlined fonts supports WOFF2; the ones that don't (Outlook desktop) wouldn't use any format.
How do I make the font small enough?
Subset before encoding. Use font-subsetter for a named range like Latin Basic, or character-whitelist-builder for the exact glyph set your template uses. Then encode the subset WOFF2 with font-to-base64. A tight subset can drop a 20 KB font to 2-4 KB.
Can I track whether the font loaded?
No. Email clients block tracking pixels and external resources, and an inlined base64 font fires no network request at all, so there's no analytics hook. That opacity is also why inlined fonts are privacy-friendly — no font CDN ever sees your recipients. Verify rendering with real-client tests instead.
Where exactly do I put the @font-face?
In a <style> block inside the email's <head>. Then reference the family with a fallback stack on each styled element, including inline style="" attributes, since some clients honour inline styles but strip <style> blocks. The fallback is what Outlook desktop and other non-supporting clients display.
Does the encoder give me everything I need for email?
Almost. It emits font-family (from the filename), the quoted single-line data URI with format("woff2"), and font-display: swap. For email you should rename the family to your brand, add font-weight/font-style per inlined weight, and add the system fallback in your references — the encoder doesn't do those last steps.
Will the email still render years from now?
Yes — that's a real advantage of inlining. The font travels inside the message, so there's no hosted-font CDN that could go offline or change. A transactional receipt opened five years later still shows the brand font in supporting clients, with the fallback elsewhere.
Why is the @font-face family name wrong?
The encoder derives it from your uploaded filename stem — Brand-subset.woff2 becomes "Brand-subset". Rename it to your brand in the @font-face rule and update every font-family reference to match. It's a one-line edit before pasting into the template.
Does Gmail support custom fonts at all?
Partially and inconsistently — it renders custom fonts in some contexts and not others, varying by app vs web and over time. Treat the system fallback as your baseline experience for Gmail and consider the custom font a progressive enhancement. Always send a real Gmail test before broadcasting.
Is base64 inlining the right approach outside email too?
Usually not. On the open web, an external preloaded WOFF2 is faster and cacheable; inlining's render-blocking and caching costs make it a net loss there. Email is special because external @font-face is stripped. The full web comparison is in base64 fonts vs external files.
Can I script the encoding into my email build?
Yes — encode the subset WOFF2 at build time and inject the @font-face into the template. Use Node's toString('base64') (single-line) or drive JAD's tool via the runner: pair once and POST to 127.0.0.1:9789/v1/tools/font-to-base64/run. The build-time approach is detailed in the automation guide.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.