최근 스크래치 페이퍼로 그림을 그려 게시물을 등록하는 2인 미니 프로젝트를 진행하고 있다. 부트캠프에서 첫 Node.js 서버 프레임워크로 배웠던 Express를 머릿속에 상기시키기 위해 Express로 서버를 구축하고 있다.
최대한 간단한 요구 사항들을 정의하고 구현할 예정이지만, 처음 배울 때 모르는 것이 많아 시간에 쫓겨 시도해보지 못한 사소한 기능들을 넣어 보고자 했고 그중 하나가 이메일 인증 기능이다. Node.js에서 이메일 인증을 구현하기 위해 필요한 모듈은 nodemailer
였다.
Nodemailer는 Node.js 애플리케이션이 쉽게 이메일을 보낼 수 있도록 하는 모듈이다.
- 단일 모듈: Zero dependecies
- 높은 보안성
- Emoji를 포함한 유니코드 지원
- 윈도우 지원 및 npm으로 설치 가능
- HTML content와 Plain text 사용 가능
- 메시지에 다양한 첨부 기능
- HTML 콘텐츠에 이미지 포함 가능
- TLS/STARTTLS로 보안 발송
- SMTP 외에도 다양한 전송 방식 지원
- DKIM(도메인키 인증 메일) sign 방식
- 메시지 조작을 위한 커스텀 플러그인 지원
- OAuth2 인증 지원
- SMTP 연결을 위한 프록시 지원
- ES6 코드
- Ethereal.email로부터 이메일 테스트 계정 자동 생성
npm install nodemailer
이메일을 보내기 위해서는 transporter 객체가 필요하며 한 번만 생성하면 된다.
let transporter = nodemailer.createTransport(transport[, defaults])
transporter
는 메일을 발송할 수 있는 객체이다.transport
는 전송 환결설정 객체(tranport configuration object), 연결 url 또는 전송 플러그인 인스턴스이다.defaults
는 메일 옵션 중 디폴트 값을 정의하는 객체이다.transporter 객체가 있다면, .sendMail()
로 이메일을 보낼 수 있다.
transporter.sendMail(data[, callback])
data
는 메일 내용을 정의한다. 참고 - MESSAGE CONFIGURATIONcallback
은 메시지가 전송되었는지 실패했는지에 따른 콜백 함수이다.이외 다른 내용은 공식 문서 참고
이메일 인증 기능을 개발하기 위해 구글링과 이메일 설정을 하다보면 SMTP
, TLS
, POP3
, IMAP
이라는 용어를 흔히 볼 수 있었다.
SMTP_Simple Mail Transfer Protocol
이메일을 전송할 때 사용하는 프로토콜이다. 인터넷에서 이메일을 보내기 위해 이용되는 프로토콜로 TCP 포트번호는 25번이다. 메일 서버 간 송수신 뿐만 아니라 메일 클라이언트에서 메일 서버로 메일을 보낼 때에도 사용된다.
TLS_Transport Layer Security
인터넷상의 커뮤니케이션을 위한 개인 정보와 데이터 보안을 용이하게 하기 위해 설계되어 채택된 보안 프로토콜이다. 웹 서버와 웹 브라우저 간 보안 통신을 위한 보안 채널을 만드는 역할을 한다. OSI 7계층에서 4계층인 Transport Layer에서 보안 채널 설립 과정을 수행한다. SSL을 표준화하여 만들어졌으며, 보통 SSL/TLS로 함께 묶어 분류된다.
POP3_Post Office Protocol 3
이메일을 수신할 때 사용하는 프로토콜의 한 종류이다. 이메일 서버에 도착한 메일을 클라이언트로 가져올 때 사용한다. 응용 계층 인터넷 프로토콜 중 하나로, 원격 서버로부터 TCP/IP 연결을 통해 이메일을 가져온다.
POP3의 경우 서버의 사서함으로부터 클라이언트 PC로 메일을 직접 다운로드 하는 형식이다. POP3로 서버로부터 메일을 다운로드 할 때는 헤더 부분(발신자의 정보, 수신 서버의 호스트 주소, 해당 메일의 고유한 식별자와 메일이 수신된 날짜 시간 등의 정보를 담은 메일의 앞머리 부분)과 본문(메일 본문 및 첨부파일을 포함한 실제 메일 내용)을 모두 다운로드한다. 또한 다운로드와 동시에 사서함에 있는 이메일이 삭제되는 것이 기본적 특징이다.
IMAP_Internet Message Access Protocol
POP3와 마찬가지로 이메일을 수신할 때 사용하는 또 다른 프로토콜의 종류이다. POP3와 동일하게 응용 계층 인터넷 프로토콜 중 하나로, 원격 서버로부터 TCP/IP 연결을 통해 이메일을 가져온다.
IMAP의 경우 이메일 서버와 동기화되는 방식이기에 스마트폰, 태블릿, PC 등 다양한 클라이언트 장치에서 동일하게 미리 설정한 ‘받은 편지함’, ‘보낸 편지함’ 등을 확인할 수 있다. 이는 서버에 직접 접속하여 직접 메일을 확인하는 방식이기 때문에 메일 열람 후에도 서버에 이메일이 그대로 남아있어 여러 클라이언트를 통해서도 반복적으로 이메일을 확인할 수 있다.
용량이 큰 메일을 자주 주고받아 주기적인 용량 관리가 필요하고 오프라인 상태에서도 메일 확인이 필요한 경우라면 POP3가 적합하고, 다양한 단말기에서 메일 확인이 필요하거나 불필요한 메일 다운로드 없이 빠르게 필요한 메일만 확인하고자 하는 경우 IMAP를 사용하는 것이 바람직하다고 한다.
출처: 이메일 프로토콜 이해하기: SMTP, POP3, IMAP의 의미 - 가비아
import { Router } from 'express';
import SignController from '../controllers/signController';
const router = Router();
... // 이메일 인증과 관련 없는 코드 생략
router.post('/emailcheck', SignController.emailAuthentication);
export default router;
import { Request, Response } from 'express';
import { generateAccessToken, sendAuthNumber } from './authFunctions';
... // 이메일 인증과 관련 없는 코드 생략
const signController = {
emailAuthentication: async (req: Request, res: Response) => {
const { email } = req.body;
await sendAuthNumber(email, res);
},
}
export default signController;
import * as nodemailer from 'nodemailer';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({
path: path.resolve(
process.env.NODE_ENV === 'production'
? '.prod.env'
: process.env.NODE_ENV === 'test'
? '.test.env'
: '.dev.env',
),
});
export const smtpTransport = nodemailer.createTransport({
service: process.env.SMTP_SERVICE, // mail 서비스명 ex) 'Naver', 'gmail' 등
auth: {
user: process.env.SMTP_USER, // mail 발송 이메일 주소
pass: process.env.SMTP_PASSWORD, // 해당 이메일 비밀번호
},
tls: {
rejectUnauthorized: false,
},
});
import { Request, Response } from 'express';
import { smtpTransport } from '../config/emailAuth';
... // 이메일 인증과 관련 없는 코드 생략
export const sendAuthNumber = (email: string, res: Response) => {
// 6자리 난수 생성
const authNumber = Math.floor(Math.random() * 888888) + 111111;
const mailOptions = {
from: 'ScratchNow Team', // 발송 주체
to: email, // 인증을 요청한 이메일 주소
subject: '[ScratchNow] 이메일 확인 인증번호 안내', // 이메일 제목
text: `아래 인증번호를 확인하여 이메일 주소 인증을 완료해 주세요.\n
연락처 이메일 👉 ${email}\n
인증번호 6자리 👉 ${authNumber}`, // 이메일 내용
};
smtpTransport.sendMail(mailOptions, (error, responses) => {
if (error) {
res.status(500).json({
message: `Failed to send authentication email to ${email}`,
});
} else {
res.status(200).json({
authNumber,
message: `Authentication mail is sent to ${email}`,
});
}
smtpTransport.close();
});
};
최종적으로 정상 작동이 되는 코드이지만, 처음에는 인증 메일이 발송되지 않았다. .sendMail()
의 error를 콘솔에 찍어 보았고, 아래와 같은 에러 메시지를 확인할 수 있었다.
[Node.js] Error: Invalid login: 535-5.7.8 Username and Password not accepted
환경변수로 관리하는 이메일 주소와 비밀번호에 오타가 없었기에 왜 에러가 나는지 알 수가 없었다. Gmail을 사용할 예정이어서 구글 계정에 보안 수준이 낮은 앱 엑세스 허용
으로 설정해도 그대로였다.
위 에러 메시지를 검색한 결과 바로 해결할 수 있었다. Gmail 계정 설정에서 2단계 인증과 앱 비밀번호를 설정해야 했고, 여기서 설정한 앱 비밀번호를 위 nodemailer.createTransport()
에서 auth의 pass 값
으로 넣어줘야 했다.
postman으로 요청을 보내보니 설정한 대로 인증 안내 메일을 발송한다!