HTML Email开发经验

1,597 阅读5分钟

背景

在项目中的某些操作需要通过邮件的方式通知到用户,并且视觉对邮件有自己的想法,于是就开始自己动手编写(截止目前共12封邮件)。

下面记录并总结一下自己开发过程演变:

最开始

使用拉易网用拖拽的形式按照UI稿拖拽出一个基本框架,然后导出html代码,再在这份html上按照视觉稿上修改。

优点:

  • 登陆之后,可以保存自己的模版,再基于自己的模版进行拖拽能减少一部分重复工作。

  • 拖拽方便,而且可以设置的属性也挺多。

缺点:

  • 拖拽生成导出的代码很冗余,可读性差,修改起来很麻烦(伤眼睛....., 效果与源码大概如下图)

  • 写inline样式很麻烦(在导出后的html里写,更麻烦)

  • 无法持续维护:拉易网上只能保存自己最开始拖拽后生成的框架,后续的开发都是基于导出后的html。

效果图 导出后的源码

结果: 初步可行,硬着头皮做完了,兼容性问题在部分客户端上还是会有,维护成本太高。


项目后续的版本提出了优化邮件的需求,明确了要兼容的邮件客户端。(web-outlook&hotmail、web-gmail),修改了部分原来邮件的样式并且新增了几封邮件。

回想起上述方案的缺点.... 最终决定寻求新道路。

通过各种途径搜索到以下几个资源:

1、www.litmus.com/ (据说业界都是用它的解决方案,可以方便的查看邮件在各个客户端上的表现等等功能....,可惜是收费的,免费注册试用还要提供信用卡 → 放弃)

2、www.campaignmonitor.com/css/ (好像也是一个卖解决方案的,但是它列举了各个邮件客户端支持的css,算一个CSS兼容性指南吧)

3、app.postdrop.io/最后找到了一个在线编辑工具,可以写class,最后导出时转化为inline样式,进阶版收费)

postdrop收费标准

基于以上的资源,于是有了我以下的开发过程:

现在

直接在postdrop上基于它提供的基础模版进行开发,如下图。 postdrop使用图片1 postdrop使用图片2

优点:

  • 模版中提供了一些全局的Reset样式,考虑了一些兼容性、手机端体验的场景。(总比我自己想的要多)

  • 可以写一些公共的class(如下图),在需要的元素上使用,最后导出时都会自动转换成inline样式。 公共class代码片段

  • 支持模版保存&在线编辑html,比拉易网的只能拖拽更加灵活,新旧邮件的开发与维护都可以在这个网页中搞定。

  • 支持直接向已经绑定验证的邮箱发送测试邮件。

  • 支持无限次的导出/下载

缺点:

  • 免费计划最多支持保存5个模版。

  • 免费计划每天最多能发5封测试邮件。

  • 免费计划最多能够绑定2个收件人。

总结:与拉易网的方案相比,这种方法无论是可维护性、便捷性还有开发体验都得到了质的飞跃。


好在这些缺点都可以想办法绕过.......

缺点1-模版数量上限:

解决方案:直接把所有邮件合成一封来写, 模版数量上限解决方案

方案优点:

  • 调试更加方便,右边就能看见所有的邮件,发送测试邮件时也不需要一封一封验证兼容性。

  • 公共的class也能更好的复用,不需要各个模版里重新写一遍class

方案缺点:

  • 最后导出的邮件都在一个html里。(总不能让后台自己再去拆一封封邮件吧.....,于是就写个拆分邮件的工具)
// splitEmail.js
const fs = require("fs");
const jsdom = require("jsdom");

const { JSDOM } = jsdom;

// 导出的邮件html合集
const html = fs.readFileSync("./uc_all.html", {
  encoding: "utf-8",
});

const { document } = new JSDOM(html).window;

// 提取通用的部分
const list = document.querySelectorAll("table.body");
document.body.innerHTML = "";
const bootstrap = document.children[0].outerHTML;

const emails = [].slice.call(list);
const outputPath = "./output";

if (!fs.existsSync(outputPath)) {
  fs.mkdirSync(outputPath);
} else {
  // 清空已存在文件夹
  const files = fs.readdirSync(outputPath);
  files.forEach((f) => {
    fs.unlinkSync(`${outputPath}/${f}`);
  });
}

emails.forEach((email) => {
  const title = email.getElementsByClassName("title")[0].innerHTML;
  fs.writeFileSync(
    `${outputPath}/${title}.html`,
    `${bootstrap.replace("</body></html>", `${email.outerHTML}</body></html>`)}`
  );
});

拆分后

缺点2、3-收件人、测试邮件的数量上限:

解决方案:反正可以无限次导出html,那就自己写一个发送邮件工具。

const nodemailer = require("nodemailer");
const fs = require("fs");

// 发送单封邮件
// const html = fs.readFileSync("./output/New application.html", {

// 发送合集
const html = fs.readFileSync("./uc_all.html", {
  encoding: "utf-8",
});

let transporter = nodemailer.createTransport({
  host: "smtp.exmail.qq.com",
  port: 465,
  auth: {
    user: "xxx@xxx.com", // 填你自己的
    pass: "xxxx", // 填你自己的
  },
});

// 收件人
const tos = [
  "xxxx@outlook.com",
  // "xxxx@hotmail.com",
  // "xxxx@gmail.com",
  // "xxxx@126.com",
  // "xxxx@qq.com",
];

var message = {
  from: "xxxxx@xxxx.com",
  to: "xxxx@outlook.com",
  subject: "Message title",
  html,
};

transporter.verify(function (error, success) {
  if (error) {
    console.log(error);
  } else {
    console.log("Server is ready to take our messages");
    tos.forEach((to) => {
      transporter.sendMail({ ...message, to });
    });
  }
});

至此,以上就是我目前探索出的比较舒服的HTML Emai的开发流程了。


最后记录一些遇到的兼容性问题:

1、邮箱字符串会被邮件客户端自动转换成a标签,并添加样式。

兼容问题1

解决方案:

添加如下class(注:@media all中的样式会以style的形式存在在邮件中,所以最终还得看邮件客户端支不支持head里放style,www.campaignmonitor.com/css/style-e…这个网页就是用来干这个的。)

在线编辑器: 代码片段1 导出的html: 导出后代码1

支持head里放style的邮件客户端最终效果(outlook、gmail、hotmail都支持): 成功效果1 126邮箱失败: 失败效果1

2、class的命名和邮件客户端内部的命名重复,导致样式错误。

qq邮箱内部有.btn这个样式: qq邮箱效果

解决方案:修改自定义的class命名 解决方案2 最终效果: 最终效果2

3、文字的颜色务必设置到目标元素上,部分客户端子元素不会继承color