在 NestJS 中使用队列发送邮件可以提高系统的响应速度,避免因为邮件发送的延迟而阻塞主业务逻辑。我们可以使用 @nestjs/bull 包来处理队列,它基于 Redis。 步骤:
- 安装必要的依赖
- 配置Redis和Bull队列
- 创建邮件模块,包括邮件服务、队列处理器、队列服务
- 创建邮件模板
- 创建控制器
- 环境配置
创建以下文件:
- app.module.ts - 根模块
- app.service.ts - 邮件服务&队列服务(使用队列)
- app.processor.ts - 队列处理器(执行发送邮件)
- app.controller.ts - 控制器
- 模板文件(如welcome.hbs)
项目结构
css
src/
├── main.ts
├── app.module.ts
├── app.processor.ts
├── app.service.ts
├── app.controller.ts
└── templates/
├── welcome.hbs
第一步:安装工作队列依赖
bash
npm install --save @nestjs/bull bull
npm install --save-dev @types/bull
第二步:在 AppModule 中导入 BullModule 并且进行注册
ts
imports: [
// 配置 Bull 模块
BullModule.forRoot({
redis: {
host: '127.0.0.1',
port: 6379,
},
}),
// 注册队列
BullModule.registerQueue({
name: 'smtp',
}),
],
第三步:创建邮件服务
ts
@Injectable()
export class AppService {
constructor(
@InjectQueue('smtp') private readonly smtpQueue: Queue,
) {}
async sendWelcomeEmail(data: SendEmailRequest) {
const job = await this.smtpQueue.add('welcome', { data });
return { jobId: job.id };
}
}
第四步:安装邮件的通信包以及 Handlebars 模板
bash
npm install --save @nestjs-modules/mailer nodemailer
npm install --save handlebars
第五步:在 AppModule 中导入 nodemailer 并且进行注册
ts
imports: [
MailerModule.forRoot({
transport:{
host: 'smtp.qq.com',
port: 465,
secure: true, // use SSL if required
auth: {
user: '邮箱',//例如:***@qq.com
pass: '权限码',
},
},
defaults: {
from: '"系统通知" <****@qq.com>',
},
template:{
dir:join(__dirname,'templates'),
adapter:new HandlebarsAdapter(),
}
}),
],
第六步:创建队列处理器 app.processor.ts 并且把任务处理交给app.module.ts中的providers
ts
@Processor('smtp')
export class AppProcessor {
constructor(private readonly mailerService: MailerService) {
console.log('MailerService 已注入:', !!mailerService);
}
@Process('welcome')
async sendWelcomeEmail(job: Job<{ data: SendEmailRequest }>) {
console.log('开始处理 welcome 队列任务,数据:', job.data);
const sendEmailData = job.data.data;
try {
await this.mailerService.sendMail(
{
to: sendEmailData.to,
subject: sendEmailData.subject,
template:'welcome',
context:{
name:sendEmailData.metadata?.name || 'User',
currentYear: new Date().getFullYear(),
}
}
);
console.log('邮件发送成功');
} catch (error) {
console.error('邮件发送失败:', error.message);
}
}
}
ts
providers: [AppProcessor,AppService],
最后是创建控制器
ts
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post()
async sendEmail(@Body() data: SendEmailRequest) {
console.log('接收到的请求数据:', data);
await this.appService.sendWelcomeEmail(data);
}
}
然后通过grpc的email微服务
在资源目录下创建proto文件,创建需要用到的字段
proto
syntax = "proto3";
package email;
service EmailService {
rpc Send (SendEmailRequest) returns (SendEmailResponse) {};
}
message SendEmailRequest {
string from = 1;
string to = 2;
string subject = 3;
string body = 4;
map<string, string> metadata = 5;
}
message SendEmailResponse {
bool success = 1;
string message = 2;
}
还需要安装解析依赖以及grpc
bash
npm i --save @grpc/grpc-js @grpc/proto-loader
//需要ts的话可以安装
npm install nestjs-proto-gen-ts
并且添加脚本
json
"proto:gen": "tsproto --path src/proto --output ./src/types",
运行proto:gen 可以在资源目录在获取ts文件
微服务搭建需要安装
bash
npm install @nestjs/microservices
在入口文件中搭建grpc服务
ts
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule,{
transport: Transport.GRPC,//选择grpc通信
options: {
package: 'email',//包名,注意一定要跟proto中的保持一致
protoPath:join(__dirname,'proto/email.proto'),//proto文件的目录
url:'0.0.0.0:8001'
},
});
await app.listen();
console.log('🚀 Email service is running on port 8001');
}
bootstrap();
在控制器中调用
ts
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@GrpcMethod('EmailService','send')
send(data:SendEmailRequest){
console.log('接收到的请求数据:', data);
return this.appService.sendWelcomeEmail(data);
}
}
接着在网关注册grpc客户端,也需要安装相同的依赖以及proto文件
ts
@Module({
imports: [
ClientsModule.register([
{
name: 'EMAIL_SERVICE',
transport: Transport.GRPC,
options: {
package: 'email',
protoPath: join(__dirname, 'proto/email/email.proto'),
url:'0.0.0.0:8001'
},
},
]),
]
})
在email服务中使用
ts
@Injectable()
export class EmailService implements OnModuleInit {
private emailService: email.EmailService;
constructor(@Inject('EMAIL_SERVICE') private emailCLIENT: ClientGrpc) {
}
onModuleInit(): any {
this.emailService = this.emailCLIENT.getService<email.EmailService>('EmailService');
}
sendWelcomeEmail(data:email.SendEmailRequest){
return this.emailService.send(data);
}
}
最后在通过控制器
ts
@Controller('email')
export class EmailController {
constructor(private emailService: EmailService) {
}
@Post('send-welcome-email')
sendWelcomeEmail(@Body() data:SendWelcomeEmailRequest){
return this.emailService.sendWelcomeEmail(data);
}
}
开启email服务以及网关服务 就可以成功调用了