Skip to main content

邮件 CSS 与网页样式的差异

同一封事务邮件,在网页里用 flex 排得很整齐,进了 Outlook 桌面端就散掉。那边的排版引擎是 Word,不是浏览器。邮件样式得按最差的客户端写,不能按 Chrome 里看到的效果交。

1. 邮件客户端停在哪个年代​

邮件没有统一的渲染引擎:Outlook 桌面端用的是 Word 的排版引擎,Gmail 会清洗 <style> 并改写类选择器,各家的安全与隐私策略也不同(外链图片默认拦截是常态)。所以写邮件模板的第一条原则是——按支持度最差的客户端来写,而不是按平均值来写。这也意味着现代布局里的 flex、grid、position 在这里统统不可用,布局必须回到 table。

同一封邮件,要同时活在这几种渲染环境里经典 Outlook新 OutlookGmailApple Mail / iOS可行的写法 = 这些引擎能力的交集交集之外都在赌:table 布局、内联样式、系统字体、不依赖 flex / grid / position 的排版。所以邮件模板的取舍不是「现代还是老土」,而是「最小可行集有多大」。
图:邮件的写法由最弱的那个引擎决定——先取交集,再谈优化

2. 只能用的那套老写法​

想用的实际要做的
flex / grid / float 布局table + td 搭栅格
写在 <style> 里的类选择器尽量内联到 style 属性(部分客户端会剥离或改写)
外链 Web 字体用系统字体,回退要提前设计
伪元素 / position / transform一律不用

2.1 表格上的必写属性​

表格布局的必写项:role="presentation" 避免读屏读成数据表、border/cellpadding/cellspacing 归零、图片 display:block 并带 alt。

2.2 预览文本​

还有一个位置最容易被忘:preheader(预览文本)。收件箱列表里跟在标题后面那行摘要,取的往往是正文最靠前的文字——如果开头是「在浏览器中查看」,这行摘要就废了。通行做法是在正文最前面放一段隐藏的预览文字,再用一串空白字符把它与真正的内容隔开,让客户端取它当摘要、却不渲染进版面。

3. 各客户端的差异点​

固定宽度 + 最大宽度居中(600px 是行业惯例),移动端靠媒体查询兜底。Outlook 需要条件注释处理行高与间距,图片必须带尺寸属性否则 Outlook 会按原始尺寸撑开。

<!--[if mso]>
<table role="presentation" width="600" cellpadding="0" cellspacing="0"><tr><td>
<![endif]-->
<div style="max-width:600px;margin:0 auto;padding:16px;">
<img src="hero.png" width="600" height="200"
alt="活动主视觉" style="display:block;width:100%;height:auto;" />
</div>
<!--[if mso]></td></tr></table><![endif]-->

3.1 别让客户端自动反色​

深色模式不要交给客户端自动反色——它会把对比度搞坏。应显式声明 color-scheme 与支持方案,并自己写好深色下的前景与背景色。

<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<!-- 显式声明颜色,避免客户端自动反色 -->
<p style="color:#111827;background:#ffffff;">您的订单已发货</p>

3.2 按钮背景落在单元格上​

按钮是另一个重灾区:用 <a> 加 border-radius、background 拼出来的「按钮」在 Outlook 里会丢样式、变成方块。可靠做法是「带背景色的 td + 里面的链接」——背景色落在单元格上 Outlook 才认:

<table role="presentation" cellpadding="0" cellspacing="0"><tr>
<td bgcolor="#2563eb" style="border-radius:6px;">
<a href="https://example.com" style="display:block;padding:12px 24px;color:#fff;font-weight:600;text-decoration:none;">查看订单</a>
</td>
</tr></table>

补充一个时间线:微软已宣布桌面端 Outlook 将在 2026 年 10 月停止 Word 渲染引擎,迁移到基于 Web 的「新 Outlook」引擎。但企业升级周期很长,经典桌面 Outlook 会存在很多年,所以「table + 内联」这套写法到 2027 年乃至更久仍是必须,不能因为「新 Outlook 支持 flex」就放松。

提交前加一道自检,能拦掉大部分「本地看着没问题、发出去就乱」的模板:

const html = renderTemplate(data)

// Gmail 会清洗 style 标签,未内联的样式等于没有
if (/<style[\s\S]*?>/.test(html)) throw new Error('存在未内联的 style 标签')

// 外链图片被拦截是常态,缺 alt 时用户看到的就是一片空白
const imgs = [...html.matchAll(/<img[^>]*>/g)]
if (imgs.some((m) => !/alt=/.test(m[0]))) throw new Error('存在无 alt 的图片')

4. 模板怎么组织和测试​

手写兼容 HTML 是不可持续的:条件注释、内联样式、属性补齐全靠人肉,改一次错一次。正确做法是用 MJML、maizzle 这类模板语言写源码,由编译器产出兼容 HTML 并自动内联样式。

<!-- 源码:MJML -->
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>订单已发货</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
<!-- 编译后:table 布局、样式内联、Outlook 条件注释都由编译器补上 -->
<table role="presentation" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td style="padding:16px;font-family:sans-serif;font-size:16px;">订单已发货</td>
</tr>
</table>

多语言邮件还要单独过一遍:同一句话在德语、芬兰语下能长出一倍,固定宽度的表格单元格会被撑破,这不是翻译问题而是排版问题,必须逐语言预览。整体流程是:模板语言写源码 → 编译 → 自动内联 → 多客户端预览(含不同语言文案长度)→ 检查布局有没有被撑破。

预览不能只看桌面客户端:移动端 Gmail、Apple Mail、各桌面客户端对媒体查询与暗色声明的支持差异很大,要在真实客户端或渲染测试服务里各跑一遍。图片也要单独验收——缺失 alt、尺寸不对、被隐私策略拦截,在部分客户端会直接显示破图或占位块,破坏整封邮件的观感。沉淀一套「编译 → 内联 → 多客户端预览 → 多语言长度检查 → 图片验收」的清单,比每次手写再返工省得多。

5. 一封订单邮件在 Outlook 里怎么塌​

网页上用 flex 把商品图和标题排成一行,Outlook 桌面端的排版引擎是 Word,flex 被丢掉,图片和标题上下叠在一起,有时还会多出一段空白。Gmail 会改写 <style> 里的类名,写在 <head> 里的选择器不一定还指向原来的元素。

订单邮件用表格撑结构。外层一张定宽表,比如 600 像素,中间一行两列:左列固定 96 像素放图,右列放标题和价格。宽度写在 td 的属性和内联样式上,各写一遍。背景色、字号、行高也内联。按钮用表格单元格加内联背景,不用 <button>。

<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="96"><img src="https://cdn.example.com/sku.jpg" width="80" height="80" alt="" /></td>
<td style="font-family:Arial,sans-serif;font-size:14px;">订单商品</td>
</tr>
</table>

图片地址用绝对 URL。多语言时德语会比中文长出一截,列不要写死高度,让单元格被文字撑开。发出去之前用实际的九种文案各预览一次,只看中文稿过不了。

某个属性在 Outlook 里到底能不能用,别猜,直接查 Can I email——它按客户端逐个列出支持情况。