도메인 주소는 IP 주소에 비해 외우기 쉽다는 장점이 있어 주로 웹 페이지 등에 쉽게 연결할 수 있도록 사용된다. 도메인 주소 하나만으로도 개인이나 단체의 이름, 특성, 컨셉을 나타낼 수 있기도 하고, 도메인 주소를 부여함으로써 내 웹 페이지에 특별함을 추가해 줄 수도 있다. 시대가 가며 웹 개발을 아예 모르는 사람이더라도 'Velog'나 'Tistory' 등의 유명 블로그 플랫폼이나 'WordPress'와 같은 설치형 블로그를 쉽게 운영할 수 있게 되었기에, '나다움'이나 본인만의 개성을 강조하기 위한 다른 수단으로써 사용되는 것이 보통이 되었다.
하지만 도메인 주소를 꼭 웹 페이지 운영에만 사용할 수 있는가 하면, 그렇지는 않다. 사실 이미 모두에게 친숙한 곳에서 도메인이 사용되는 곳이 있는데, 그것은 바로 이메일이다. 으레 알고 있듯이, 이메일 계정의 '@' 문자 뒤에 오는 것이 도메인 주소다. 포털 사이트에서 만든 계정이 그대로 이메일 주소가 되는 경우가 대다수이기 때문에, 개발자나 회사원이 아닌 일반적인 개인이나 단체라면, 대개 'naver.com', 'gmail.com' 등을 사용하기 마련이다.
내가 도메인 주소를 구매하여 갖고 있다면 웹 사이트 하나만을 위해 사용하는 것은 아까울 수도 있고, 특히 개발자에게는 자신의 도메인 주소로 구성된 메일 주소를 가지고 있는 것이 더 로망 있을 수 있다. 어디서든 내 도메인 주소에 접근하여 그 곳에서 메일을 송신하고, 수신할 수 있다면 그것은 정말 멋져 보일 것이다. 필자는 그러한 가벼운 마음에 '내 도메인에서 작동하는 웹메일 클라이언트'를 개발해보고자 한다.
Step 1.
내 도메인으로 메일 주소 만들기
'도메인으로 메일 주소를 사용할 수 있도록 해주는 서비스'는 이미 많이 존재한다. 'Mailplug'나, 'Mailgun'이 그 좋은 예시이다. 이 서비스 내에선 이메일 프로토콜을 통해 메일 데이터를 직접 전송하고 수신하는 서버인 메일 서버가 존재하고, 이메일 주소의 '@' 뒤에 오는 도메인 주소가 바로 이 메일 서버의 주소를 나타낸다. 따라서, 내 도메인으로 메일 주소를 만들려면 '메일 서버'를 구축해야 한다는 결론에 다다른다.
회사나 규모가 매우 큰 단체, 재단 등이 아니라면, 메일 서버를 자체 구축하는 경우는 많지 않다. 괜히 많은 기업들이 'Mailplug'와 같은 외부 메일 서비스를 이용하는 것이 아니다. 실제로 구축에는 상당히 다양한 과정이 있고, 구축을 완료하더라도 추후 그 메일 서버를 통해 보낸 메일들이 전부 스팸 메일로 분류되는 경우가 생기기도 한다. 따라서 필자도 외부 메일 서비스를 통해 메일 주소를 만드려고 한다.
다음이나 카카오 메일 서비스는 한 번도 사용해 본 적이 없지만, 다음에서 제공하는 '다음 스마트워크'를 통해 메일 주소를 만들어 보았다. 무료라는 점에 이 서비스를 이용했지만, 만약 다른 클라이언트에서 SMTP, POP3에 연결할 수 있는 서비스라면 다른 어떤 서비스라도 이용할 수 있다. 스팸메일이 아니라는 것을 인증하는 SPF까지 설정해야만 Gmail로 메일이 전송되는 점에 유의해야 한다.
보유한 도메인 주소의 DNS를 설정할 수 있다면, MX 레코드를 제공된 정보에 따라 설정해주고, SPF 설정을 해주면 이 단계가 끝난다. 필자의 경우는 가비아에서 DNS를 설정할 수 있게끔 기능을 제공하고 있었다.
Step 2.
플로우 계획해보기
목표만 생각해 본다면 단순히 클라이언트를 만들고 메일 프로토콜을 통해 메일 서버와 통신하는 것으로 요약할 수 있다. Form에 내용을 입력하여 SMTP를 통해 메일을 전송하고, POP3를 통해 수신된 메일을 가져와 화면에 표시하는 것이 전부이다.
POP3 서버는 엔드포인트로부터 여러 명령어들을 받아 메일 데이터를 전송해 줄 것이고, SMTP 서버는 요청에 따라 사용자가 입력한 메일 정보를 대상 메일 주소로 전송해 주는 역할을 하게 된다.
사용자가 엔드포인트에 로그인을 요청하면, 사용자가 입력한 이메일 주소와 비밀번호를 POP3 서버에 각각 USER, PASS 명령으로 보낸다. POP3 서버가 이에 대해 'OK' 응답을 한다면 사용자가 입력한 이메일 주소와 비밀번호가 로그인 성공한 것으로 간주되어 세션을 부여하고 사용자를 로그인 처리할 것이고, 그렇지 않는다면 사용자에게 Fallback 메시지를 표시할 것이다.
규모가 크지 않기 때문에 세션 데이터를 저장하기 위해 MySQL이나 MongoDB와 같은 데이터베이스들을 사용할 필요가 없다. 도메인 메일 주소를 많은 사람에게 사용할 수 있게끔 할 것도, 내 도메인이 아닌 다른 도메인도 이 서비스를 이용하게끔 할 것도 아니기 때문이다. 만약 추후에 생각이 바뀐다고 해도, 그저 세션 하나만을 저장하는 용도이기에 라이브러리 추가와 설정 정도만 해주면 된다.
Step 3.
서버 준비와 테스트
이번 프로젝트에서는 데이터베이스를 사용하지도 않고, 복잡할 것으로 예상되는 기능이 몇 없으므로 가볍지만 강력한 Node.js 프레임워크인 'Express.js'를 사용하고자 한다. 적절한 장소에 빈 디렉터리를 만든 후 npm을 통해 기본적인 package.json 파일을 작성하고, 빠른 디버깅을 위해 'nodemon'을 설정해 준다.
세션 사용을 위한 'express-session'과 디버깅을 위한 'dotenv'도 설치한다. 메일 수신, 발신이 가능하게끔 만들기 위해 SMTP를 통한 메일 전송을 지원하는 'nodemailer'와 POP3를 통해 서버에서 메일을 가져올 수 있는 'mailpop3' 또한 사용해 볼 생각이다.
SMTP, POP3
서버 정보 알아보기
일단, 제일 중요한 기능인 메일의 송수신을 테스트할 생각이다. 따라서, 자체적으로 구축한 메일 서버가 아니라면 관련 설정에서 SMTP와 POP3을 활성화하여야 한다. 그리고, 현재 사용하는 메일 서버의 SMTP 서버 정보와 POP3 정보가 필요하다. 필자가 사용하기로 한 다음 스마트메일에서는 SMTP와 POP3를 활성화할 수 있는 메뉴에서 해당 정보들을 찾을 수 있었다.
얻은 POP3 서버와 SMTP 서버의 정보를 프로젝트 폴더에 '.env' 파일에 저장하였다.
POP3_HOST=pop.daum.net
POP3_PORT=995
POP3_TLS_SSL=true
SMTP_HOST=smtp.daum.net
SMTP_PORT=465
SMTP_TLS_SSL=true
POP3 연결
테스트하기
미리 설치해 두었던 'mailpop3'를 테스트해 본다. 아래 코드는 순서대로 클라이언트를 POP3 서버에 연결하고, 로그인을 요청하고, LIST 명령어를 전송하여 메일 리스트를 불러오며, 메일 리스트의 2번째 메일을 다운로드하여 데이터를 콘솔에 표시하게끔 작동한다.
// main.js
const POP3Client = require("mailpop3");
const client = new POP3Client(process.env.POP3_PORT, process.env.POP3_HOST, {
tlserrs: false,
enabletls: process.env.POP3_TLS_SSL == "true",
debug: false
});
client.on("connect", function () {
client.login(process.env.TEST_EMAIL, process.env.TEST_PASSWORD);
console.log("Mail server connect success");
});
client.on("login", function (st, raw) {
console.log(raw);
if (st) {
console.log("Login Pass");
client.list();
} else {
client.quit();
}
});
client.on("list", function (st, cnt, no, d, raw) {
console.log(raw);
client.retr(2);
});
client.on("retr", function (st, no, d, raw) {
console.log(d);
});
로그인 요청을 받으면, client.login(username, password)를 실행하여 USER username, PASS password를 POP3 서버에 전송한다. 이후 아래와 같이 "OK" 응답을 받는 경우, 로그인 완료로 간주하고 세션을 발급하게끔 만들 생각이다.
client.list()를 호출하여 LIST 명령을 서버에 전송하면, 서버는 현재 보관 중인 메일의 ID들을 돌려준다. 앞에 있는 숫자는 인덱스(순번)로, 1부터 시작한다. 그리고 뒤에 있는 숫자가 메일의 ID이다. 중요한 것은 인덱스로, 이를 통해 메일의 상세 내용을 알아볼 수 있다.
client.retr(n)을 호출하여 n번 메일을 받는 명령어인 'RETR n'을 서버에 전송하면, 서버는 LIST의 n번 메일의 상세 정보를 돌려준다. 보낸 이, 받은 이, 메시지 ID, 제목과 여러 헤더들이 포함되어 있고, 내용은 Content-Transfer-Encoding 헤더의 내용에 따라 인코딩 되어 있다.
Date: Sun, 14 Jul 2024 06:55:02 +0900 (KST)
From: =?UTF-8?B?6rmA64yA7KSR?= <me@dekiyu.xyz>
To: =?UTF-8?B?6rmA64yA7KSR?= <me@dekiyu.xyz>
Message-ID: <20240714065502.YHQL0uuWSwe9uqY-DZqS3w@kr.decu.hanmail.net>
Subject: =?UTF-8?B?64K06rKM7JOw6riwIO2FjOyKpO2KuA==?=
Errors-To: <kr.decu@hanmail.net>
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: base64
X-Kakaomail-MID: CvAKNAAAAAgAAAGQrheiVQAM2O8=
X-Mailer: mint
PGh0bWw+CjxoZWFkPgogICAgPHN0eWxlPgogICAgICAgIHB7bWFyZ2luLXRvcDowO21hcmdpbi1i
b3R0b206MH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CiAgICA8ZGl2IHN0eWxlPSJjb2xv
cjojMTExO2ZvbnQtZmFtaWx5OkFwcGxlIFNEIEdvdGhpYyBOZW8sTWFsZ3VuIEdvdGhpYywn66eR
7J2AIOqzoOuUlScsc2Fucy1zZXJpZjtmb250LXNpemU6MTBwdDtsaW5lLWhlaWdodDoxLjU7Ij48
cD7rgrTqsozsk7DquLA8L3A+PHA+PGJyPjwvcD48L2Rpdj4KPC9ib2R5Pgo8L2h0bWw+Cg==
받은 이와 보낸 이, 제목의 형식이 익숙하지 않은 형식인데, 이는 Gmail 등 글로벌 이메일 서비스에서 내용을 표시할 때 인코딩 문제로 깨지는 것을 방지하기 위해서 =?UTF-8?B? 문자열과 BASE64로 인코딩 된 문자열을 포함하기 때문이다.
SMTP 연결
테스트하기
mailpop3는 잘 동작함을 확인했다. 리스트를 어떻게 보여주어야 하는가는 잘 생각해보아야 하겠지만, 아직은 테스트 단계이니 넘어가 보고 이번에는 SMTP 통신을 위해 nodemailer를 테스트해 본다. nodemailer는 이벤트 핸들러 기반으로 작성된 mailpop3에 비해 사용하기 상당히 쉽다. 연결하고 메일을 보내는 코드만 작성하면 된다.
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
}
});
transporter.sendMail({
from: process.env.TEST_EMAIL,
to: "playerdecuple@dekiyu.xyz",
subject: "nodemailer 테스트",
html: "<b>Hello, world!</b>"
}).then(info => {
console.log(info);
});
nodemailer는 transport를 생성하고, 생성된 transport가 메일을 송신하는 방식으로 작동한다. mailpop3는 필자가 직접 코드를 작성하여 명령어를 송신해야 하는 데에 반해, nodemailer는 SMTP 명령어를 송신하는 과정을 압축하여 transport를 생성하여 메일을 전송하게끔 만들기만 하면 된다.
대상 메일 주소별 전송 성공 여부, SMTP 서버에서 수신한 응답, 메시지 ID를 돌려받을 수 있다.
{
accepted: [ 'playerdecuple@dekiyu.xyz' ],
rejected: [],
ehlo: [ 'PIPELINING', '8BITMIME', 'SIZE 73400320', 'AUTH LOGIN PLAIN' ],
envelopeTime: 25,
messageTime: 2114,
messageSize: 333,
response: '250 2.0.0 20240714071111.XgLwZpPFRLy30WDiyh1c1w@me.dekiyu.xyz Message accepted for delivery',
envelope: { from: 'me@dekiyu.xyz', to: [ 'playerdecuple@dekiyu.xyz' ] },
messageId: '<6cbc1ff1-a44f-d0b0-19b5-4d70ad03f76b@dekiyu.xyz>'
}
받는 이메일 주소를 가진 계정으로 다음 메일에 로그인하면, 다음과 같이 성공적으로 메일을 받았음을 확인해 볼 수 있다. Gmail과 같은 다른 메일 주소로도 스팸 메일로 분류되지 않고 잘 전송되는 모습을 확인할 수 있었다.
Express.js
설정해 보기
Express.js는 간단한 코드로도 강력한 서버를 개발할 수 있는 웹 프레임워크이다. 미니멀리즘을 추구하는 것은 아니지만 빨리 개발하기 위해 적은 코드로도 큰 효율을 내기 위해 사용하기로 했다. 백엔드에서 예상되는 주요 기능이 웹 페이지를 보여주는 것과, POP3, SMTP 서버로의 데이터 전송, 클라이언트로부터 파일 업로드받기로 요약할 수 있을 정도로 규모가 크지 않아 의도에 부합한다.
require("dotenv").config();
const express = require('express');
const app = express();
const publicRouter = require("./route/public.route");
const endpointRouter = require("./route/endpoint.route");
const port = process.env.EVERYDAY_PORT;
app.listen(port, () => {
console.log(`Everyday server is listening on port ${port}.`)
});
app.use("/assets", express.static(__dirname + "/assets"));
app.use("/endpoints", endpointRouter);
app.use("/", publicRouter);
퍼블리싱된 웹 페이지를 보여주기 위한 라우터와 REST API를 위한 엔드포인트 라우터를 생성하고 연결해 주면 기본적인 Express.js 설정은 끝이다.
마치며
이 과정을 진행함으로써, Express.js 서버를 설정하고 SMTP, POP3 관련 라이브러리를 사용해 보았으며 플로우 설계를 완료했다. 큰 규모가 아닐 것이라 생각되기에 틈이 날 때 조금씩 개발하여도 빠르게 완료할 수 있으리라고 본다. 프로젝트의 길이가 짧더라도 한 번에 몰아 올릴 정도는 아니기에 여러 부작으로 나누어서 글이 작성될 수 있음에, 불편하시더라도 양해 부탁드린다.
'개발일지 > 웹' 카테고리의 다른 글
텍스트 에디터로 메일 전송 페이지 만들기: 웹메일 클라이언트 개발하기 6 (0) | 2024.07.28 |
---|---|
웹메일들은 어떻게 메일을 보여주는가: 웹메일 클라이언트 개발하기 5 (0) | 2024.07.23 |
로그인 페이지 구현해보기: 웹메일 클라이언트 개발하기 4 (0) | 2024.07.20 |
웹 우체국으로 편지 보내는 방법: 웹메일 클라이언트 개발하기 3 (0) | 2024.07.19 |
메일은 어떻게 받아오는가: 웹메일 클라이언트 개발하기 2 (0) | 2024.07.16 |