虽然 IE 退出了浏览器的历史舞台,但是 Windows 平台上的 Outlook 依然继承了 IE 的衣钵,继续在邮件客户端界发光发热。
最好用内联样式
很多情况下,使用外部样式表并不靠谱,比如 GMail 以及 Outlook 就会在特定情况下移除邮件 HTML 中的 style 标签,一个更保守的做法是使用元素上的内联样式。对于简单的通知邮件来说,完全够用了。
如果嫌手搓样式很麻烦,可以先用 TailWind CSS 编写,然后让 ChatGPT 帮忙转换成内联样式。
避免使用 base64 内联图片
虽然 Base64 图片 URL 很简单易用,但是很多邮件客户端会把它们当作外部图片一样默认屏蔽。
一个对展示效果更加友好的做法是通过 cid
引用内联图片,这时,图片会被作为邮件的附件一起发送给收件人,如果不希望正文图片被客户端识别为可下载的附件,则需要将附件的 Content-Disposition
设置为 inline
,同时,还需要设置正确的 Content-Type
:
Quick summary
mime – HTML-Email with inline attachments and non-inline attachments – Stack Overflow
- Inline only attachments: use
multipart/related
- Non-inline only attachments: use
multipart/mixed
- Inline and non-inline attachments use
multipart/mixed
andmultipart/related
我的 Email 模板工作流
考虑到 EMail HTML 本身的复杂性,为了简化 Email 模板的开发,我采用了下面的工作流:
首先,使用 degit
初始化一个集成了 TailWind CSS 跟 Vite 的项目模板:
npx degit kometolabs/vite-tailwind-nojs-starter email-template
修改其中的 vite.config.mjs:
import { resolve } from "path";
import { defineConfig } from "vite";
import { ViteImageOptimizer } from "vite-plugin-image-optimizer";
function optimizeSvgPlugin() {
if (!!process.env.OPTIMIZE_IMAGES) {
return ViteImageOptimizer({
svgo: {
plugins: [{ removeViewBox: false }, { removeDimensions: true }],
},
});
}
}
export default defineConfig(() => ({
build: {
outDir: "../dist",
emptyOutDir: true,
// avoid inlining images
assetsInlineLimit: () => false,
rollupOptions: {
input: {
page1: resolve(__dirname, "src", "page1.html"),
page2: resolve(__dirname, "src", "page2.html"),
},
output: {
// avoid hash in assets filename
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split(".");
let extType = info[info.length - 1];
// Explicitly handle image files
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = "img";
// Return the original filename without hashing
return `assets/${extType}/[name][extname]`;
}
// For other asset types, you can keep the default hashing behavior
return `assets/${extType}/[name]-[hash][extname]`;
},
},
},
},
root: "src",
plugins: [optimizeSvgPlugin()],
}));
在编写 HTML 时需要手动创建一个引入 TailWind CSS 的 style
标签:
<style>
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
接下来就可以按照常规的方法用 HTML 画页面了,不过需要注意的是,大部分邮件客户端对 flex-box 的支持都比较差,在编写样式时需要注意。
完成了 HTML 页面以及样式后,就可以借助 ChatGPT 将 TailWind 转换成内联样式的代码。最后,借助下面的脚本,即可将构建出来的 HTML 文件转换成 ejs 模板:
// convert all `{{ foo }}` to `<%= foo %>`
function convertToEjs(content: string) {
return content.replace(/{{\s*(\w+?)\s*}}/g, "<%= $1 %>");
}
/**
* Convert /assets/img/foo-bar.svg to cid:foo-bar
* @param content
*/
function toCidReference(content: string): string {
return content.replace(/\/assets\/img\/([a-zA-Z0-9-)]+?)\.(svg|png|jpg|jpeg)/g, "cid:$1");
}
const files = await glob(dir + "/**/*.html");
for (const filename of files) {
const content = await fs.readFile(filename, "utf-8");
const dirOfFile = path.dirname(filename);
const newContent = toCidReference(convertToEjs(content));
const newFilename = path.join(
dirOfFile,
path.basename(filename, ".html") + ".ejs",
);
await fs.writeFile(newFilename, newContent);
echo(chalk.green(`Converted ${filename} to ${newFilename}`));
}
用来生成邮件模板的命令行操作如下:
#!/usr/bin/bash
OPTIMIZE_IMAGES=true yarn build
cp ./dist/assets/img/* ./assets/img/
npx tsx ./scripts/to-ejs-template.ts --dir ./dist
参考链接
How to Embed Images in Your Emails (CID, HTML Inline & More) | SendGrid
mime – HTML-Email with inline attachments and non-inline attachments – Stack Overflow
发表回复