必须用内联CSS,因为Gmail、Outlook(Windows版)、Apple Mail等主流客户端会主动剥离<head>和<style>标签,并过滤class/id属性,仅保留style="..."属性生效;juice等工具需预处理CSS、手动加载文件并保留!important,且最终须用table布局、显式vertical-align、字体兜底,并经真实客户端测试。

为什么必须用内联CSS而不是
因为 Gmail、Outlook(尤其是 Windows 桌面版)、Apple Mail 等主流客户端会直接剥离 <head>、<style> 标签,甚至过滤掉 class 和 id 属性。不是它们“不支持CSS”,而是主动清除——防止样式干扰自身UI或被用于追踪。所以 <link rel="stylesheet"> 和 <style>body{...}</style> 在发出去那一刻就失效了,只剩 style="..." 能活下来。
juice.inlineContent() 的三个关键陷阱
Node.js 下最常用的是 juice,但它默认行为在邮件场景下容易翻车:
-
juice不处理@media查询,响应式规则必须提前用postcss-preset-env拆成两套独立 CSS(桌面/移动端),再分别喂给它 - 它保留
!important,但 Outlook 某些版本(如 Outlook 2016 for Windows)只认带!important的样式,建议所有关键样式手动加,比如color: #333 !important; - 它不自动读取
<link href="email.css">,必须自己用fs.readFileSync('email.css', 'utf8')加载内容,再传进juice.inlineContent(html, css, {...})
推荐调用写法:
const juice = require('juice');
const html = fs.readFileSync('template.html', 'utf8');
const css = fs.readFileSync('email.css', 'utf8');
const inlined = juice.inlineContent(html, css, {
preserveImportant: true,
webResources: { images: false } // 邮件图片必须用绝对 URL,别让 juice 转 base64
});
CssToInlineStyles 在 PHP 项目里怎么避坑
如果你用的是 PHP(比如 Laravel 或 Symfony 邮件系统),CssToInlineStyles 是更自然的选择,但它也有几处硬性约束:
立即学习“前端免费学习笔记(深入)”;
- 它能解析
<style>块,但对@import和外部<link>仍无感,CSS 内容得先合并好再传入 - 它默认不处理
!important优先级冲突,若原始 CSS 里有多个选择器命中同一元素,且其中含!important,结果可能和预期不一致——建议预处理时统一清理或加固关键规则 - 对
font-family这类复合值,它有时会漏掉引号包裹空格(如把"Helvetica Neue", Arial变成Helvetica Neue, Arial),导致 Outlook 解析失败,需后置正则校验
内联完还不能直接发:Outlook 和 Gmail 的二次校验点
内联只是起点,真实客户端还会做自己的“魔改”:
- Outlook Windows 版会把
<div>强制替换成<p>,所以所有布局容器必须用<table>,别信语义化标签 - 每个
<td>必须显式写style="vertical-align: top;",否则 Outlook 默认居中,多行内容会错位挤压 - Gmail 会删掉 HTML 中所有换行和多余空格,导致长
style值粘连(如color:#333;font-size:14px;line-height:1.5变成一行),检查时要盯紧生成后的源码是否可读 - 所有图片必须带
width和height属性,且用绝对 URL;<img src="/logo.png">这种相对路径在 Gmail 里直接不显示
真正上线前,唯一靠谱的验证方式是:用真实邮箱(Gmail、Outlook.com、iOS Mail、Outlook for Windows)逐个收信看渲染,截图比对——工具能帮你内联,但客户端永远留着最后一道黑盒。



















