8-2 Nestjs中的邮件服务(内置node-mailer)
本节目标
在 NestJS 中集成 @nestjs-modules/mailer 邮件模块,配置 Handlebars 模板引擎,实现基于 SMTP 的邮件发送功能,并解决模板文件在构建过程中未自动拷贝的问题。
技术选型
nodemailer
nodemailer 是 Node.js 生态中最流行的邮件发送库,支持 SMTP 和 IMAP 协议,可以直接发送 HTML 邮件。
@nestjs-modules/mailer
NestJS 社区封装的邮件模块,基于 nodemailer,提供了以下增强功能:
- 依赖注入支持(
MailerService) - 模板引擎集成(Pug / EJS / Handlebars)
- 异步配置支持(
forRootAsync) - 邮件预览功能(
preview-email)
模板引擎对比
| 引擎 | 语法风格 | 模板后缀 | 适用人群 |
|---|---|---|---|
| Pug | 严格缩进,类似 Python | .pug | Python 开发者 |
| EJS | HTML + <%= %> 占位符 | .ejs | Ruby on Rails 背景开发者 |
| Handlebars | {{variable}} 双花括号 | .hbs | Vue 开发者(最直观) |
本节选用 Handlebars 作为模板引擎。
安装依赖
pnpm add @nestjs-modules/mailer nodemailer handlebars
pnpm add -D @types/nodemailer
bash
创建邮件模板
src/common/mail/
|-- mail.module.ts
|-- templates/
|-- welcome.hbs
text
welcome.hbs:
<h1>Hello {{name}}</h1>
<p>你有新的通知,请注意查收</p>
html
配置 MailerModule
使用 QQ 邮箱的 SMTP 服务作为测试环境:
// mail.module.ts
import { Module } from '@nestjs/common';
import { MailerModule } from '@nestjs-modules/mailer';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/adapters/handlebars.adapter';
@Module({
imports: [
MailerModule.forRoot({
transport: {
host: 'smtp.qq.com',
port: 465,
secure: true,
auth: {
user: 'your-email@qq.com',
pass: 'your-authorization-code', // QQ 邮箱授权码,非登录密码
},
},
defaults: {
from: '"Brian" <your-email@qq.com>',
},
template: {
dir: __dirname + '/templates',
adapter: new HandlebarsAdapter(),
options: { strict: true },
},
}),
],
})
export class MailModule {}
typescript
QQ 邮箱授权码获取方式:
- 登录 QQ 邮箱 -> 设置 -> 账户
- 开启 SMTP 服务
- 生成授权码(短信验证后获得)
在 Controller 中使用
// user.controller.ts
@Controller('api/v1/user')
export class UserController {
constructor(
private readonly userRepository: UserRepository,
private readonly mailerService: MailerService,
) {}
@Get('mail')
async sendMail() {
await this.mailerService.sendMail({
to: 'recipient@example.com',
subject: 'Welcome Notification',
template: 'welcome', // 自动查找 welcome.hbs
context: {
name: 'iMooc', // 模板变量
},
});
return { message: 'success' };
}
}
typescript
模板文件构建问题
问题现象:发送邮件时报错,提示找不到模板文件。
根因:nest-cli.json 默认只编译 TypeScript 文件到 dist 目录,.hbs 模板文件不会被自动拷贝。
验证:查看 dist/common/mail/ 目录,确认 templates 文件夹不存在。
解决方案:在 nest-cli.json 中配置 compilerOptions.assets:
{
"compilerOptions": {
"watchAssets": true,
"assets": [
{
"include": "common/mail/templates/**/*.{hbs,email}",
"outDir": "dist"
}
]
}
}
json
配置说明:
watchAssets: true:开发模式下模板文件变更时自动重新拷贝。assets数组:指定需要拷贝到dist目录的非 TypeScript 文件。- 支持 glob 模式匹配文件路径。
验证:删除 dist 目录,重新启动调试进程,确认 dist/common/mail/templates/ 下存在模板文件。
异步配置(生产推荐)
生产环境中不应将 SMTP 密码等敏感信息硬编码在代码中。推荐使用 forRootAsync 结合 ConfigService:
// mail.module.ts - 生产环境配置
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
MailerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
transport: {
host: config.get('MAIL_HOST'),
port: config.get('MAIL_PORT'),
secure: config.get('MAIL_SECURE'),
auth: {
user: config.get('MAIL_USER'),
pass: config.get('MAIL_PASS'),
},
},
defaults: {
from: config.get('MAIL_FROM'),
},
template: {
dir: __dirname + '/templates',
adapter: new HandlebarsAdapter(),
options: { strict: true },
},
}),
}),
],
})
export class MailModule {}
typescript
邮件服务常见应用场景
| 场景 | 说明 | 模板示例 |
|---|---|---|
| 用户注册 | 发送欢迎邮件或验证链接 | welcome.hbs |
| 密码找回 | 发送重置密码链接 | reset-password.hbs |
| 系统告警 | 服务异常时通知运维人员 | alert.hbs |
| 订单通知 | 订单状态变更提醒 | order-status.hbs |
| 周期报告 | 每周/月数据统计报告 | report.hbs |
本节小结
- 掌握了
@nestjs-modules/mailer的安装、配置和使用方法。 - 理解了 Handlebars 模板引擎在邮件模板中的应用。
- 解决了
nest-cli.json中模板文件未自动拷贝到dist目录的问题。 - 学会了使用
forRootAsync+ConfigService管理邮件服务的敏感配置。
↑