글을 읽기 전에, 이전 글을 읽어보기를 권한다. 간략하게나마 언급하자면, 이전 글에서는 Express.js, mailpop3 등을 이용하여 POP3 서버로부터 이메일 리스트를 받아와 RESTful API 형식으로 만들어내는 과정을 거쳤다. 이번에는 POST 요청을 받아 SMTP 서버를 통해 다른 이메일 주소로 메일을 전송하는 코드를 작성할 것이다. 만약, 당신이 이 과정에 대해 관심이 있고 지금 이 글을 통해 처음 접했다면, 웹메일 클라이언트 개발하기 태그 페이지를 방문해보는 것도 좋다.
POP3 과정에서 사용한 라이브러리인 mailpop3는 필자가 사용하기 편하게 구조를 변경할 필요가 있었지만, SMTP 과정에서 사용할 라이브러리인 nodemailer는 그럴 필요 없이 편리하게 코드를 작성할 수 있도록 설계되었다. 따라서, 저번보다는 더 쉽고 빠르게 원하는 기능을 구현할 수 있다.
Step 7.
엔드포인트: 이메일 전송
POST 요청을 통해 실행될 것이므로, 엔드포인트 라우터에 라우트를 한 개 더 추가한다. 로그인한 사용자만 접근할 수 있도록 세션을 체크해야 함을 주의한다.
// route/endpoint.route.js
// ...
function checkUserPermission(req, res, next) {
if (req.session.username == null) {
sendResponse(res, 401, "Unauthorized");
return;
}
next();
}
// ...
router.use("/mails", checkUserPermission);
router.post("/mails", async (req, res) => {
});
module.exports = router;
Step 3에서 테스트 용도로 미리 작성된 코드를 참고해본다. 메일을 전송할 때마다 Transport를 생성하고 삭제하기보다는 POP3 클라이언트를 clientMap에 저장한 것과 같이 로그인 시에 Transport를 생성되게 하여 Map에 저장하고, 메일을 전송할 때마다 활성화된 Transport가 메일을 전송하게끔 만들면 된다.
// 이전 글에서 SMTP를 테스트하기 위해 사용한 코드
const nodemailer = require("nodemailer");
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure: process.env.SMTP_TLS_SSL == "true",
auth: {
user: process.env.TEST_EMAIL,
pass: process.env.TEST_PASSWORD
}
});
// route/endpoint.route.js
// ...
const transportMap = new Map();
function createTransport(username, password) {
return nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure: process.env.SMtP_TLS_SSL == "true",
auth: {
user: username,
pass: password
}
});
}
router.post("/login", async (req, res) => {
// ...
await client.login(username, password);
// ... POP3 서버 로그인에 성공하면 SMTP Transport를 생성한다.
transportMap.set(username, createTransport(username, password));
// ...
});
이후, 미리 만들어둔 메일 전송 라우트에 여러 내용을 포함하여 요청하면 username, 즉 사용자의 이메일을 통해 Transport를 불러오고, 불러온 Transport로 메일을 발송하게 된다.
// route/endpoint.route.js
// ...
router.post("/mails", async (req, res) => {
const username = req.session.username;
const { to, subject, html } = req.body;
const transport = transportMap.get(username);
if ([ to, subject, html ].some(e => e == null || e.trim() == '')) {
return sendResponse(res, 400, "Bad Request");
}
await transport.sendMail({
from: username,
to, subject, html
});
sendResponse(res, 200, "Ok");
});
받는 메일 주소가 잘못되면 nodemailer가 오류를 Throw함과 동시에 통신 클라이언트에는 커넥션 리셋 오류가 표시된다. 잘못된 메일 주소를 입력하여 nodemailer에서 오류를 던지면 사용자에게 오류가 발생하였음을 알리기 위해 try~catch 문으로 코드를 감싼다.
// route/endpoint.route.js
// ...
router.post("/mails", async (req, res) => {
try {
const username = req.session.username;
const { to, subject, html } = req.body;
const transport = transportMap.get(username);
if ([ to, subject, html ].some(e => e == null || e.trim() == '')) {
return sendResponse(res, 400, "Bad Request");
}
await transport.sendMail({
from: username,
to, subject, html
});
sendResponse(res, 200, "Ok");
} catch (e) {
switch (e.responseCode) {
case 451:
return sendResponse(res, 451, "DNS Server Error");
default:
return sendResponse(res, 400, "Bad Request");
}
}
});
Step 8.
엔드포인트: 파일 첨부
이메일의 내용 요소에는 첨부된 파일도 있다. urlencoded를 통해 파일을 업로드받는 방법이 있겠지만, POST 요청의 Body에 Base64 형식으로 받아도 크게 문제될 것이 없다. 따라서 최대한 코드를 변형하지 않고 필요한 코드를 맞추어 넣는다.
// route/endpoint.route.js
router.post("/mails", async (req, res) => {
try {
const username = req.session.username;
let { to, subject, html, attachments } = req.body;
const transport = transportMap.get(username);
attachments = attachments?.map(e => {
const { filename, content } = e;
if ([ filename, content ].some(el => el == null || el.trim() == ''))
return null;
return ({
filename, content: Buffer.from(content, "base64")
});
}) ?? [];
if ([ to, subject, html ].some(e => e == null || e.trim() == ''))
return sendResponse(res, 400, "Bad Request");
if (attachments.some(e => e == null))
return sendResponse(res, 400, "Bad Attachment");
await transport.sendMail({
from: username,
to, subject, html, attachments
});
sendResponse(res, 200, "Ok");
} catch (e) {
switch (e.responseCode) {
case 451:
return sendResponse(res, 451, "DNS Server Error");
default:
return sendResponse(res, 400, "Bad Request");
}
}
});
프론트엔드에서 파일 자체를 Base64로 인코딩하고 POST Body에 담아 전송할 것이기에, 따로 가공은 거치지 않고 오로지 유효성 검사만을 실행한다.
프론트엔드 단에서 엔드포인트에 첨부 파일 정보를 넘길 때에는, FileReader.readAsDataURL() 등을 사용하여 전송하면 된다.
마치며
SMTP 라이브러리가 편하게 구성되어 있어서 그런지, 정말 쉽게 SMTP 클라이언트를 만들 수 있었다. 다음 과정은 프론트엔드 페이지를 구축하고 엔드포인트와 연결해주는 것이 되겠다.
이번에는 길지 않은 분량이었지만, 여기까지 읽어주신 여러분께 감사의 말씀 전한다. 글 내용에 오류가 있거나, 착오가 있으면 댓글로 피드백해 주시는 것은 언제나 환영한다.
'개발일지 > 웹' 카테고리의 다른 글
텍스트 에디터로 메일 전송 페이지 만들기: 웹메일 클라이언트 개발하기 6 (0) | 2024.07.28 |
---|---|
웹메일들은 어떻게 메일을 보여주는가: 웹메일 클라이언트 개발하기 5 (0) | 2024.07.23 |
로그인 페이지 구현해보기: 웹메일 클라이언트 개발하기 4 (0) | 2024.07.20 |
메일은 어떻게 받아오는가: 웹메일 클라이언트 개발하기 2 (0) | 2024.07.16 |
내 도메인 주소에서 보내는 메일: 웹메일 클라이언트 개발하기 1 (0) | 2024.07.14 |