nest.js의 공식 문서에 MailerModule을 이용하여 메일을 보내는 방법이 나와있다.
공식문서를 따라가며 이메일 인증을 구현해보자!
npm install --save @nestjs-modules/mailer nodemailer
이메일을 보낼 때, 템플릿을 사용하면 더더욱 이쁘게 보일 수 있다
필수적인 기능은 아니지만 퀄리티를 높여주는 아이기 때문에 같이 설치해본당
공식 문서에는 handlebars, pub, ejs가 나와있는데,
이 블로그 를 참고하여 고민해본 결과, Handlebars를 사용하기로 결정하였다!
새로운 라이브러리를 또 깔지 않아도 되고, 확장성이 좋아보였다 :D
-> 다른 언어에서도 사용할 수 있다는 점!
nest.js 공식 문서에서 알려준 비동기 코드를 적용하였다.
//app.module.ts
import { Module } from '@nestjs/common';
import { MailerModule } from '@nestjs-modules/mailer';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
@Module({
imports: [
MailerModule.forRootAsync({
useFactory: () => ({
transport: 'smtps://user@domain.com:pass@smtp.domain.com',
defaults: {
from: '"nest-modules" <modules@nestjs.com>',
},
template: {
dir: __dirname + '/templates',
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
}),
],
})
export class AppModule {}
보내는 이메일을 네이버 이메일로 보낼 거기 때문에 네이버 메일에서 설정을 해주어야 했다.
-> IMAP/SMTP를 사용 안 함 -> 사용 함으로 바꾸어주고 저장한다.
이 블로그 를 참고하여 진행하였다
위의 imports 코드들은 AppModule에서 진행했었는데, MailModule을 파고 난 후, MailModule로 변경해주었다.
아직 템플릿 적용 전이다!
nest g controller Mail
nest g service Mail
nest g module Mail
mail.module.ts
import * as path from 'path';
import { Module } from '@nestjs/common';
import { MailerModule } from '@nestjs-modules/mailer';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
import { MailService } from './mail.service';
import { MailController } from './mail.controller';
@Module({
imports: [
MailerModule.forRootAsync({
useFactory: () => ({
transport: {
host: 'smtp.naver.com',
port: 587,
auth: {
user: process.env.EMAILADDRESS,
pass: process.env.EMAILPASSWORD,
},
},
defaults: {
// 우리 서비스 이름 Waa와 함께, 답장을 해야할 이메일을 알려준다.
from: `'Waa' <${process.env.EMAILADDRESS}>`,
},
}),
}),
],
controllers: [MailController],
providers: [MailService],
})
export class MailModule {}
mail.service.ts
import { Injectable } from '@nestjs/common';
import { MailerService } from '@nestjs-modules/mailer';
@Injectable()
export class MailService {
constructor(private readonly mailerService: MailerService) {}
async sendEmail() {
await this.mailerService.sendMail({
// 누구에게 보낼 것인가?
to: 'myEmail',
// 메일 제목 설정
subject: 'Test',
// 메일 내용 설정
text: '테스트',
});
}
}
mail.controller.ts
import { Controller, Get } from '@nestjs/common';
import { MailService } from './mail.service';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller('mail')
export class MailController {
constructor(private readonly userService: MailService) {}
@Get()
@ApiTags('Auth')
@ApiOperation({ summary: '이메일 보내기' })
async createUser() {
return this.userService.sendEmail();
}
}
위와 같이 설정해주면, 메일 발송 준비 끝!
-> 스웨거에 잘 나타나는 모습도 볼 수 있고, 요청을 날리면 메일이 온다!
이 블로그 를 참고하여 진행하였다.
위에서 구현했던 mail.module.ts에 template를 추가한다
@Module({
imports: [
MailerModule.forRootAsync({
useFactory: () => ({
transport: {
host: 'smtp.naver.com',
port: 587,
auth: {
user: process.env.EMAILADDRESS,
pass: process.env.EMAILPASSWORD,
},
},
defaults: {
from: `'Waa' <${process.env.EMAILADDRESS}>`,
},
// 이곳 추가
template: {
dir: path.join(__dirname, './templates'),
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
}),
],
controllers: [MailController],
providers: [MailService],
})
export class MailModule {}
./templates로 적어두었으니 mail의 하위폴더로 templates 폴더를 생성한다.
그리고, 템플릿을 적을 email.hbs를 생성!
// email.hbs
<p>Hi {{username}},</p>
<p>Hello from NestJS NodeMailer</p>
일단 임시 데이터로 이렇게 놔둔다.
그리고 service 로직을 변경해준다.
// mail.service.ts
@Injectable()
export class MailService {
constructor(private readonly mailerService: MailerService) {}
async sendEmail() {
await this.mailerService
.sendMail({
to: 'myEmail.naver.com',
subject: 'Test',
text: '테스트',
// email.hbs라서 ./email 적어줌. 확장자는 자동 생성
template: './email',
// 동적으로 들어갈 변수 정의.
context: {
code: 'cf1a3f828287',
username: 'Chaeyeon',
},
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.log(err);
});
}
}
email.hbs에서
<p>Hi {{username}},</p>
이렇게 적었었는데, 그걸 service 로직에서 username: 'Chaeyeon'과 같이 정의해주는 방식이다.
-> 나중엔 이것도 당연히 동적으로 변경시켜주어야 한다!
분명, 맞는 디렉토리 경로를 입력해도 불구하고 계속해서 디렉토리를 찾지 못한다는 이슈가 있었다.
Error: ENOENT: no such file or directory, open '/Users/Desktop/dist/mail/templates/email.hbs'
-> 조금 더 자세히 에러 코드를 봤어야 했는데 처음에는 뒤의 mail/templates/email.hbs만 보아 왜 안 되지.. 이러고 있었다.
계속 바꾸어도 안 되길래 조금 더 에러코드를 자세히 보니 /dist에서 난다는 것을 확인하였고 실제 dist 폴더를 가보니 /templates라는 폴더 자체가 생성이 안 되고 있었다.
npm run build
를 이용하여 빌드를 해보아도 생성이 안 됐다.
구글링 결과 nest-cli.json에 코드를 추가해줘야 한다는 것을 알았고,
"compilerOptions": {
"deleteOutDir": true,
"assets": [
{
"include": "mail/templates",
"outDir": "dist",
"watchAssets": true
}
]
}
다음과 같이 입력하여 템플릿 폴더를 무사히 생성할 수 있었다!
-> username에 Chaeyeon이 잘 들어가는 모습!
<table
width='100%'
cellpadding='0'
cellspacing='0'
style="border: '0px'; background-color: #ffffff; width: 100%"
class='container'
>
<tr style='display: block; width: 100%; margin-bottom: 20px'>
<td style='display: block; width: 100%; text-align: center'></td>
</tr>
<tr width='100%' style='display: block; margin-bottom: 50px'>
<td
style='
display: block;
width: 100%;
font-weight: bold;
font-size: 28px;
color: #000000;
text-align: center;
'
>
자녀의
<span style='color: red'>위험 의심 단어</span>가 5회를 초과하였습니다.
</td>
<td
style='
margin-top: 4px;
display: block;
width: 100%;
font-size: 20px;
color: #000000;
text-align: center;
'
>
아래는 5회 초과한 단어 리스트입니다.
</td>
</tr>
<tr>
<td
style='
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
'
>
<div
style='
background: #f3f3f3;
padding: 20px 34px 12px 34px;
width: 450px;
border-radius: 8px;
color: #4b5550;
font-size: 15px;
'
>
{{!-- 반복문 코드 --}}
{{#each keywords}}
<div style='margin-bottom: 8px'>
{{value}}
:
{{count}}
</div>
{{/each}}
</div>
</td>
</tr>
</table>
완성 !
잘 보고 갑니다. 👍