在网站开发中,发送邮件是一个经常会遇到的需求。比如注册账号的时候,可以通过邮件来验证邮箱地址,服务变更或账号异常的时候,也可以通过邮件来通知。我的博客在回复评论的时候,也会有邮件通知。

我用来发送邮件的模块是 Nodemailer,这是一个第三方的邮件模块,也是目前使用最多的 Node.js 邮件模块。

使用 npm 安装 nodemailer:

npm install nodemailer --save

我使用的邮箱是 QQ 邮箱,使用的是 SMTP 协议。

SMTP(Simple Mail Transfer Protocol)也叫简单邮件传输协议,这是互联网上常用的一种邮件传输协议。SMTP 主要是发送邮件,不包括邮件接收。

常见邮箱的 SMTP 地址和端口

下面是一些常见邮箱的 SMTP 地址和端口:

服务商SMTP 地址SMTP 端口
QQ邮箱smtp.qq.com25
网易126邮箱smtp.126.com25
网易163邮箱smtp.163.com25
新浪邮箱smtp.sina.cn25
Gmailsmtp.gmail.com465
雅虎邮箱smtp.mail.yahoo.com465

Foxmail 和 QQ邮箱的 SMTP 地址都是 smtp.qq.com

一般邮箱的 SMTP 服务默认都是关闭状态,你可能需要到邮箱设置中开启 SMTP。

发送文本

下面使用 Nodemailer 发送普通文本:

const nodemailer = require('nodemailer');

// 创建  SMTP 连接
const transporter = nodemailer.createTransport({
  // SMTP 服务器地址
  host: 'smtp.qq.com',
  // 端口
  port: 25,
  // 使用 TLS
  secure: false,
  auth: {
    // 你的邮箱账号或地址
    user: '[email protected]',
    // 密码
    pass: 'sjifjhierhjiji'
  }
});

// 发送邮件
transporter.sendMail({
  // 发信地址
  from: '[email protected]',
  // 收信地址
  to: '[email protected]',
  // 邮件标题
  subject: 'Hello',
  // 邮件内容(普通文本)
  text: `这是来自 Mr. Ma's Blog www.misterma.com 的邮件`,
}).then(result => {
  console.log(`发送成功,id:${result.messageId}`);
}).catch(error => {
  console.log(error);
});

注意!在调用 createTransport 的时候,有一个选项 secure,这是使用 TLS 连接到 SMTP 服务器。如果 SMTP 服务器的端口是 465,secure 需要设置为 true,端口 25 和 587 secure 需要设置为 false

sendMailto 收信地址可以设置多个,多个地址之间用英文逗号 , 分隔。

sendMail 的发送结果会通过 Promise 的方式返回,成功的 Promise 内容如下:

{
  "accepted": [
    "[email protected]"
  ],
  "rejected": [],
  "ehlo": [
    "PIPELINING",
    "SIZE 73400320",
    "AUTH LOGIN PLAIN XOAUTH XOAUTH2",
    "AUTH=LOGIN",
    "MAILCOMPRESS",
    "8BITMIME"
  ],
  "envelopeTime": 233,
  "messageTime": 348,
  "messageSize": 379,
  "response": "250 OK: queued as.",
  "envelope": {
    "from": "[email protected]",
    "to": [
      "[email protected]"
    ]
  },
  "messageId": "<[email protected]>"
}

发送 HTML

这里使用的 transporter 选项和上面是一样的,下面直接使用 sendMail 发送邮件:

// 要发送的 HTML
const htmlText = `
<h2>Mr. Ma's Blog评论回复邮件通知</h2>
<p>呵呵呵呵😀😁😂🤣</p>
`;

// 发送邮件
transporter.sendMail({
  // 发信地址
  from: '[email protected]',
  // 收信地址
  to: '[email protected]',
  // 邮件标题
  subject: 'Hello',
  // 邮件内容(HTML)
  html: htmlText
}).then(result => {
  console.log(`发送成功,id:${result.messageId}`);
}).catch(error => {
  console.log(error);
});

如果你要发送比较复杂的 HTML 内容,你可能需要使用 Web 模板引擎生成,关于模板引擎的使用可以看 Node.js Web 模板引擎 Eta 的简单使用

在发送 HTML 时,CSS 样式需要写在元素的 style 属性里,不能直接使用 link 调用 CSS 文件,也不能使用 style 标签来写 CSS。

发送附件

下面把项目目录下的 package.json 作为附件发送:

const path = require('path');

// 发送邮件
transporter.sendMail({
  // 发信地址
  from: '[email protected]',
  // 收信地址
  to: '[email protected]',
  // 邮件标题
  subject: 'Hello',
  // 邮件内容
  text: '给你发了一个 JSON 文件',
  // 附件
  attachments: [
    {
      filename: 'package.json',
      path: path.join(__dirname, 'package.json')
    }
  ]
}).then(result => {
  console.log(`发送成功,id:${result.messageId}`);
});

附件 attachments 需要接收一个对象数组,下面是属性说明:

  • filename:用于显示的文件名,可以和真实的文件名不一样
  • path:真实的文件路径
  • content:文件内容,可以直接使用字符串,也可以使用 fs.readFile 之类的读取文件发送
  • href:文件的 URL
  • httpHeaders:与 href 一起使用的 HTTP 请求头
  • contentType:附件类型,省略会根据 filename 自动生成
  • cid:附件的 Content-ID(CID),用于内联显示图片等嵌入式资源
  • encoding:附件内容的编码方式。默认为 base64
  • headers:附件的自定义标题。可以设置任何有效的 MIME 头字段和值的键值对

在发送附件的时候,pathcontenthref 一般只会选择一个。如果要发送大文件可以使用 pathpath 是流式传输的,发送动态生成的内容可以使用 content,发送公开的网络文件可以使用 href

在 HTML 中插入附件图片

附件有一个 cid 属性,通过 cid 可以直接在 HTML 中显示附件图片:

const path = require('path');

// 发送邮件
transporter.sendMail({
  // 发信地址
  from: '[email protected]',
  // 收信地址
  to: '[email protected]',
  // 邮件标题
  subject: 'Hello',
  // 邮件内容(HTML)
  html: `看看这张图片 <img src="cid:www.misterma.com" alt="图片" />`,
  // 附件
  attachments: [
    {
      filename: 'test.gif',
      path: path.join(__dirname, 'test.gif'),
      cid: 'www.misterma.com'
    }
  ]
}).then(result => {
  console.log(`发送成功,id:${result.messageId}`);
});

附件的 cid 可以设置一个字符串,在 HTML 的 img src 可以直接使用附件 cidsrc 需要以 cid: 开头。