SMTP
고친 사람 github-actions[bot]
SMTP 는 메일 한 통을 보내는 쪽에서 받는 쪽 메일 서버까지 실어 나르는 프로토콜입니다. 보내는 주소와 받는 주소를 먼저 알린 다음 본문을 넘기고, 서버는 단계마다 숫자 코드로 답합니다. 메일함에 쌓인 메일을 꺼내 읽는 일은 하지 않습니다.
쉽고 빠른 이해
SMTP 는 메일 한 통을 상대 메일 서버까지 배달하는 약속입니다. 보내는 프로그램이 서버에 접속해 누가 보내고 누구에게 가는지를 먼저 말하고, 그다음에 본문을 넘깁니다.
이게 없으면 메일 프로그램마다 배달 방식이 달라 서로 메일을 주고받지 못합니다. 세상의 메일 서버가 모두 같은 말로 이야기하게 만드는 것이 이 프로토콜의 일입니다.
어떻게 도는가:
- 보내는 쪽이 받는 쪽 서버에 접속하면 서버가 먼저 인사를 건넵니다
- 보내는 주소와 받는 주소를 한 줄씩 알리고, 서버는 줄마다 받아들일지 답합니다
- 본문을 넘긴 뒤 점 하나만 있는 줄로 끝을 알리면 서버가 접수합니다
대가도 있습니다. 접수했다는 답은 서버가 맡았다는 뜻이지 사람에게 닿았다는 뜻이 아닙니다. 그리고 처음 만들 때 보내는 사람을 확인하는 장치를 두지 않아서, 주소를 속이는 일을 프로토콜 혼자서는 막지 못합니다.
상세
SMTP(Simple Mail Transfer Protocol, 단순 메일 전송 프로토콜)는 메일 한 통을 보내는 쪽에서 받는 쪽 메일 서버까지 옮기는 프로토콜입니다. 이 절은 SMTP 가 전자메일 전체 흐름에서 맡는 구간, 한 번의 대화가 어떻게 생겼는지, 서버가 돌려주는 숫자를 읽는 법, 그리고 배달이 어긋났을 때 벌어지는 일을 차례로 봅니다.
우편으로 치면 집배원의 일입니다. 봉투 겉면에 적힌 보내는 사람과 받는 사람만 보고 다음 우체국으로 넘깁니다. 안에 든 편지가 무슨 내용인지는 보지 않고, 받는 사람이 봉투를 뜯어 읽는 일에도 끼어들지 않습니다.
메일이 지나가는 길에서 SMTP 가 맡는 구간
메일 한 통은 여러 손을 거칩니다. 사람이 쓰는 메일 프로그램이 있고, 그 프로그램이 메일을 맡기는 서버가 있고, 받는 쪽 메일 서버가 있고, 받는 사람이 메일을 꺼내 보는 단계가 있습니다.
SMTP 는 이 가운데 맡기는 구간과 나르는 구간을 맡습니다. 메일 프로그램이 자기 서버에 메일을 제출할 때 한 번 돌고, 그 서버가 받는 쪽 서버로 넘길 때 또 한 번 돕니다.
꺼내 읽는 구간은 다른 프로토콜의 몫입니다. 받는 사람이 메일함을 열어 목록을 보고 한 통을 여는 일은 IMAP(Internet Message Access Protocol, 인터넷 메시지 접근 프로토콜)이나 POP3(Post Office Protocol 3, 우체국 프로토콜 3)가 합니다. SMTP 로는 받은 메일을 꺼내 올 수 없습니다.
flowchart TD
A["보내는 사람의 메일 프로그램"] -->|SMTP| B["보내는 쪽 메일 서버"]
B -->|SMTP| C["받는 쪽 메일 서버"]
C --> D["받는 사람의 메일함"]
D -->|"IMAP · POP3"| E["받는 사람의 메일 프로그램"]
상대 서버를 찾는 방법
보내는 쪽 서버는 받는 주소의 도메인만 압니다. [email protected] 이라면 example.com 입니다. 이 이름만으로는 어느 기계에 접속해야 할지 모릅니다.
그래서 DNS(Domain Name System, 도메인 이름 시스템)에 묻습니다. 도메인마다 MX 레코드(Mail Exchanger, 메일 교환기) 항목을 둘 수 있고, 거기에 그 도메인의 메일을 받아 줄 서버 이름이 적혀 있습니다.
MX 레코드는 여러 개여도 됩니다. 각각 우선순위 숫자를 달고 있고, 숫자가 작을수록 먼저 시도합니다. 앞선 서버가 답하지 않으면 다음 숫자의 서버로 넘어갑니다.
한 번의 대화
보내는 쪽은 TCP(Transmission Control Protocol, 전송 제어 프로토콜) 연결을 엽니다. 서버끼리 메일을 넘길 때 쓰는 포트 번호는 25 번입니다.
연결이 열리면 대화는 줄 단위로 오갑니다. 보내는 쪽이 명령 한 줄을 적어 보내면 받는 쪽이 한 줄로 답하고, 그 답을 보고 다음 명령을 보냅니다. 아래는 메일 한 통이 접수되기까지의 전체 대화입니다. C: 가 보내는 쪽, S: 가 서버입니다.
C: (연결을 연다)
S: 220 mail.example.com ESMTP // 준비됐다
C: EHLO client.example.org
S: 250-mail.example.com // 확장 목록
S: 250 SIZE 52428800
C: MAIL FROM:<a@example.org>
S: 250 OK // 보낸이 접수
C: RCPT TO:<b@example.com>
S: 250 OK // 받는이 접수
C: DATA
S: 354 Start mail input // 본문 주세요
C: From: a@example.org
C: To: b@example.com
C: Subject: 안녕하세요
C:
C: 본문입니다.
C: .
S: 250 OK: queued as 3F2A1 // 맡았다
C: QUIT
S: 221 Bye // 끊는다
첫 명령인 EHLO 는 인사입니다. 보내는 쪽이 자기 이름을 밝히면 서버는 자기가 알아듣는 확장 기능의 목록을 붙여 답합니다. 위 대화에서는 SIZE 가 그 목록의 한 줄이고, 한 통에 받을 수 있는 최대 바이트 수를 알려 줍니다.
그다음 세 명령이 실제 배달을 만듭니다. MAIL 이 보내는 주소를, RCPT 가 받는 주소를 알립니다. 받는 사람이 여럿이면 RCPT 를 그 수만큼 되풀이합니다. 마지막으로 DATA 를 보내면 서버가 354 로 답하고, 그때부터 넘어가는 줄은 전부 본문으로 쌓입니다.
sequenceDiagram
participant 보내는쪽
participant 서버
보내는쪽->>서버: 연결
서버-->>보내는쪽: 220 준비됐다
보내는쪽->>서버: EHLO · 나는 누구다
서버-->>보내는쪽: 250 · 내가 아는 확장 목록
보내는쪽->>서버: MAIL · 보내는 주소
서버-->>보내는쪽: 250 접수
보내는쪽->>서버: RCPT · 받는 주소
서버-->>보내는쪽: 250 접수
보내는쪽->>서버: DATA
서버-->>보내는쪽: 354 본문을 보내라
보내는쪽->>서버: 본문 · 마지막에 점 한 줄
서버-->>보내는쪽: 250 맡았다
보내는쪽->>서버: QUIT
서버-->>보내는쪽: 221 끊는다
봉투와 편지지의 구분
위 대화를 다시 보면 주소가 두 번 나옵니다. MAIL · RCPT 명령에 한 번, DATA 뒤 본문의 From: · To: 줄에 또 한 번입니다. 둘은 같은 값이 아니어도 됩니다.
명령으로 오간 주소를 봉투라고 부릅니다. 배달은 이 봉투만 보고 이뤄집니다. 본문 앞머리의 From: · To: · Subject: 줄은 편지지에 해당하고, 이 줄들의 문법은 인터넷 메시지 형식이라는 별도의 규칙이 정합니다. SMTP 는 편지지 안을 들여다보지 않고 통째로 나릅니다.
flowchart TD
subgraph ENV["봉투 · 명령으로 오간다"]
M["MAIL · 보내는 주소"]
R["RCPT · 받는 주소"]
end
subgraph LET["편지지 · DATA 뒤에 넘어간다"]
H["헤더 · From · To · Subject"]
B["본문"]
end
ENV --> LET
이 구분이 실무에서 두 가지를 설명해 줍니다. 하나는 숨은참조입니다. 숨은참조로 넣은 사람은 봉투의 RCPT 에만 들어가고 편지지에는 안 적혀서, 받은 사람들이 서로를 볼 수 없습니다.
다른 하나는 위조입니다. 편지지의 From: 줄은 보내는 쪽이 아무 값이나 적을 수 있고 SMTP 는 그것을 검사하지 않습니다. 메일 프로그램이 화면에 보여 주는 보낸 사람이 바로 이 줄입니다.
본문의 끝을 알리는 점 하나
DATA 뒤로는 본문이 줄줄이 이어집니다. 서버는 어디까지가 본문인지 알아야 다음 명령을 기다릴 수 있습니다. 그 끝 표시가 점 하나만 있는 줄입니다.
그러면 본문 안에 점 하나로 시작하는 줄이 들어 있을 때가 문제가 됩니다. 그대로 보내면 서버가 거기서 본문이 끝났다고 여겨 메일이 잘립니다.
그래서 보내는 쪽은 점으로 시작하는 줄마다 점을 하나 더 붙여 보냅니다. 받는 쪽은 맨 앞 점 하나를 떼어 내고 저장합니다. 본문은 원래 모습 그대로 도착하고, 끝 표시는 끝 표시로만 읽힙니다.
숫자 코드로 오는 응답
서버의 답은 언제나 숫자 세 개로 시작합니다. 뒤에 붙는 영어 문장은 사람이 읽으라고 둔 것이고, 보내는 프로그램이 보는 것은 숫자입니다. 상태 코드를 이렇게 쓰는 방식은 여러 프로토콜이 공유합니다.
그 셋 가운데 첫 자리가 보내는 쪽이 무엇을 해야 하는지를 정합니다. 뒤따르는 둘은 이유를 좀 더 좁혀 줍니다.
| 첫 자리 | 뜻 | 보내는 쪽이 할 일 | 예 |
|---|---|---|---|
| 2 | 받아들였다 | 다음 명령으로 넘어간다 | 250 접수 · 221 연결 종료 |
| 3 | 더 보내라 | 이어서 본문을 넘긴다 | 354 본문 입력 시작 |
| 4 | 지금은 안 된다 | 나중에 다시 시도한다 | 421 서비스 사용 불가 · 450 메일함 사용 중 |
| 5 | 앞으로도 안 된다 | 포기하고 보낸 사람에게 알린다 | 550 그런 사람 없음 · 500 문법 오류 |
4 와 5 의 차이가 실무에서 제일 크게 갈립니다. 4 로 시작하는 답은 일시적인 사정이라는 뜻이라 메일이 살아남고, 5 로 시작하는 답은 다시 보내도 같다는 뜻이라 메일이 거기서 끝납니다.
못 보낸 메일과 반송
상대 서버가 4 로 답했거나 아예 연결이 안 되면 보내는 쪽은 메일을 버리지 않습니다. 큐에 넣어 둡니다. 큐는 아직 못 보낸 메일을 쌓아 두는 대기 목록이고, 서버는 시간 간격을 두고 큐의 메일을 다시 시도합니다.
정해진 기간 안에 끝내 성공하지 못하면 포기합니다. 그때 서버는 보낸 사람에게 실패를 알리는 메일을 새로 만들어 보냅니다. 이것이 반송 메일입니다. 5 로 답이 온 경우에는 기다릴 이유가 없으므로 곧바로 반송합니다.
flowchart TD
A["서버가 답한 숫자"] --> B{"첫 자리"}
B -->|"2"| E["배달 끝"]
B -->|"4"| C["큐에 넣고 나중에 다시"]
C --> D{"기간 안에 성공했나"}
D -->|"성공"| E
D -->|"끝내 실패"| F["반송 메일을 보낸 사람에게"]
B -->|"5"| F
메일이 한 번에 목적지까지 못 갈 수도 있습니다. 중간 서버가 받아서 다음 서버로 넘기는 일을 릴레이라고 합니다. 릴레이를 하는 서버는 본문을 고치지 않는 대신, 언제 누구에게서 받아 누구에게 넘겼는지를 적은 줄 하나를 편지지 맨 위에 덧붙입니다.
이 줄이 쌓여 메일이 지나온 길이 됩니다. 헤더 맨 위가 가장 나중에 거친 서버이고 아래로 내려갈수록 처음에 가깝습니다. 메일이 늦게 온 이유를 찾을 때 이 줄들의 시각을 견주어 봅니다.
ASCII 만 전제한 본문
SMTP 는 본문이 ASCII(American Standard Code for Information Interchange, 미국 정보 교환 표준 부호) 글자로만 되어 있다고 전제하고 만들어졌습니다. 영어 알파벳과 숫자, 기호 몇 가지가 전부입니다. 한글도, 사진도, 첨부 파일도 그대로는 실을 수 없습니다.
이 제약을 SMTP 를 고쳐서 풀지 않고 그 위에 규칙을 한 겹 더 얹어서 풀었습니다. MIME(Multipurpose Internet Mail Extensions, 다목적 인터넷 메일 확장)이 그 규칙입니다. 임의의 바이트를 base64 같은 방식으로 ASCII 글자로 바꿔 담고, 본문을 여러 조각으로 나눠 각 조각이 무엇인지 표시합니다.
우리가 쓰는 첨부 파일과 이미지가 박힌 메일은 전부 이 방식으로 나릅니다. 나중에는 8 비트 글자를 그대로 실어 나르는 확장도 생겼지만, 상대 서버가 그 확장을 알아들을 때만 쓸 수 있습니다.
포트 25 와 587, 그리고 암호화
25 번 포트는 서버끼리 메일을 넘기는 통로입니다. 여기에는 누구나 접속할 수 있어야 합니다. 어느 서버가 우리 도메인으로 메일을 보내올지 미리 알 수 없기 때문입니다.
사람이 자기 메일을 맡길 때는 587 번 포트를 씁니다. 이쪽은 아무나 쓰면 안 되므로 서버가 아이디와 비밀번호를 요구합니다. 두 통로를 가른 이유가 여기 있습니다. 한 통로에 섞어 두면 인증을 요구할 수도 없고 안 할 수도 없습니다.
연결 자체는 원래 평문이었습니다. 그래서 EHLO 응답의 확장 목록에 STARTTLS 가 들어 있으면, 보내는 쪽이 같은 이름의 명령을 보내 그 연결을 TLS(Transport Layer Security, 전송 계층 보안)로 감쌉니다. 감싼 뒤에는 인사부터 다시 시작합니다.
암호화는 오가는 내용을 가려 줄 뿐 보내는 사람이 진짜인지는 말해 주지 않습니다. 앞서 본 대로 편지지의 보낸 사람 줄은 아무 값이나 적힐 수 있습니다. 그 구멍을 메우려고 도메인 소유자가 발송 서버를 미리 밝혀 두거나 메일에 서명을 붙이는 장치가 뒤늦게 얹혔습니다.
관련 항목
SMTP 한 번의 대화에 오가는 명령
EHLO · HELO · MAIL · RCPT · DATA · QUIT · RSET · NOOP · VRFY · SMTP AUTH
SMTP 서버가 돌려주는 응답 코드
상태 코드 · SMTP 220 · SMTP 221 · SMTP 250 · SMTP 354 · SMTP 421 · SMTP 450 · SMTP 500 · SMTP 550
SMTP 가 나르는 메일을 이루는 부분
봉투 · 인터넷 메시지 형식 · 헤더 · Received 헤더 · 메시지 아이디 · 첨부 파일 · 숨은참조
SMTP 주소에 쓰이는 이름
메일 주소 · reverse-path · forward-path · 로컬 파트 · 도메인 이름 · 널 반환 경로
메일을 메일함에서 꺼내 오는 프로토콜
IMAP · POP3 · JMAP · 메일박스 · 웹메일
SMTP 가 올라타는 아래 계층의 프로토콜
TCP · DNS · MX 레코드 · TLS · STARTTLS · 포트 번호
메일 본문에 글자와 첨부를 담는 규칙
MIME · base64 · quoted-printable · ASCII · UTF-8 · 8BITMIME · SMTPUTF8
배달이 실패할 때 생기는 메일과 장애
반송 메일 · 메일 큐 · 메일 루프 · 블랙리스트 · 배달 지연 알림
보내는 쪽이 진짜인지 확인하는 장치
SPF · DKIM · DMARC · 발신자 위조 · 메일 서명
SMTP 를 통해 퍼지는 악용과 그 대응
스팸 · 피싱 · 오픈 릴레이 · 그레이리스팅 · 백스캐터
SMTP 를 구현한 메일 서버와 발송 대행 서비스
Postfix · Sendmail · Exim · Microsoft Exchange · Amazon SES · SendGrid · Mailgun
SMTP 를 직접 다루는 도구와 라이브러리
telnet · swaks · MailHog · smtplib · JavaMail · Nodemailer
SMTP 가 속하는 상위 분류
전자메일 · 프로토콜 · 응용 계층 · 인터넷 프로토콜 스위트 · 네트워크
다른 이름: Simple Mail Transfer Protocol · 단순 메일 전송 프로토콜 · 메일 전송 프로토콜