共计 7374 个字符,预计需要花费 19 分钟才能阅读完成。
提醒:本文最后更新于2025-07-07 14:43,文中所关联的信息可能已发生改变,请知悉!
本文主要介绍 node.js 发送基于 STMP
协议和 MS Exchange Web Service(EWS)
协议的邮件的方法。文中所有参考代码均以 TypeScript 编码示例。
1 基于 STMP 协议的 node.js 发送邮件方法
提到使用 node.js 发送邮件,基本都会提到大名鼎鼎的 Nodemailer
模块,它是当前使用 STMP
方式发送邮件的首选。
基于 NodeMailer
发送 STMP 协议邮件的文章网上已非常多,官方文档介绍也比较详细,在此仅列举示例代码以供对比参考:
封装一个 sendMail
邮件发送方法:
/** | |
* 使用 Nodemailer 发送 STMP 邮件 | |
* @param {Object} opts 邮件发送配置 | |
* @param {Object} smtpCfg smtp 服务器配置 | |
*/ | |
async function sendMail(opts, smtpCfg) { | |
const resultInfo = { code: 0, msg: '', result: null }; | |
if (!smtpCfg) { | |
resultInfo.msg = '未配置邮件发送信息'; | |
resultInfo.code = - 1009; | |
return resultInfo; | |
} | |
// 创建一个邮件对象 | |
const mailOpts = Object.assign( | |
{ | |
// 发件人 | |
from: `Notify <${smtpCfg.auth.user}>`, | |
// 主题 | |
subject: 'Notify', | |
// text: opts.content, | |
// html: opts.content, | |
// 附件内容 | |
// /*attachments: [{ | |
// filename: 'data1.json', | |
// path: path.resolve(__dirname, 'data1.json') | |
// }, { | |
// filename: 'pic01.jpg', | |
// path: path.resolve(__dirname, 'pic01.jpg') | |
// }, { | |
// filename: 'test.txt', | |
// path: path.resolve(__dirname, 'test.txt') | |
// }],*/ | |
}, | |
opts | |
); | |
if (!mailOpts.to) mailOpts.to = []; | |
if (!Array.isArray(mailOpts.to)) mailOpts.to = String(mailOpts.to).split(','); | |
mailOpts.to = mailOpts.to.map(m => String(m).trim()).filter(m => m.includes('@')); | |
if (!mailOpts.to.length) { | |
resultInfo.msg = '未配置邮件接收者'; | |
resultInfo.code = - 1010; | |
return resultInfo; | |
} | |
const mailToList = mailOpts.to; | |
const transporter = nodemailer.createTransport(smtpCfg); | |
// to 列表分开发送 | |
for (const to of mailToList) { | |
mailOpts.to = to.trim(); | |
try { | |
const info = await transporter.sendMail(mailOpts); | |
console.log('mail sent to:', mailOpts.to, ' response:', info.response); | |
resultInfo.msg = info.response; | |
} catch (error) { | |
console.log(error); | |
resultInfo.code = -1001; | |
resultInfo.msg = error; | |
} | |
} | |
return resultInfo; | |
} |
使用 sendMail
方法发送邮件:
const opts = { | |
subject: 'subject for test', | |
/** HTML 格式邮件正文内容 */ | |
html: `email content for test: <a href="https://lzw.me">https://lzw.me</a>`, | |
/** TEXT 文本格式邮件正文内容 */ | |
text: '', | |
to: 'xxx@lzw.me', | |
// 附件列表 | |
// attachments: [], | |
}; | |
const smtpConfig = { | |
host: 'smtp.qq.com', //QQ: smtp.qq.com; 网易: smtp.163.com | |
port: 465, //端口号。QQ邮箱 465,网易邮箱 25 | |
secure: true, | |
auth: { | |
user: 'xxx@qq.com', //邮箱账号 | |
pass: '', //邮箱的授权码 | |
}, | |
}; | |
sendMail(opts, smtpConfig).then(result => console.log(result)); |
2 基于 MS Exchange 邮件服务器的 node.js 发送邮件方法
对于使用微软的 Microsoft Exchange Server
搭建的邮件服务,Nodemailer
就无能为力了。Exchange Web Service(EWS)
提供了访问 Exchange 资源的接口,在微软官方文档中对其有详细的接口定义文档。针对 Exchange 邮件服务的流行第三方库主要有 node-ews
和 ews-javascript-api
。
2.1 使用 node-ews
发送 MS Exchange 邮件
下面以 node-ews
模块为例,介绍使用 Exchange 邮件服务发送邮件的方法。
2.1.1 封装一个基于 node-ews
发送邮件的方法
封装一个 sendMailByNodeEws
方法:
import EWS from 'node-ews'; | |
export interface IEwsSendOptions { | |
auth: { | |
user: string; | |
pass?: string; | |
/** 密码加密后的秘钥(NTLMAuth.nt_password)。为字符串时,应为 hex 编码结果 */ | |
nt_password?: string | Buffer; | |
/** 密码加密后的秘钥(NTLMAuth.lm_password)。为字符串时,应为 hex 编码结果 */ | |
lm_password?: string | Buffer; | |
}; | |
/** Exchange 地址 */ | |
host?: string; | |
/** 邮件主题 */ | |
subject?: string; | |
/** HTML 格式邮件正文内容 */ | |
html?: string; | |
/** TEXT 文本格式邮件正文内容(优先级低于 html 参数) */ | |
text?: string; | |
to?: string; | |
} | |
/** | |
* 使用 Exchange(EWS) 发送邮件 | |
*/ | |
export async function sendMailByNodeEws(options: IEwsSendOptions) { | |
const resultInfo = { code: 0, msg: '', result: null }; | |
if (!options) { | |
resultInfo.code = -1001; | |
resultInfo.msg = 'Options can not be null'; | |
} else if (!options.auth) { | |
resultInfo.code = -1002; | |
resultInfo.msg = 'Options.auth{user,pass} can not be null'; | |
} else if (!options.auth.user || (!options.auth.pass && !options.auth.lm_password)) { | |
resultInfo.code = -1003; | |
resultInfo.msg = 'Options.auth.user or Options.auth.password can not be null'; | |
} | |
if (resultInfo.code) return resultInfo; | |
const ewsConfig = { | |
username: options.auth.user, | |
password: options.auth.pass, | |
nt_password: options.auth.nt_password, | |
lm_password: options.auth.lm_password, | |
host: options.host, | |
// auth: 'basic', | |
}; | |
if (ewsConfig.nt_password && typeof ewsConfig.nt_password === 'string') { | |
ewsConfig.nt_password = Buffer.from(ewsConfig.nt_password, 'hex'); | |
} | |
if (ewsConfig.lm_password && typeof ewsConfig.lm_password === 'string') { | |
ewsConfig.lm_password = Buffer.from(ewsConfig.lm_password, 'hex'); | |
} | |
Object.keys(ewsConfig).forEach(key => { | |
if (!ewsConfig[key]) delete ewsConfig[key]; | |
}); | |
// initialize node-ews | |
const ews = new EWS(ewsConfig); | |
// define ews api function | |
const ewsFunction = 'CreateItem'; | |
// define ews api function args | |
const ewsArgs = { | |
attributes: { | |
MessageDisposition: 'SendAndSaveCopy', | |
}, | |
SavedItemFolderId: { | |
DistinguishedFolderId: { | |
attributes: { | |
Id: 'sentitems', | |
}, | |
}, | |
}, | |
Items: { | |
Message: { | |
ItemClass: 'IPM.Note', | |
Subject: options.subject, | |
Body: { | |
attributes: { | |
BodyType: options.html ? 'HTML' : 'Text', | |
}, | |
$value: options.html || options.text, | |
}, | |
ToRecipients: { | |
Mailbox: { | |
EmailAddress: options.to, | |
}, | |
}, | |
IsRead: 'false', | |
}, | |
}, | |
}; | |
try { | |
const result = await ews.run(ewsFunction, ewsArgs); | |
// console.log('mail sent to:', options.to, ' response:', result); | |
resultInfo.result = result; | |
if (result.ResponseMessages.MessageText) resultInfo.msg = result.ResponseMessages.MessageText; | |
} catch (err) { | |
console.log(err.stack); | |
resultInfo.code = 1001; | |
resultInfo.msg = err.stack; | |
} | |
return resultInfo; | |
} |
使用 sendMailByNodeEws
方法发送邮件:
sendMailByNodeEws({ | |
auth: { | |
user: 'abc@xxx.com', | |
pass: '123456', | |
/** 密码加密后的秘钥(NTLMAuth.nt_password)。为字符串时,应为 hex 编码结果 */ | |
nt_password: '', | |
/** 密码加密后的秘钥(NTLMAuth.lm_password)。为字符串时,应为 hex 编码结果 */ | |
lm_password: '', | |
}, | |
/** Exchange 地址 */ | |
host: 'https://ews.xxx.com', | |
/** 邮件主题 */ | |
subject: 'subject for test', | |
/** HTML 格式邮件正文内容 */ | |
html: `email content for test: <a href="https://lzw.me">https://lzw.me</a>`, | |
/** TEXT 文本格式邮件正文内容(优先级低于 html 参数) */ | |
text: '', | |
to: 'xxx@lzw.me', | |
}) |
2.1.2 基于 NTLMAuth
的认证配置方式
直接配置 pass 密码可能会导致明文密码泄露,我们可以将 pass 字段留空,配置 nt_password
和 lm_password
字段,使用 NTLMAuth
认证模式。此二字段基于 pass 明文生成,其 nodejs 生成方式可借助 httpntlm
模块完成,具体参考如下:
import { ntlm as NTLMAuth } from 'httpntlm'; | |
/** 将输入的邮箱账号密码转换为 NTLMAuth 秘钥(hex)格式并输出 */ | |
const getHashedPwd = () => { | |
const passwordPlainText = process.argv.slice(2)[0]; | |
if (!passwordPlainText) { | |
console.log('USEAGE: \n\tnode get-hashed-pwd.js 此处含有隐藏内容,需要正确输入密码后可见!立即查看'); | |
return; | |
} | |
const nt_password = NTLMAuth.create_NT_hashed_password(passwordPlainText.trim()); | |
const lm_password = NTLMAuth.create_LM_hashed_password(passwordPlainText.trim()); | |
// console.log('\n password:', passwordPlainText); | |
console.log(` nt_password:`, nt_password.toString('hex')); | |
console.log(` lm_password:`, lm_password.toString('hex')); | |
return { | |
nt_password, | |
lm_password, | |
}; | |
}; | |
getHashedPwd(); |
2.2 使用 ews-javascript-api
发送 MS Exchange 邮件
基于 ews-javascript-api
发送邮件的方式,在其官方 wiki 有相关示例,但本人在测试过程中未能成功,具体为无法取得服务器认证,也未能查证具体原因,故以下代码仅作参考:
/** | |
* 使用 `ews-javascript-api` 发送(MS Exchange)邮件 | |
*/ | |
export async function sendMailByEwsJApi(options: IEwsSendOptions) { | |
const resultInfo = { code: 0, msg: '', result: null }; | |
if (!options) { | |
resultInfo.code = -1001; | |
resultInfo.msg = 'Options can not be null'; | |
} else if (!options.auth) { | |
resultInfo.code = -1002; | |
resultInfo.msg = 'Options.auth{user,pass} can not be null'; | |
} else if (!options.auth.user || (!options.auth.pass && !options.auth.lm_password)) { | |
resultInfo.code = -1003; | |
resultInfo.msg = 'Options.auth.user or Options.auth.password can not be null'; | |
} | |
const ews = require('ews-javascript-api'); | |
const exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2010); | |
exch.Credentials = new ews.WebCredentials(options.auth.user, options.auth.pass); | |
exch.Url = new ews.Uri(options.host); | |
ews.EwsLogging.DebugLogEnabled = true; // false to turnoff debugging. | |
const msgattach = new ews.EmailMessage(exch); | |
msgattach.Subject = options.subject; | |
msgattach.Body = new ews.MessageBody(ews.BodyType.HTML, escape(options.html || options.text)); | |
if (!Array.isArray(options.to)) options.to = [options.to]; | |
options.to.forEach(to => msgattach.ToRecipients.Add(to)); | |
// msgattach.Importance = ews.Importance.High; | |
// 发送附件 | |
// msgattach.Attachments.AddFileAttachment('filename to attach.txt', 'c29tZSB0ZXh0'); | |
try { | |
const result = await msgattach.SendAndSaveCopy(); // .Send(); | |
console.log('DONE!', result); | |
resultInfo.result = result; | |
} catch (err) { | |
console.log('ERROR:', err); | |
resultInfo.code = 1001; | |
resultInfo.msg = err; | |
} | |
return resultInfo; | |
} |
3 扩展参考
- https://nodemailer.com/about/
- https://github.com/CumberlandGroup/node-ews
- https://github.com/gautamsi/ews-javascript-api/wiki/Send-Attachment
- https://github.com/lzwme/node-exchange-mailer