This article describes how node.js can send emails based on the STMP protocol and the MS Exchange Web Service (EWS) protocol. All reference code in this article is coded in TypeScript examples.

STMP protocol-based approach to sending emails with node.js

When it comes to sending emails using node.js, the famous Nodemailer module is basically mentioned as the first choice for sending emails using the STMP method. There are many articles on sending STMP protocol emails based on NodeMailer, and the official documentation is quite detailed, so here is just the sample code for comparison.

Wrapping a sendMail mail delivery method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
 * 使用 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;
}

To send a message using the sendMail method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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));

MS Exchange mail server based node.js method for sending emails

For mail services built with Microsoft’s Microsoft Exchange Server, Nodemailer cannot do anything about it. The Exchange Web Service (EWS) provides an interface to Exchange resources and is documented in detail in the official Microsoft documentation. Popular third-party libraries for Exchange mail services are node-ews and ews-javascript-api.

Sending MS Exchange emails with node-ews

The following is an example of using the node-ews module to send emails using the Exchange mail service.

Wrapping a method for sending emails based on node-ews

Wrapping a sendMailByNodeEws method.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
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;
}

To send a message using the sendMailByNodeEws method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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',
})

NTLMAuth-based authentication configuration method

We can leave the pass field empty and configure the nt_password and lm_password fields to use the NTLMAuth authentication mode. These two fields are generated based on pass plaintext, and their nodejs generation can be done with the httpntlm module, as described below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 [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();

Sending MS Exchange emails using ews-javascript-api

Based on the ews-javascript-api way of sending emails, there is a relevant example in its official wiki, but I failed in the testing process, specifically because I could not get the server authentication, but also failed to check the specific reasons, so the following code is only for reference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * 使用 `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;
}

Extended Reference