MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js邮件发送服务搭建指南

2024-10-156.5k 阅读

Node.js 邮件发送服务搭建指南

一、准备工作

1.1 安装 Node.js

在搭建 Node.js 邮件发送服务之前,首先要确保系统中安装了 Node.js。Node.js 可以从其官方网站(https://nodejs.org/)下载对应操作系统的安装包进行安装。安装完成后,在命令行中输入 node -v 可以查看 Node.js 的版本号,以确认安装是否成功。

1.2 选择邮件发送库

在 Node.js 生态系统中,有多个库可以用于发送邮件,其中比较常用的是 nodemailernodemailer 是一个功能丰富且易于使用的邮件发送库,支持多种传输方式,如 SMTP、SES 等。可以通过 npm(Node Package Manager)来安装 nodemailer。在项目目录下打开命令行,执行以下命令:

npm install nodemailer

二、基本邮件发送

2.1 创建简单的邮件发送脚本

安装好 nodemailer 后,就可以编写一个简单的邮件发送脚本。在项目目录下创建一个新的 JavaScript 文件,例如 sendMail.js

const nodemailer = require('nodemailer');

// 创建一个传输对象
let transporter = nodemailer.createTransport({
  host: 'smtp.qq.com', // 以 QQ 邮箱 SMTP 服务器为例
  port: 465,
  secure: true, // 使用 SSL 连接
  auth: {
    user: 'your_email@qq.com', // 你的邮箱地址
    pass: 'your_email_password' // 你的邮箱密码(或授权码,对于某些邮箱如 QQ 邮箱需要使用授权码)
  }
});

// 邮件内容
let mailOptions = {
  from: 'your_email@qq.com', // 发件人邮箱
  to: 'recipient_email@example.com', // 收件人邮箱
  subject: 'Node.js 邮件测试', // 邮件主题
  text: '这是一封通过 Node.js 发送的测试邮件' // 邮件正文纯文本内容
};

// 发送邮件
transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    return console.log(error);
  }
  console.log('邮件已发送: ', info.response);
});

在上述代码中:

  1. 首先引入了 nodemailer 模块。
  2. 使用 createTransport 方法创建了一个传输对象,配置了 SMTP 服务器的相关信息,包括服务器地址、端口、是否使用 SSL 以及认证信息。这里以 QQ 邮箱为例,不同邮箱的 SMTP 服务器设置会有所不同。
  3. 定义了 mailOptions 对象,包含了邮件的发件人、收件人、主题和正文等信息。
  4. 最后使用 transporter.sendMail 方法发送邮件,并在回调函数中处理发送过程中的错误和成功响应。

2.2 配置不同邮箱的 SMTP 设置

2.2.1 QQ 邮箱

QQ 邮箱的 SMTP 服务器地址为 smtp.qq.com,端口为 465(SSL)或 587(TLS)。由于 QQ 邮箱出于安全考虑,需要开启 SMTP 服务并获取授权码来代替密码进行登录。具体步骤如下:

  1. 登录 QQ 邮箱,点击设置 - 账户。
  2. 在账户设置页面中,找到“POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务”,开启“SMTP 服务”。
  3. 按照提示通过短信验证等方式获取授权码,在代码中使用授权码代替邮箱密码。

2.2.2 Gmail 邮箱

Gmail 的 SMTP 服务器地址为 smtp.gmail.com,端口为 465(SSL)或 587(TLS)。如果使用 Gmail 发送邮件,需要注意 Google 对于安全性的要求。有时候可能需要在 Google 账户设置中允许“不太安全的应用访问”(不推荐长期使用此设置,存在安全风险),或者通过 OAuth 2.0 进行身份验证。

以下是使用 Gmail SMTP 服务器且使用 OAuth 2.0 认证的示例代码(此示例较为复杂,需要先获取 OAuth 2.0 相关的凭证):

const {google} = require('googleapis');
const nodemailer = require('nodemailer');
const OAuth2 = google.auth.OAuth2;

// 配置 OAuth 2.0 凭证
const oauth2Client = new OAuth2(
  'YOUR_CLIENT_ID',
  'YOUR_CLIENT_SECRET',
  'https://developers.google.com/oauthplayground'
);
oauth2Client.setCredentials({
  refresh_token: 'YOUR_REFRESH_TOKEN'
});

// 获取访问令牌
const accessToken = new Promise((resolve, reject) => {
  oauth2Client.getAccessToken((err, token) => {
    if (err) {
      reject(err);
    } else {
      resolve(token);
    }
  });
});

// 创建传输对象
const createTransporter = async () => {
  const token = await accessToken;
  return nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {
      type: 'OAuth2',
      user: 'your_email@gmail.com',
      clientId: 'YOUR_CLIENT_ID',
      clientSecret: 'YOUR_CLIENT_SECRET',
      refreshToken: 'YOUR_REFRESH_TOKEN',
      accessToken: token
    }
  });
};

// 邮件内容
const mailOptions = {
  from: 'your_email@gmail.com',
  to:'recipient_email@example.com',
  subject: 'Node.js Gmail 邮件测试',
  text: '这是一封通过 Node.js 使用 Gmail 发送的测试邮件'
};

// 发送邮件
createTransporter().then(transporter => {
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return console.log(error);
    }
    console.log('邮件已发送: ', info.response);
  });
}).catch(console.error);

2.2.3 163 邮箱

163 邮箱的 SMTP 服务器地址为 smtp.163.com,端口为 465(SSL)或 25(非加密)。同样,需要在 163 邮箱设置中开启 SMTP 服务,并获取授权码用于登录。在代码中的配置与 QQ 邮箱类似,只需将服务器地址和认证信息修改为 163 邮箱对应的信息即可。

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.163.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@163.com',
    pass: 'your_email_password_or_auth_code'
  }
});

let mailOptions = {
  from: 'your_email@163.com',
  to:'recipient_email@example.com',
  subject: 'Node.js 163 邮箱邮件测试',
  text: '这是一封通过 Node.js 使用 163 邮箱发送的测试邮件'
};

transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    return console.log(error);
  }
  console.log('邮件已发送: ', info.response);
});

三、邮件内容丰富化

3.1 添加 HTML 内容

除了纯文本内容,邮件还可以包含 HTML 格式的正文,这样可以使邮件更加美观和丰富。在 mailOptions 中添加 html 字段即可。

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

let mailOptions = {
  from: 'your_email@qq.com',
  to:'recipient_email@example.com',
  subject: 'Node.js HTML 邮件测试',
  text: '这是纯文本内容,不支持 HTML 格式',
  html: '<h1>这是一封 HTML 格式的邮件</h1><p>这里可以包含各种 HTML 标签,如链接:<a href="https://example.com">示例链接</a></p>'
};

transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    return console.log(error);
  }
  console.log('邮件已发送: ', info.response);
});

在上述代码中,html 字段包含了 HTML 格式的内容,邮件客户端在显示邮件时会渲染这些 HTML 代码。

3.2 添加附件

可以通过在 mailOptions 中添加 attachments 字段来给邮件添加附件。attachments 是一个数组,每个元素代表一个附件。

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

let mailOptions = {
  from: 'your_email@qq.com',
  to:'recipient_email@example.com',
  subject: 'Node.js 带附件邮件测试',
  text: '这是一封带有附件的邮件',
  attachments: [
    {
      filename: 'example.txt',
      path: './example.txt'
    },
    {
      filename: 'image.jpg',
      path: './image.jpg',
      contentType: 'image/jpeg'
    }
  ]
};

transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    return console.log(error);
  }
  console.log('邮件已发送: ', info.response);
});

在上述代码中,attachments 数组中有两个附件,一个是文本文件 example.txt,另一个是图片文件 image.jpgfilename 表示附件在邮件中显示的文件名,path 表示附件在本地文件系统中的路径,contentType 用于指定附件的 MIME 类型,对于一些常见文件类型,nodemailer 可以自动推断,但对于一些特殊类型建议手动指定。

3.3 嵌入图片

在 HTML 邮件中嵌入图片可以使邮件更加生动。可以通过将图片作为附件添加,并在 HTML 中使用 cid(Content - ID)来引用。

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

let mailOptions = {
  from: 'your_email@qq.com',
  to:'recipient_email@example.com',
  subject: 'Node.js 嵌入图片邮件测试',
  text: '这是一封嵌入图片的邮件',
  html: '<h1>嵌入图片示例</h1><img src="cid:logo" alt="公司 logo">',
  attachments: [
    {
      filename: 'logo.png',
      path: './logo.png',
      cid: 'logo' // 与 HTML 中引用的 cid 一致
    }
  ]
};

transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    return console.log(error);
  }
  console.log('邮件已发送: ', info.response);
});

在上述代码中,首先在 attachments 中添加了一个图片附件,并指定了 cidlogo。然后在 html 内容中通过 <img src="cid:logo"> 来引用该图片,这样图片就会嵌入到 HTML 邮件中显示。

四、邮件发送优化与错误处理

4.1 连接池与性能优化

在高并发场景下,频繁创建和销毁邮件传输连接会影响性能。nodemailer 支持连接池功能,可以复用连接来提高性能。通过在 createTransport 的配置中设置 pool: true 来启用连接池。

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  },
  pool: true,
  maxConnections: 5 // 最大连接数
});

// 多个邮件发送任务
const mailOptionsArray = [
  {
    from: 'your_email@qq.com',
    to:'recipient1_email@example.com',
    subject: '邮件 1',
    text: '邮件 1 的内容'
  },
  {
    from: 'your_email@qq.com',
    to:'recipient2_email@example.com',
    subject: '邮件 2',
    text: '邮件 2 的内容'
  }
];

mailOptionsArray.forEach(mailOptions => {
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return console.log(error);
    }
    console.log('邮件已发送: ', info.response);
  });
});

在上述代码中,启用了连接池并设置了最大连接数为 5。这样在处理多个邮件发送任务时,nodemailer 会复用连接,而不是每次都创建新的连接,从而提高了性能。

4.2 全面的错误处理

在邮件发送过程中,可能会遇到各种错误,如认证失败、网络问题、SMTP 服务器错误等。需要对这些错误进行全面的处理,以提供更好的用户体验和系统稳定性。

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

let mailOptions = {
  from: 'your_email@qq.com',
  to:'recipient_email@example.com',
  subject: '错误处理测试邮件',
  text: '这是一封测试错误处理的邮件'
};

transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    if (error.code === 'ETIMEDOUT') {
      console.log('连接超时,可能是网络问题或服务器响应慢');
    } else if (error.code === 'EAUTH') {
      console.log('认证失败,检查邮箱账号和密码(或授权码)');
    } else {
      console.log('其他错误: ', error.message);
    }
  } else {
    console.log('邮件已发送: ', info.response);
  }
});

在上述代码中,对常见的错误码进行了针对性的处理。例如,ETIMEDOUT 错误表示连接超时,可能是网络问题或服务器响应慢;EAUTH 错误表示认证失败,需要检查邮箱账号和密码(或授权码)。对于其他未知错误,也打印出了错误信息以便排查问题。

五、集成到 Web 应用

5.1 在 Express 应用中发送邮件

如果使用 Express 框架来构建 Web 应用,可以很方便地将邮件发送功能集成进去。以下是一个简单的示例,在 Express 应用中通过一个 API 接口来发送邮件。

首先确保已经安装了 Express:

npm install express

然后创建一个 Express 应用并添加邮件发送功能:

const express = require('express');
const nodemailer = require('nodemailer');
const app = express();
const port = 3000;

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

app.post('/send-mail', (req, res) => {
  let mailOptions = {
    from: 'your_email@qq.com',
    to: req.body.to,
    subject: req.body.subject,
    text: req.body.text
  };

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return res.status(500).json({error: '邮件发送失败', details: error.message});
    }
    res.status(200).json({message: '邮件已成功发送', response: info.response});
  });
});

app.listen(port, () => {
  console.log(`服务器在端口 ${port} 上运行`);
});

在上述代码中:

  1. 引入了 Express 和 nodemailer 模块。
  2. 创建了一个 Express 应用,并定义了一个 POST 路由 /send - mail
  3. 在路由处理函数中,从请求体中获取收件人、主题和正文等信息,构建邮件选项并发送邮件。
  4. 根据邮件发送的结果返回相应的 HTTP 状态码和 JSON 格式的响应。

5.2 在其他框架或服务中集成

除了 Express,nodemailer 也可以很方便地集成到其他 Node.js Web 框架中,如 Koa、Hapi 等。集成的思路基本相同,都是在相应的路由或业务逻辑中调用 nodemailer 的邮件发送方法。

例如在 Koa 框架中:

首先安装 Koa:

npm install koa

然后创建一个 Koa 应用并集成邮件发送功能:

const Koa = require('koa');
const nodemailer = require('nodemailer');
const app = new Koa();

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

app.use(async (ctx) => {
  if (ctx.path === '/send-mail' && ctx.method === 'POST') {
    const {to, subject, text} = ctx.request.body;
    let mailOptions = {
      from: 'your_email@qq.com',
      to,
      subject,
      text
    };

    try {
      const info = await transporter.sendMail(mailOptions);
      ctx.status = 200;
      ctx.body = {message: '邮件已成功发送', response: info.response};
    } catch (error) {
      ctx.status = 500;
      ctx.body = {error: '邮件发送失败', details: error.message};
    }
  }
});

app.listen(3000, () => {
  console.log('服务器在端口 3000 上运行');
});

在 Koa 应用中,通过 app.use 中间件来处理 /send - mail 的 POST 请求,同样从请求体中获取邮件相关信息并发送邮件,根据结果返回不同的响应。

六、安全考虑

6.1 保护认证信息

在代码中直接硬编码邮箱账号和密码(或授权码)是非常不安全的做法。可以使用环境变量来存储这些敏感信息。在 Node.js 中,可以通过 process.env 来访问环境变量。

例如,在 .bashrc(对于 Linux 和 macOS)或 .bash_profile 文件中设置环境变量:

export EMAIL_USER='your_email@qq.com'
export EMAIL_PASSWORD='your_email_password'

然后在 Node.js 代码中:

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true,
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASSWORD
  }
});

这样,敏感的认证信息不会暴露在代码仓库中,提高了安全性。

6.2 防止邮件被标记为垃圾邮件

为了防止发送的邮件被收件人的邮件服务器标记为垃圾邮件,可以采取以下措施:

  1. 使用正确的邮件头:确保 fromreply - to 等邮件头信息准确且合理。from 字段应该是一个有效的邮箱地址,并且最好与域名相关联。
  2. 避免使用垃圾词汇:在邮件主题和正文中避免使用常见的垃圾邮件词汇,如“免费”“快速致富”等。
  3. 遵循最佳实践:如提供退订链接(对于营销邮件)、不要过度发送邮件等。

6.3 加密传输

始终使用加密的连接方式(如 SSL/TLS)来与 SMTP 服务器通信。在 nodemailer 的配置中,通过设置 secure: true(对于 SSL)或 tls: {rejectUnauthorized: false}(对于 TLS,同时需要注意安全性)来确保加密传输。这样可以防止邮件内容在传输过程中被窃取或篡改。

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 465,
  secure: true, // 使用 SSL 连接
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

或者对于 TLS:

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  host:'smtp.qq.com',
  port: 587,
  secure: false,
  tls: {
    rejectUnauthorized: false
  },
  auth: {
    user: 'your_email@qq.com',
    pass: 'your_email_password'
  }
});

不过,对于 rejectUnauthorized: false 的设置要谨慎使用,因为它会忽略服务器证书的验证,存在一定的安全风险,只建议在测试环境或对服务器证书有充分信任的情况下使用。在生产环境中,最好确保服务器证书是有效的且被正确验证。