Note
Nodemailer
一个Node.js 邮件发送库
发布于 更新于
Nodemailer
是一个 Node.js 发送邮件的库。最常见的使用方式是通过 SMTP 协议发送邮件。它不是邮件服务器,可以理解为是 SMTP 客户端 + 邮件构造工具。你的系统需要自己生成验证码、保存验证码、校验验证码,Nodemailer 负责把验证码通过邮件发给用户。大致流程如下:
用户输入邮箱
↓
NestJS 生成 6 位验证码
↓
把验证码保存到 Redis / 数据库(例如 5 分钟有效)
↓
Nodemailer 把验证码发送到该邮箱
↓
用户填写验证码
↓
后端比较验证码
↓
正确 → 邮箱验证通过 → 创建账号
通过 SMTP 协议发送邮件大致流程如下:
你的 NestJS 后端
│
│ 调用
↓
Nodemailer
│
│ SMTP
↓
SMTP 邮件服务器
│
↓
用户的邮箱服务器
│
↓
用户邮箱
常见功能
| 功能 | 示例 |
|---|---|
| 发送普通文本邮件 | 注册验证码 |
| 发送 HTML 邮件 | 漂亮的验证码邮件 |
| 发送附件 | PDF、Excel |
| 群发 | 给多个用户发送通知 |
| 抄送 CC | 工作邮件 |
| 密送 BCC | 批量通知 |
| OAuth2 | Gmail / Outlook 等 |
| SMTP 连接池 | 大量发送邮件 |
| 测试邮件 | Ethereal |
| DKIM | 邮件签名 |
| 嵌入图片 | 邮件 Logo |
| 自定义邮件 Header | 高级邮件功能 |
安装和引入
npm install nodemailer
// 同时安装 TypeScript 类型
npm install -D @types/nodemailer
// 引入
import * as nodemailer from 'nodemailer';
常用API
| API | 作用 |
|---|---|
createTransport |
创建一个 Transporter 邮件发送器 |
sendMail |
发送一封邮件 |
verify |
测试 SMTP 配置是否能够正常工作 |
createTestAccount() |
创建一个 Ethereal 测试邮箱账号 |
getTestMessageUrl |
获取url |
createTransport()
创建一个Transporter邮件发送器实例。建议应用启动后创建一个Transporter实例后整个应用重复使用,不要每次发送邮件重新创建一个。
参数与返回值
nodemailer.createTransport(
transport: SMTPTransport.Options | string,
defaults?: object,
): Transporter
| 参数 | 含义 |
|---|---|
transport |
发送邮件核心配置,如邮箱服务器域名、发送人、收件人 |
defaults |
用于设置以后每封邮件的默认配置 |
transport属性
| 属性 | 类型 | 作用 |
|---|---|---|
host |
string | SMTP服务器地址,去对应的邮件厂商(如QQ)那里查看 |
port |
number | SMTP端口 |
secure |
boolean | 是否连接时直接使用TLS |
auth |
object | SMTP身份认证 |
service |
string | 使用预设邮件服务 |
pool |
boolean | 是否开启SMTP连接池 |
logger |
boolean | 是否输出日志 |
debug |
boolean | 是否输出SMTP调试信息 |
tls |
object | TLS高级配置 |
connectionTimeout |
number | 建立连接超时 |
socketTimeout |
number | Socket空闲超时 |
-
port 去对应的邮件厂商(如QQ)那里查看,常见的为
465或587。465:直接建立TLS连接;587:通常先建立连接,再使用STARTTLS升级。 -
auth SMTP服务器身份认证。包含两个属性:
userSMTP服务器的邮箱,passSMTP授权码,注意不是邮箱登录密码是授权码,在邮件厂商开启SMTP授权后可以查到。考虑到安全问题,这两个属性通常写到环境变量中,然后从环境变量中获取 -
service 相当于SMTP配置预设,如service: ‘gmail’,它会自己预设{ host: ‘…’, port: 465, secure: true }。但是推荐显示配置,便于维护。
示例
import * as nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport(
{
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
// default。后续通过sendMail发送邮件,不写from,默认使用这里设定的值
{
from: '"记账系统" <system@example.com>',
},
});
sendMail()
发送邮件
参数与返回值
transporter.sendMail(
mailOptions: Mail.Options
): Promise<SentMessageInfo>
// 支持callback。但在 NestJS 中推荐使用上面的
transporter.sendMail(
mailOptions,
callback
): void
| 参数 | 含义 |
|---|---|
MailOptions |
邮件配置 |
MailOptions属性
| 属性 | 作用 |
|---|---|
from |
发件人 |
to |
收件人 |
cc |
抄送 |
bcc |
密送 |
replyTo |
回复地址 |
subject |
邮件标题 |
text |
纯文本正文 |
html |
HTML正文 |
attachments |
附件 |
headers |
自定义Header |
priority |
邮件优先级 |
- from 支持字符串和对象
from: '"记账系统" <system@example.com>'
from: {
name: '记账系统',
address: 'system@example.com',
}
// 都会显示成:
// 发件人:
// 记账系统
// system@example.com
- to 支持发送给多个对象
to: "user@example.com";
// 发送给多个对象
to: "user1@example.com, user2@example.com";
// 发送给多个对象
to: ["user1@example.com", "user2@example.com"];
- html
HTML格式的正文内容,支持同事与text一起使用。
html: `
<div>
<h2>记账系统</h2>
<p>你的验证码为:</p>
<h1>123456</h1>
<p>验证码5分钟内有效。</p>
</div>
`;
- replayTo 用户点击“回复”时,回复到哪个邮箱。如replyTo: ‘support@example.com’
sendMail返回值
interface SentMessageInfo {
// SMTP 信封
envelope: Envelope;
// 邮件唯一 ID
messageId: string;
// SMTP服务器接受的收件人
accepted: Array<string | Address>;
// 被 SMTP 服务器拒绝的地址
rejected: Array<string | Address>;
pending: Array<string | Address>;
// SMTP服务器最终返回的信息
response: string;
}
示例
async function sendVerificationCode(email: string, code: string) {
const info = await transporter.sendMail({
from: '"MyMoney 记账系统" <system@example.com>',
to: email,
subject: "【MyMoney】邮箱验证码",
text: `你的验证码是:${code},5分钟内有效。`,
html: `
<div>
<h2>MyMoney 记账系统</h2>
<p>你的邮箱验证码为:</p>
<h1>${code}</h1>
<p>验证码将在 5 分钟后失效。</p>
<p>如果不是你本人操作,请忽略本邮件。</p>
</div>
`,
});
return info;
}
verify()
测试 SMTP 配置是否能够正常工作。很适合应用启动时检查 SMTP 配置正确`。
函数与返回值
transporter.verify(): Promise<true>
// callback 版本
transporter.verify(
callback: (
error: Error | null,
success: true
) => void
): void
createTestAccount()
创建一个 Ethereal 测试邮箱账号。用于发测试邮件,发送出来的邮件不会真的发送给现实中的收件人,而是保存在 Ethereal 测试系统中,非常适合开发测试。
参数与返回值
nodemailer.createTestAccount(): Promise<TestAccount>
// callback版
nodemailer.createTestAccount(
callback
): void
TestAccount 大致属性:
interface TestAccount {
user: string;
pass: string;
smtp: {
host: string;
port: number;
secure: boolean;
};
imap: {...};
pop3: {...};
web: string;
}
示例
// 创建账号
const account = await nodemailer.createTestAccount();
// 设置邮件发射器
const transporter = nodemailer.createTransport({
host: account.smtp.host,
port: account.smtp.port,
secure: account.smtp.secure,
auth: {
user: account.user,
pass: account.pass,
},
});
getTestMessageUrl()
获取 url
参数与返回值
nodemailer.getTestMessageUrl(
info: SentMessageInfo
): string | false
示例
const info = await transporter.sendMail({
from: "test@example.com",
to: "abc@example.com",
subject: "测试",
text: "Hello",
});
const url = nodemailer.getTestMessageUrl(info);
console.log(url); // 可能得到:https://ethereal.email/message/xxxx
完整示例
import * as nodemailer from "nodemailer";
async function main() {
// 1. 创建测试邮箱
const account = await nodemailer.createTestAccount();
// 2. 创建 Transporter
const transporter = nodemailer.createTransport({
host: account.smtp.host,
port: account.smtp.port,
secure: account.smtp.secure,
auth: {
user: account.user,
pass: account.pass,
},
});
// 3. 检查SMTP连接
await transporter.verify();
// 4. 发送邮件
const info = await transporter.sendMail({
from: `"记账系统" <${account.user}>`,
to: "tom@example.com",
subject: "注册验证码",
text: "你的验证码是:123456",
html: `
<h2>记账系统</h2>
<p>你的验证码为:</p>
<h1>123456</h1>
`,
});
// 5. 查看发送结果
console.log(info.messageId);
console.log(info.accepted);
console.log(info.rejected);
// 6. 获取测试邮件地址
console.log(nodemailer.getTestMessageUrl(info));
}
main();
常见错误码
| 错误 | 含义 |
|---|---|
EAUTH |
SMTP认证失败 |
ECONNECTION |
SMTP连接失败 |
ETIMEDOUT |
连接超时 |
EENVELOPE |
SMTP信封/地址问题 |
EMESSAGE |
邮件内容被拒绝 |
ETLS |
TLS问题 |
EREQUIRETLS |
TLS要求无法满足 |
ECONFIG |
配置错误 |
try {
await transporter.sendMail(...);
} catch (error) {
if (error.code === 'EAUTH') {
console.log('SMTP用户名或密码错误');
}
if (error.code === 'ETIMEDOUT') {
console.log('SMTP服务器连接超时');
}
}