Contents

Schedule Email With Nodemailer

Gmail ์•ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ •

nodemailer๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„ , ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๋Š” ์ฃผ์ฒด๊ฐ€ ํ•„์š”ํ•œ๋ฐ, ์ด๋ฅผ ์œ„ํ•ด์„œ ์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ Gmail์„ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋‹ค. nodemailer๊ฐ€ Gmail์— ์ ‘๊ทผํ•  ๋•Œ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด credentail ๊ฐ’์— ๊ตฌ๊ธ€ ์•„์ด๋”” / ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ, ์ด๋•Œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๊ตฌ๊ธ€ ๊ณ„์ •์—์„œ ์ƒ์„ฑํ•œ ๋ฌด์ž‘์œ„์˜ ์˜๋ฌธ์ž 16์ž๋กœ ์ด๋ฃจ์–ด์ง„ ์•ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ์ด๋‹ค.
์ด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์กฐ๊ฑด์ด ๋งŒ์กฑ๋˜์–ด์•ผ ํ•œ๋‹ค.

  • ํšŒ์‚ฌ, ํ•™๊ต, ์กฐ์ง์˜ ๊ณ„์ •์ด๋ฉด ์•ˆ๋œ๋‹ค.
  • 2์ฐจ ์ธ์ฆ์„ ํ•ด์•ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด๋Ÿฌ๊ณ  ๋‚˜์„œ๋„ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ํ™œ์„ฑํ™”๋œ 2๋‹จ๊ณ„ ์ธ์ฆ ํƒญ์— ๋“ค์–ด๊ฐ€ ์ œ์ผ ํ•˜๋‹จ์— ์•ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

Nest์—์„œ Task Scheduling

Linux์—์„  ์Šค์ผ€์ฅด๋ง ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด, cron์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์€ node์—๋„ ์กด์žฌํ•œ๋‹ค. nest์—์„œ๋Š” node์˜ cron ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•˜์—์„œ ์Šค์ผ€์ฅด๋ง ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
$ npm install --save @nestjs/schedule
$ npm install --save-dev @types/cron

register module

1
2
3
4
5
6
7
8
9
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
  imports: [
    ScheduleModule.forRoot()
  ],
})
export class AppModule {}

์œ„์™€ ๊ฐ™์ด ๋ชจ๋“ˆ์„ ๋“ฑ๋กํ•˜๋ฉด, ์•ฑ ๋‚ด์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ์Šค์ผ€์ฅด๋ง ์ž‘์—…์„ ๋“ฑ๋กํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๋“ฑ๋ก์€ nest lifecycle ์ค‘ onApplicationBootstrap ์ด hook์„ ์ผ์œผ์ผœ ์‹คํ–‰๋œ๋‹ค.

schedule decorator ์„ ์–ธํ•˜๊ธฐ

cron job

๋‹ค์Œ๊ณผ ๊ฐ™์ด cron-pattern, CronExpression enum, Date object๋ฅผ ์ด์šฉํ•ด์„œ cron job์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

  • @cron(10 * * * * *)
  • @cron(CronExpression.EVERY_30_SECONDS)
  • @cron(new Date(Date.now()+10*1000)) : 10์ดˆ ๋’ค์— ํ•œ๋ฒˆ ์‹คํ–‰

cron-pattern

cron-pattern

Interval

@Interval(10000) ์€ ์•ฑ์ด ์‹คํ–‰๋˜๊ณ  10์ดˆ ๋งˆ๋‹ค ์‹คํ–‰ํ•˜๋„๋ก ์Šค์ผ€์ฅด๋ง์„ ํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์ด๋‹ค. @Interval() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” setInterval()์„ ๊ธฐ๋ฐ˜ํ•˜์—ฌ ๋งŒ๋“ค์–ด์กŒ๋‹ค.

Timeouts

@Timeout(10000)์€ ์•ฑ์ด ์‹คํ–‰๋˜๊ณ  10 ๋’ค์— ํ•œ๋ฒˆ ์‹คํ–‰ํ•˜๋„๋ก ์Šค์ผ€์ฅด๋ง ํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์ด๋‹ค. @Timeout() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” setTimeout()์„ ๊ธฐ๋ฐ˜ํ•˜์—ฌ ๋งŒ๋“ค์–ด์กŒ๋‹ค.

๋™์ ์œผ๋กœ scheduler ์‚ฌ์šฉํ•˜๊ธฐ

์˜ˆ๋ฅผ ๋“ค์–ด, ํšŒ์›๊ฐ€์ž… ๋กœ์ง ์ค‘ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ฉ”์ผ๋กœ ์ธ์ฆํ™”๋ฉด์„ ๋ณด๋‚ด๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ๋•Œ, ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„  ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์ธ์ง€ ํ™•์ธํ•˜๋Š” ํ™”๋ฉด์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ, /email-scheduling/verify-user-with-email api ์— ํ•ด๋‹น ์‚ฌ์šฉ์ž์—๊ฒŒ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๋„๋ก ๋™์ ์œผ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด SchedulerRegistry instance๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
constructor(private schedulerRegistry: SchedulerRegistry) {}

addCronJob(name: string, seconds: string) {
  const job = new CronJob(`${seconds} * * * * *`, () => {
    this.logger.warn(`time (${seconds}) for job ${name} to run!`);
  });

  this.schedulerRegistry.addCronJob(name, job);
  job.start();

  this.logger.warn(
    `job ${name} added for each minute at ${seconds} seconds!`,
  );
}

ํ˜„์žฌ ์œ„์—์„œ cron job์„ ์ƒ์„ฑํ•  ๋•Œ, ์ฒซ๋ฒˆ์žฌ ์ธ์ž๋กœ cron-pattern์„ ๋„˜๊ฒจ์ฃผ์—ˆ์ง€๋งŒ, date object๋‚˜ Moment object๋ฅผ ๋„˜๊ฒจ์ค„ ์ˆ˜ ๋„ ์žˆ๋‹ค.

Nodemailer ์‚ฌ์šฉํ•ด์„œ ์ •ํ•ด์ง„ ์‹œ๊ฐ„์— ์ด๋ฉ”์ผ ๋ณด๋‚ด๋Š” ๊ธฐ๋Šฅ ๊ตฌํ˜„

api route handler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Controller('email-scheduling')
export class EmailSchedulingController {
  constructor(private readonly emailSchedulingService: EmailSchedulingService) {}
 
  @Post('schedule')
  @UseGuards(JwtAuthenticationGuard)
  async scheduleMail(@Body() emailSchedule : EmailScheduleDto){
    this.emailSchedulingService.scheduleEmail(emailSchedule); 
  }
}

cron job creation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
scheduleEmail(emailSchedule : EmailScheduleDto){
        const date = new Date(emailSchedule.date);
        console.log(date);
        const job = new CronJob(date, ()=>{
            this.emailService.sendMail({
                to : emailSchedule.recipient,
                subject : emailSchedule.subject,
                text : emailSchedule.content
            });
        });
        console.log(job);
        this.schedulerRegistry.addCronJob(`${Date.now()}-${emailSchedule.subject}`,job);
        job.start();
    }

send email by using nodemailer instance

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
constructor(
        private readonly configService : ConfigService
    ){
        this.nodemailerTransport = createTransport({
            service : configService.get("EMAIL_SERVICE"),
            auth : {
                user : configService.get("EMAIL_USER"),
                pass: configService.get("EMAIL_PASSWORD")
            }
        });
    }

    sendMail(options : Mail.Options){
        return this.nodemailerTransport.sendMail(options);
    }

๋งˆ๋ฌด๋ฆฌ

์ „์ฒด ์ฝ”๋“œ๋Š” ๋‹ค์Œ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.