Skip to main content

DKIM 签名

DomainKeys Identified Mail(DKIM)会为每封发出的邮件添加一个加密签名。该签名允许接收邮件服务器验证邮件确实来自您的域名,并且在传输过程中未被篡改。

Nodemailer 可以使用一个或多个 DKIM 密钥为邮件签名,无需任何额外依赖。在大多数情况下,签名过程快速且完全在内存中处理。对于非常大的邮件,您可以选择启用磁盘缓存,这样只有前 cacheTreshold 字节数据存储在内存中。


配置

您可以用两种方式配置 DKIM 签名:

  • 全局传输配置 —— 通过传输器发送的每封邮件都会自动用相同的密钥签名,或者
  • 单独邮件配置 —— 在邮件配置中传入 dkim 对象以覆盖或替代传输级别的设置。

如果在两个级别都指定了 DKIM 设置,邮件级别设置优先

DKIM 选项

选项类型默认值描述
domainNamestring(必填)-要签名的域名。此值显示在 DKIM 签名头部的 d= 标签中。
keySelectorstring(必填)-您的 DKIM 密钥的 DNS 选择器。用于 DNS TXT 记录查询路径的一部分:<selector>._domainkey.<domain>
privateKeystring | Buffer(必填)-您的 PEM 格式私钥。必须与 DNS TXT 记录中发布的公钥对应。
keysArray< {domainName, keySelector, privateKey} >-用于多密钥签名的密钥对象数组(适用于密钥轮换或为多个子域签名)。设置该项时,上面单密钥的字段会被忽略。
hashAlgo'sha256' | 'sha1''sha256'用于正文哈希的哈希算法。除非有特定需求,一般使用 sha256
headerFieldNamesstringRFC 4871 默认值以冒号分隔的需包含在签名中的邮件头字段列表(例如 from:to:subject)。默认情况下,Nodemailer 签名 RFC 4871 推荐的标准邮件头。
skipFieldsstring-以冒号分隔的需排除在签名之外的邮件头字段列表。当您的邮件服务提供商在签名后修改某些头部字段时使用(例如 message-id:date)。
cacheDirstring | falsefalse处理大邮件时用于临时文件的目录路径。设置为 false 可完全禁用磁盘缓存。
cacheTresholdnumber2097152(2 MB)在切换至磁盘缓存前,内存中保留的字节数。仅当 cacheDir 设置为有效路径时生效。
warning

选项 cacheTreshold 的拼写故意写为“treshold”(带有 "o" 而非 "e"),以保证与旧版本 Nodemailer 的向后兼容性。


使用示例

以下示例使用 CommonJS 语法,需 Node.js v6 或更高版本:

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

1. 对每封邮件签名

此示例在传输器级别配置 DKIM 签名,因此通过此传输器发送的所有邮件都会自动被签名:

const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 465,
secure: true,
dkim: {
domainName: "example.com",
keySelector: "2017",
privateKey: fs.readFileSync("./dkim-private.pem", "utf8"),
},
});

要验证您的 DNS 记录配置是否正确,请运行:

dig TXT 2017._domainkey.example.com

2. 使用多个密钥签名

当轮换 DKIM 密钥或代表不同子域发送邮件时,可以使用多个密钥:

const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 465,
secure: true,
dkim: {
keys: [
{
domainName: "example.com",
keySelector: "2017",
privateKey: fs.readFileSync("./dkim-2017.pem", "utf8"),
},
{
domainName: "example.com",
keySelector: "2016",
privateKey: fs.readFileSync("./dkim-2016.pem", "utf8"),
},
],
cacheDir: false, // 禁用磁盘缓存
},
});

3. 仅为特定邮件签名

如果不希望对所有邮件签名,可以在单独邮件中配置 DKIM:

const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 465,
secure: true,
// 此处无 DKIM 配置
});

const info = await transporter.sendMail({
from: "sender@example.com",
to: "recipient@example.com",
subject: "Hello with DKIM",
text: "I hope this message gets read!",
dkim: {
domainName: "example.com",
keySelector: "2017",
privateKey: fs.readFileSync("./dkim-private.pem", "utf8"),
},
});

4. 对大邮件启用磁盘缓存

发送带大附件的邮件时,可以启用磁盘缓存以减少内存使用。Nodemailer 会将超出阈值的邮件内容存储到临时文件中:

const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 465,
secure: true,
dkim: {
domainName: "example.com",
keySelector: "2017",
privateKey: fs.readFileSync("./dkim.pem", "utf8"),
cacheDir: "/tmp",
cacheTreshold: 100 * 1024, // 100 KB
},
});

5. 跳过可变的邮件头

一些邮件服务商,例如 Amazon SES,会在您提交邮件后替换诸如 Message-IDDate 的邮件头。如果这些头部包含在 DKIM 签名中,则验证会失败。使用 skipFields 排除它们。

tip

使用 SES 传输器 时,Nodemailer 会自动将 date:message-id 添加到 skipFields

const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 465,
secure: true,
dkim: {
domainName: "example.com",
keySelector: "2017",
privateKey: fs.readFileSync("./dkim.pem", "utf8"),
skipFields: "message-id:date",
},
});

故障排查

  • 签名验证失败 —— 确认您的公钥已正确发布于 DNS 的 <keySelector>._domainkey.<domainName> 处。还要检查 TXT 记录每条字符串不超过 255 个字符(部分 DNS 服务商会错误地拆分或截断过长记录)。
  • 邮件头不匹配错误 —— 如果接收服务器报告签名头部不匹配,可将出错的头部添加到 skipFields,或确保发送基础设施在签名后不修改邮件头。
  • 需要更多帮助? 使用在线工具测试您的 DKIM 配置,例如 dkimvalidator.commail-tester.com。这些服务会发送测试邮件并提供您 DKIM 设置的详细反馈。