사용자에게 이메일 알림이나 사용자 인증 메일을 보내야 하는 경우가 있는데요, 이번 글에서는 서버에서 사용자에게 이메일을 전송하는 방법에 대해 알아보겠습니다.

 nodemailer는 공식문서에 나와 있듯 'a module for Node.js applications to allow easy as cake email sending' 발신자와 수신자 정보, 보낼 내용만 설정하여 간편하게 서드파티 앱에서 이메일을 보낼 수 있게 도와줍니다.

Gmail 계정 설정

 nodemailer를 통해서 이메일을 보내려면 유효한 이메일 계정이 필요합니다. 가장 간편한 방법으로 Gmail 계정을 발신자 계정으로 설정해서 사용할 수 있습니다. 서드파티 앱에게 접근권한을 설정하기 위해서 구글 계정에 접속해 보안 - Google에 로그인 - 앱 비밀번호 항목에서 앱 비밀번호를 발급받아 본 계정의 비밀번호 대신 사용합니다.

Gmail 앱 비밀번호

1.1 Gmail 앱 비밀번호 발급

22년 5월부로 구글에서는 'allow less secure apps' 기능을 제공하지 않습니다. Gmail을 사용할 경우 앱 비밀번호를 발급받아 사용해야 합니다.

nodemailer

ts
1let transporter = nodemailer.createTransport(transport[, defaults])

 nodemailer는 createTransport함수로 SMTP 서버로 전달될 transporter 객체를 생성하고 이메일을 전송합니다. 첫 번째 인자로 createTransport 함수는 서버 호스트명, 이메일 프로바이더 포트번호, 발신자 인증 정보 등을 전달받는 필수 설정값들로 구성되고 이메일 전송마다 발신자와 같은 디폴트 옵션들을 객체로 전달할 수도 있습니다.

발신자의 서버 호스트(gmail)와 인증 정보가 준비되면 nodemailer에게 나머지 일을 맡깁니다.

ts
1const transporter = nodemailer.createTransport(
2  {
3    host: 'smtp.gmail.com', // 서버 호스트명
4    port: 587, // SSL은 465 TLS는 587 (이메일 프로바이더에 따라 포트번호는 상이할 수 있습니다)
5    secure: false, // SSL은 true TLS는 false
6    auth: {
7      // 발신자 인증 정보
8      user: 'recipient@gmail.com', // 발신자 이메일
9      pass: 'password', // 비밀번호
10    },
11  },
12  {
13    from: 'recipient@gmail.com', // 발신자 이메일
14    subject: 'Email with nodemailer', // 이메일 제목
15  }
16);

SMTP 서버는 이메일을 보내거나 받을 때 사용되고 클라이언트(Gmail)가 이메일을 보내거나 받기 위해 SMTP 서버와 통신합니다. 이메일을 전송할 때, 클라이언트는 이메일을 SMTP 서버에 전송하고, SMTP 서버는 내용을 받아서 수신자의 이메일 서버로 다시 전송합니다.

 sendMail 내장 메소드를 통해서 transporter에 발신자, 수신자, 제목 등 이메일에 담길 필수 정보들을 전달합니다. 단순 알림 이메일 또는 사용자 인증 이메일 등 이메일 성격에 따라 로직을 구상하여 html에 담아 전송합니다.

ts
1const transporter = nodemailer.createTransport({
2  host: 'smtp.gmail.com',
3  port: 587,
4  secure: false,
5  auth: {
6    user: process.env.ADMIN_EMAIL,
7    pass: process.env.PASS,
8  },
9});
10
11const sendNotification = async (recipientEmail: string, code: string) => {
12  // 발신자, 수신자와 전송할 이메일 내용 설정
13  const BUSINESS_NAME = 'moonkorea';
14  const mailOptions = {
15    from: `${BUSINESS_NAME} <${process.env.ADMIN_EMAIL}>`,
16    to: recipientEmail, // 수신자
17    subject: `${BUSINESS_NAME} : Confirmation Code`, // 제목
18    text: '', // 이메일 내용
19    html: `
20    <div>
21      <p>${BUSINESS_NAME}</p>
22      <p>Confirmation Code : ${code}</p>
23    </div>
24    `, // 이메일 본문에 html을 담아 전송
25  };
26  try {
27    await transporter.sendMail(mailOptions);
28    // ...
29  } catch (err) {
30    // ...
31  }
32};

sendMail 예외처리

 nodemailer로 sendMail 메소드의 결과에 따라 예외처리 또한 가능합니다.

ts
1transporter.sendMail(data[, callback])
ts
1transporter.sendMail(mailOptions, (error, info) => {
2  if (error) {
3    console.error('error occured sending email:', error);
4  } else {
5    console.log('success:', info.response);
6  }
7});

수신자가 다수일 경우 한 명의 수신자에게 성공적으로 이메일 전송되더라도 성공적으로 전송된 것으로 nodemailer는 간주합니다.

다수에게 전송하기

 한 번의 이메일을 보낼 때마다 nodemailer는 SMTP 서버와 연결을 하고 통신을 합니다. 다수의 사용자에게 이메일을 전송할 때는 불필요하게 서버와 연결을 맺고 끊고를 반복하지 않고 pool 옵션을 추가해 한 번의 연결을 통해서 다수의 이메일을 전송할 수 있습니다.

ts
1const transporter = nodemailer.createTransport({
2  pool: true, // pooled connection
3  host: 'smtp.gmail.com',
4  port: 587,
5  secure: false,
6  auth: {
7    user: process.env.ADMIN_EMAIL,
8    pass: process.env.PASS,
9  },
10});
11const sendNotification = async (to: string[], subject: string, date: Date) => {
12  const mailOptions = {
13    from: process.env.ADMIN_EMAIL,
14    to: to.join(','),
15    subject,
16    text: 'notification',
17    html: `<div>Your subscription expires at ${date}</div>`,
18  };
19  try {
20    await transporter.sendMail(mailOptions);
21    // ...
22  } catch (err) {
23    // ...
24  }
25};

비즈니스 계정으로 전송하기 (Zoho mail)

기타 이메일 클라이언트를 사용할 경우 해당 클라이언트의 도메인 이름(Gmail의 경우 user@gmail.com)을 사용해야 하는데요, Zoho mail은 무료로 도메인 명을 기반으로 이메일을 사용할 수 있어 발신자가 관리자일 경우 비즈니스 계정으로 사용할 수 있습니다. 무료 티어에서도 넉넉한 스토리지를 제공합니다.

  1. Zoho에서 계정을 새로 생성합니다. 소유하고 있는 도메인을 기반으로 계정으로 생성할 거기 때문에 아래의 설정으로 진행합니다.

zoho 가입

2.1 Zoho 계정 생성

  1. 계정 생성 후 메일 설정을 계속 진행합니다. 소유하고 있는 도메인 이름과 기타 정보를 입력합니다.

도메인 입력

2.2 도메인 입력

  1. 도메인이 등록된 호스트의 도메인 설정 페이지에서 레코드를 추가합니다. 아래 2.4에서는 AWS를 예시로 Route 53에서 레코드를 생성했습니다. Vercel은 Dashboard - Domains에서 DNS 설정을 하고 기타 도메인 프로바이더 계정은 각 DNS 페이지(DNS Manager, DNS Control Panel 등)에서 레코드를 생성합니다.

레코드 추가

2.3 레코드

route 53

2.4 Route 53

txt-record

2.5 레코드 추가

  1. 단계를 계속 진행하며 TXT와 MX 레코드를 추가합니다.

  2. 도메인 이름 기반 계정을 새로 생성했으면 서드파티앱에서도 이메일을 사용할 수 있도록 two-factor authentication 설정을 합니다. TFA를 허용하면 기존 비밀번호 대신 Application Specific Password를 사용해서 인증 프로세스를 거치게 됩니다. Zoho Mail Admin Console에 로그인 후 Security and Compliance 탭에서 TFA를 허용합니다.

TFA 설정

2.6 TFA 설정

  1. Zoho Accounts에 로그인 후 Security - App Paswords탭에서 Application Specific Password를 새로 발급받습니다.

Application Specific Password

2.7 Application Specific Password

  1. 새로 생성된 비밀번호는 transporter 객체에서 사용됩니다.
ts
1const transporter = nodemailer.createTransport({
2  host: 'smtp.zoho.com', // 서버 호스트는 Zoho
3  port: 465,
4  secure: true,
5  auth: {
6    user: process.env.NODEMAILER_USER, // 생성한 Zoho mail
7    pass: process.env.NODEMAILER_PASS, // Application Specific Password
8  },
9});

설정 후 발생하는 535 서버에러는 계정 설정을 다시 확인해 주세요.

  1. 보안 설정과 관련해서는 여기서 권장되는 항목들에 대한 보안 상태를 제공합니다. 보안 상태를 확인한 후 여러 액션에 필요한 지침을 따라주세요.