Side Project/Feature

Nodemailer를 사용해 이메일 인증 구현하기

Yepchani 2023. 11. 28. 10:07
반응형

들어가며

오늘은 nodemailer를 사용해 이메일 인증을 구현해 보겠습니다.

nodemailer는 Node.js 애플리케이션에서 이메일을 쉽게 보낼 수 있도록 해주는 모듈입니다.

Next.js를 사용한 프로젝트에서 진행했습니다.

 

 

Nodemailer :: Nodemailer

Nodemailer Nodemailer is a module for Node.js applications to allow easy as cake email sending. The project got started back in 2010 when there was no sane option to send email messages, today it is the solution most Node.js users turn to by default. npm i

nodemailer.com

 

nodemailer 설정

먼저 nodemailer를 설치합니다.

npm install nodemailer

 

파일을 하나 만들고 다음과 같이 설정해 줍니다.

/libs/server/email.ts

import nodemailer from "nodemailer";

const smtpTransport = nodemailer.createTransport({
  service: "Naver",
  host: "smtp.naver.com",
  port: 587,
  auth: {
    user: process.env.MAIL_ID,
    pass: process.env.MAIL_PASSWORD,
  },
  tls: {
    rejectUnauthorized: false,
  },
});
export default smtpTransport;

 

서비스 호스트로 네이버를 이용했습니다.

 

이메일 전송

여기서 첫 문제가 발생했습니다.

Vercel 프로덕션 모드에서 nodemailer를 사용할 때 이메일 전송이 안 되는 겁니다.

아래는 해당 문제에 대한 링크입니다.

https://yepchani.tistory.com/entry/Vercel-production-모드-nodemailer-이메일-전송-문제

 

/pages/api/users/verify

import { NextApiRequest, NextApiResponse } from "next";
import withHandler, { ResponseType } from "@libs/server/withHandler";
import client from "@libs/server/client";
import smtpTransport from "@/libs/server/email";
import { getServerSession } from "next-auth";
import { authOptions } from "../auth/[...nextauth]";

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  const session = await getServerSession(req, res, authOptions);

  const {
    body: { email },
  } = req;

  if (!email) return res.status(400).json({ ok: false });

  if (session) {
    // 동일한 이메일인지 체크 로직 작성
  }

  // 이미 등록된 이메일인지 체크 로직 작성

  await new Promise((resolve, reject) => {
    // verify connection configuration
    smtpTransport.verify(function (error, success) {
      if (error) {
        console.log(error);
        reject(error);
      } else {
        console.log("Server is ready to take our messages");
        resolve(success);
      }
    });
  });

  const payload = `${Math.floor(100000 + Math.random() * 900000)}`;
  const token = await client.token.create({
    data: {
      payload,
      email,
    },
  });
  console.log("token:", token);

  const mailOptions = {
    from: process.env.MAIL_ID,
    to: email,
    subject: "Carrot market email verification",
    text: `Verification Code : ${payload}`,
  };

  await new Promise((resolve, reject) => {
    // send mail
    smtpTransport.sendMail(mailOptions, (error, responses) => {
      if (error) {
        console.log(error);
        reject(error);
      } else {
        console.log(responses);
        resolve(responses);
      }
    });
  });
  smtpTransport.close();

  return res.json({
    ok: true,
  });
}

export default withHandler({ methods: ["POST"], handler });
  • smtpTransport.verify를 이용해 서버 연결을 확인합니다.
  • payload, token, mail options을 생성합니다. 토큰은 이메일 인증 코드이며, 토큰 하나는 이메일 하나와 매칭됩니다. 이메일은 여러 개의 토큰을 가질 수 있습니다.
  • smtpTransport.sendMail를 이용해 이메일을 전송합니다.

 

토큰 검증

import { NextApiRequest, NextApiResponse } from "next";
import withHandler, { ResponseType } from "@libs/server/withHandler";
import client from "@libs/server/client";
import { getServerSession } from "next-auth";
import { authOptions } from "../auth/[...nextauth]";

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  const session = await getServerSession(req, res, authOptions);

  const { token, email } = req.body;

  const foundToken = await client.token.findUnique({
    where: {
      payload: token,
    },
  });

  if (!foundToken || foundToken.email !== email)
    return res.json({
      ok: false,
      error: "Invalid Token. Please check again.",
    });

  // 토큰 삭제 로직 작성
  // 유저 정보 이메일 업데이트 로직 작성

  return res.json({ ok: true });
}

export default withHandler({ methods: ["POST"], handler });
  • 입력받은 토큰을 확인합니다. 잘못된 토큰인 경우 에러를 반환합니다.
  • 정상인 경우 해당 이메일과 관련된 모든 토큰을 삭제합니다.
  • 해당 이메일을 유저 정보에 업데이트합니다.

 

마치며

짠! nodemailer를 사용해서 이메일 인증을 구현해 보았는데요.

nodemailer 덕분에 큰 어려움 없이 구현할 수 있던 것 같습니다.

Vercel 사용 시에는 조금 유의하셔야 합니다.

'Side Project > Feature' 카테고리의 다른 글

검색어 자동 완성 기능 구현하기  (1) 2023.12.11