Nodejs 메일 서버 (IMAP, SMTP)

메일 프로토콜로는 IMAP(Internet Message Access Protocol)과 SMTP(Simple Mail Transfer Protocol)가 있습니다.

IMAP은 사용자가 메일 서버에 접속해 메일을 읽고 관리할 수 있도록 해주며, 클라이언트에서 메일을 다운로드하지 않고 서버 상에서 메일을 유지하는 특징이 있습니다. 이로 인해 다양한 기기에서 메일의 동기화가 가능해지며, 메일 관리에 유연성을 제공합니다.

SMTP는 메일 전송을 담당하는 프로토콜로, 클라이언트가 작성한 메일을 서버로 전달하고 다른 메일 서버로 메일을 전송하는 데 사용됩니다. SMTP는 메일 발송의 핵심적인 역할을 하며, 메일 서버 간의 통신도 이 프로토콜을 통해 이루어집니다.

Mail 인증 방식 개선

메일 서버와 클라이언트 간의 통신에는 사용자 인증이 필수적입니다. 일반적인 인증 방식으로는 사용자 이름과 비밀번호를 통한 인증이 있으나, 이러한 방식은 보안상 취약할 수 밖에 없습니다. 때문에 소셜로그인 Oauth 인증 방식으로 사용자의 개인 정보를 받지 않고, mail서버 구축을 할 수 있습니다.

구글 Oauth 인증 및 테스트 토큰 발급

1. 구글 프로젝트 생성 및 이메일 api 설정

2. oauth 설정

테스트 사용자 등록을 해서 이후에 테스트용 토큰을 받을 수 있다.

3. client 및 secret 키 확인

4. 테스트용 키 발급

위에서 등록한 리다이렉트 주소와, 테스트 메일로 테스트용 토큰을 받는다. 이후 이 토큰으로 gmail인증 메일 송수신 테스트를 할 수 있다.


SMTP로 메일 발신하기

/send.js
// google key
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;

// oauth인증 토큰
const REFRESH_TOKEN = process.env.REFRESH_TOKEN;

// 메일을 보낼 유저 메일 주소
const USER_EMAIL = process.env.USER_EMAIL;

// 메일을 수신할 테스트 메일 주소
const RECEIVE_TEST_EMAIL = process.env.RECEIVE_TEST_EMAIL;


const transporter = nodemailer.createTransport({
    service: "gmail",
    host: "smtp.google.com",
    port: 587,
    secure: true,
    auth: {
      type: "OAuth2",
      user: USER_EMAIL,
      clientId: CLIENT_ID,
      clientSecret: CLIENT_SECRET,
      refreshToken: REFRESH_TOKEN,
    },
  });

  const message = {
    from: USER_EMAIL,
    to: receiverEmail,
    subject: "Nodemailer X Gmail OAuth 2.0 테스트",
    html: `
      <h1>
        Nodemailer X Gmail OAuth 2.0 테스트 메일
      </h1>
      <hr />
      <br />
      <p>테스트 메일 입니다.<p/>
    `,
  };

  try {
    await transporter.sendMail(message);
    console.log("메일을 성공적으로 발송했습니다.");
  } catch (e) {
    console.log(e);
  }

IMAP로 메일 수신하기

/read.js
try {
    const client = new ImapFlow({
      host: "imap.gmail.com",
      port: 993,
      secure: true,
      auth: {
        user: USER_EMAIL,
        accessToken: ACCESS_TOKEN,
      },
    });

    await client.connect();
    console.log("IMAP 서버에 연결되었습니다.");

    // INBOX 폴더를 읽기 전용 모드로 엽니다
    await client.mailboxOpen("INBOX");
    console.log("INBOX가 열렸습니다.");

    // INBOX에서 읽지 않은 메시지를 가져오기
    // `{ seen: false }` 옵션: 읽지 않은 메시지만 가져오기
    for await (let message of client.fetch(
      { seen: false },
      { envelope: true, source: true }
    )) {
      const from = message.envelope.from.map((f) => f.address).join(", ");
      const subject = message.envelope.subject;
      const date = message.envelope.date;
      const content = message.source.toString();

      console.group(`------------------------`);
      console.log(`From: ${from}`);
      console.log(`Subject: ${subject}`);
      console.log(`Date: ${date}`);
      console.log(`content: ${content}`);
      console.groupEnd(`------------------------`);
    }

    await client.logout();
  } catch (error) {
    console.error("이메일 가져오기 오류:", error);
  }