node.js 基于 STMP 协议和 EWS 协议发送邮件
这篇文章主要介绍了node.js 基于 STMP 协议和 EWS 协议发送邮件的示例,帮助大家更好的理解和使用node.js,感兴趣的朋友可以了解下提到使用 node.js 发送邮件,基本都会提到大名鼎鼎的 Nodemailer 模块,它是当前使用 STMP 方式发送邮件的首选。基于 NodeMailer 发送 STMP 协议邮件的文章网上已非常多,官方文档介绍也比较详细,在此仅列举示例代码以供对比参考:封装一个 sendMail 邮件发送方法:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566/*** 使用 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 方法发送邮件:1234567891011121314151617181920const opts = {subject: 'subject for test',/** HTML 格式邮件正文内容 */html: `email content for test: <a href="https://lzw.me" rel="external nofollow" rel="external nofollow" >https://lzw.me</a>`,/** TEXT 文本格式邮件正文内容 */text: '',to: 'xxx@lzw.me',// 附件列表// attachments: [],};const smtpConfig = {host: 'smtp.qq.com', //QQ: smtp.qq.com; 网易: smtp.163.comport: 465, //端口号。QQ邮箱 465,网易邮箱 25secure: 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 方法:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111import 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-ewsconst ews = new EWS(ewsConfig);// define ews api functionconst ewsFunction = 'CreateItem';// define ews api function argsconst 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 方法发送邮件:12345678910111213141516171819sendMailByNodeEws({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" rel="external nofollow" rel="external nofollow" >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 模块完成,具体参考如下:12345678910111213141516171819202122232425import { 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 [password]');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 有相关示例,但本人在测试过程中未能成功,具体为无法取得服务器认证,也未能查证具体原因,故以下代码仅作参考:1234567891011121314151617181920212223242526272829303132333435363738394041424344/*** 使用 `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;}