인터넷 이메일 시스템은 세 개의 주요 구성 요소, 사용자 에이전트(user agent), 메일 서버, SMTP로 이루어져 있다.
사용자는 사용자 에이전트를 통해 메시지를 쓰고, 보내고, 저장하고, 전달한다. 사용자가 메시지 작성을 완료하면 사용자 에이전트는 해당 메시지를 그 사용자의 메일 서버로 보내며, 메시지는 메일 서버의 발신 메시지 큐(outgoing message queue)로 들어간다.
메일 서버는 이메일 인프라의 핵심이다. 각 사용자는 메일 서버 내에 메일박스(mailbox)를 가지고 있으며, 각 메일박스는 해당 사용자에게 보내진 메시지를 관리하고 유지한다.
메시지는 송신자의 사용자 에이전트를 출발해 송신자 메일 서버로 보내지고, 수신자 메일 서버로 보내져 수신자의 메일박스로 들어 간다. 송신자의 메일 서버는 수신자의 메일 서버로 메일을 전송할 수 없을 때, 해당 메시지를 메시지 큐에 가지고 있다 나중에 재전송한다.
SMTP는 이메일의 주요 애플리케이션-계층 프로토콜이다. SMTP는 송신자 메일 서버에서 수신자 메일 서버로 메시지를 보내기 위해 TCP의 신뢰할 수 있는 데이터 전송 서비스를 사용한다. 다른 많은 애플리케이션-계층 프로토콜과 마찬가지로 SMTP에는 클라이언트와 서버의 두 쪽이 있다. 다른 메일 서버로 메일을 보내는 메일 서버는 SMTP 클라이언트의 역할을 하고, 다른 메일 서버로부터 메일을 받는 서버는 SMTP 서버의 역할을 한다.
SMTP는 HTTP보다 오래 됐다. SMTP는 그 자체로 훌륭한 프로토콜이기는 하지만, SMTP가 처음 표준화된 1982년과 달리, 오늘날에는 여러 제약 사항으로 인해 그 자체로만 쓰이는 경우는 드물다. 예를 들어 SMTP에는 헤더 뿐만 아니라 바디도 7-비트 아스키로 제한되어 있다. 사람들이 이메일을 통해 큰 첨부 파일을 보내거나 하지는 않았던 1980년대에야 이 제약 사항이 문제가 되지 않았겠지만, 여러 멀티미디어 파일들을 이메일로 보내곤 하는 오늘날에 이 제약은 뼈아프다.
이제 SMTP의 기본적인 동작들을 시나리오를 통해 알아보자. Alice는 Bob에게 간단한 아스키 메시지를 보내려 한다.
SMTP에서는 수신 메일 서버와 송신 메일 서버가 서로 지구 반대편에 있다 하더라도, 중간 메일 서버를 이용한다거나 하지 않고, 직접 TCP 연결을 맺는다. 메일을 보내려 할 때, 수신자의 메일 서버가 다운되어 있다면, 해당 메시지는 송신자의 메시지 큐에 들어가 있지, 중간 메일 서버의 어딘가에 들어가 있거나 하지 않는다.
어떻게 SMTP가 메시지를 송신 메일 서버에서 수신 메일 서버로 전송하는지를 더 자세히 알아보자.
SMTP 클라이언트와 서버 사이의 메시지 교환을 나타내는 아래의 대화록을 보자. 서버의 호스트명은 hamburger.edu
, 클라이언트의 호스트명은 crepes.fr
이다. S: 는 서버가 자신의 TCP 소켓으로 보내는 메시지, C: 는 클라이언트가 자신의 TCP 소켓으로 보내는 메시지다. 이미 클라이언트와 서버 사이의 TCP 연결이 이루어졌음을 가정한다.
S: 220 hamburger.edu
C: HELO crepes.fr
S: 250 Hello crepes.fr, pleased to meet you
C: MAIL FROM: <alice@crepes.fr>
S: 250 alice@crepes.fr ... Sender ok
C: RCPT TO: <bob@hamburger.edu>
S: 250 bob@hamburger.edu ... Recipient ok
C: DATA
S: 354 Enter mail, end with ”.” on a line by itself
C: Do you like ketchup?
C: How about pickles?
C: .
S: 250 Message accepted for delivery
C: QUIT
S: 221 hamburger.edu closing connection
위 대화록에서 클라이언트는 서버에 "Do you like ketchup? How about pickles?"라는 메시지를 보내고 있고, 또한 클라이언트가 HELO, MAIL FROM, RCPT TO, DATA, QUIT
의 다섯 커맨드를 사용하고 있음을 볼 수 있다. 또 마지막에 마침표 하나(.)도 볼 수 있는데, 이는 서버에 메시지의 끝을 알려주기 위함이다. 아스키 코드로 본다면, 각 메시지의 끝에는 CRLF
도 붙어 있다.
서버는 각 커맨드에 대해 답을 하는데, 이 응답에는 답장 코드와 그에 대한 설명이 포함되어 있다.
SMTP는 지속 연결을 사용한다. 만약 송신 메일 서버에 수신 메일 서버로 보낼 메시지가 여러 개 있다면, 이 메시지들은 하나의 TCP 연결을 통해 보내진다. 각 메시지 마다 클라이언트는 새로운 MAIL FROM:
을 통해 프로세스를 시작하며, 모든 메시지가 보내져 남은 메시지가 없는 경우에만 QUIT
을 보낸다.
SMTP와 HTTP는 모두 파일을 한 호스트에서 다른 호스트를 보낼 때 쓰이며, 파일 전송 시 모두 지속 연결을 사용한다는 공통점이 있다. 하지만 두 프로토콜 사이에는 중요한 차이점들이 있다.
한 사람에게서 다른 사람에게로 이메일 메시지를 보낼 때, 메시지 바디 앞에 주변 정보들을 담은 헤더가 있어야 한다. 헤더 라인과 메시지 바디는 CRLF
로 구분되며, HTTP와 마찬가지로 각 헤더 라인에는 키워드: 값
형식의 텍스트가 들어있다.
각 헤더에는 From:
헤더 라인과 To:
헤더 라인이 있어야 하고, Subject:
헤더 라인이 있을 수도 있다. 다만 이 헤더 라인들은 앞서 봤던 SMTP 커맨드들과는 다르다. 이 커맨드들은 SMTP 핸드셰이킹을 위해 쓰이는 것들이고, 이 헤더 라인들은 메일 메시지 자체를 설명하기 위해 쓰이는 것들이다.
전형적인 메시지 헤더는 다음과 같다.
From: alice@crepes.fr
To: bob@hamburger.edu
Subject: Searching for the meaning of life.
메시지 헤더 뒤에는 빈 줄이 있고, 그 뒤에는 ASCII 메시지 바디가 뒤따른다.
Alice의 메일 서버에서 Bob의 메일 서버로 메시지가 전달되면, 이 메시지는 Bob의 메일 박스에 들어간다. 이 메일 박스에 들어있는 메시지를 읽으려면 어떻게 해야할까?
1990년대 초까지, 사용자는 메일 박스에 있는 메시지를 읽기 위해, 서버 호스트에 로그인 하고 거기서 돌아가는 메일 리더를 실행해야 했다. 하지만 오늘날 메일 접근은 클라이언트-서버 구조를 사용한다. 사용자는 자신의 종단 시스템에서 실행되는 클라이언트를 통해 이메일을 읽는다.
수신자가 자신의 사용자 에이전트를 로컬 PC에서 실행한다면, 메일 서버도 마찬가지로 로컬 PC에다 두는 게 자연스러워 보인다. 이때 송신자의 메일 서버는 수신자의 PC와 직접 대화를 하게 될 것이다. 하지만 이러한 방식에는 몇 가지 문제점들이 있다. 만약 수신자의 메일 서버가 로컬 PC에 위치한다면, 수신자의 PC는 언제 도착할지 모르는 새 메일을 받기 위해 항상 켜져 있어야 하고, 또 인터넷에도 연결되어 있어야 한다.
때문에 보통 사용자는 사용자 에이전트는 로컬 PC에서 실행하고, 항상 켜져 있는 공유 메일 서버에 저장된 메일박스에 접근함으로써 자신이 받을 메일을 가져 온다. 이 메일 서버는 다른 사용자들과 공유되며, 보통 사용자의 ISP를 통해 유지된다.
다시 Alice에게서 Bob으로 이메일 메시지를 보낸다고 해보자. 이때 Alice의 메시지는 어떻게든 Bob의 메일 서버에 저장되어야 한다. Alice의 사용자 에이전트에서 Bob의 메일 서버로, SMTP를 이용해 직접 메시지를 보낼 수도 있다. 하지만 보통 송신자의 사용자 에이전트는 수신자 메일 서버와 직접 대화하지 안흔다. 그 대신 Alice의 사용자 에이전트는 SMTP를 이용해 자신의 메일 서버로 이메일 메시지를 푸시하고, Alice의 메일 서버도 SMTP를 이용해 이메일 메시지를 Bob의 메일 서버로 중계한다. 왜 굳이 이렇게 두 단계를 거치게 만들었을까?
가장 큰 이유는, 송신자의 사용자 에이전트에서 수신사의 메일 서버로 직접 메시지를 보내는 경우 , 수신자의 메일 서버가 어떤 이유로 다운되어 있다면 해당 수신자에게 메일을 보낼 수 없기 때문이다. Alice의 메시지를 우선 메일 서버에 담아 둔다면, Bob에게 메시지를 지속적으로 다시 보내 위와 같은 경우를 해결할 수 있다.
그렇다면 수신자 Bob은 어떻게 자신의 사용자 에이전트를 로컬 PC에서 실행하면서, Bob의 ISP에 위치한 메일 서버에서 자신의 메시지를 가져올 수 있을까? Bob의 사용자 에이전트는 메시지를 가져오기 위해 SMTP를 사용할 수 없다. 메시지를 가져오는 것은 pull이고, SMTP는 push 프로토콜이기 때문이다. 이를 위해서는 Bob의 메일 서버에서 로컬 PC로 메시지를 전송하기 위한 특별한 메일 접근 프로토콜이 필요하다.
POP3는 아주 간단한 메일 접근 프로토콜인데, 너무 단순한 나머지 기능적으로도 제한이 있다. POP3는 사용자 에이전트가 포트 110으로 메일 서버와 TCP 연결을 열 때 시작한다. TCP 연결이 되고 나면 POP3는 authorization, transaction, update의 세 단계로 진행된다.
quit
커맨드를 보내 POP3 세션을 마치고 나면 일어난다. 여기서 메일 서버는 삭제하도록 마크된 메시지들을 삭제한다. POP3 transaction에서, 사용자는 커맨드를 보내고, 서버는 각 커맨드에 대한 응답을 보낸다. 여기에는 두 가지 응답이 있을 수 있는데, +OK
와 -ERR
다.
Authorization 단계에는 user <username>
과 pass <password>
의 두 커맨드가 있다.
telnet mailServer 110
+OK POP3 server ready
user bob
+OK
pass hungry
+OK user successfully logged on
만약 커맨드를 잘못 입력하면 POP3 서버는 -ERR
메시지로 응답하게 될 것이다.
Transaction 단계를 보자. POP3를 사용하는 사용자 에이전트는 "download and delete" 모드와 "download and keep" 모드 중 하나를 선택할 수 있다. POP3 사용자 에이전트가 만드는 커맨드 시퀀스는 사용자 에이전트가 이 두 모드 중 어떤 모드를 선택하고 있는지에 따라 달라진다.
Download-and-delete 모드에서 사용자 에이전트는 list, retr, dele
커맨드를 보낸다. 아래 대화록에서 C: 는 사용자 에이전트, S: 는 메일 서버다.
C: list
S: 1 498
S: 2 912
S: .
C: retr 1
S: (blah blah ...
S: .................
S: ..........blah)
S: .
C: dele 1
C: retr 2
S: (blah blah ...
S: .................
S: ..........blah)
S: .
C: dele 2
C: quit
S: +OK POP3 server signing off
사용자 에이전트는 우선 메일 서버에 저장된 각 메시지의 사이즈를 리스트해달라고 묻는다. 이후 사용자 에이전트는 각 메시지를 탐색하고 서버로부터 삭제한다. 마지막 quit
커맨드 이후 POP3 서버는 업데이트 단계로 넘어가 메시지 1, 2를 메일박스에서 삭제한다.
Download-and-delete 모드를 사용하는 경우, 사용자는 같은 메시지를 다른 기기에서 받을 수 없게 된다. 예컨대 Bob이 PC에서 위의 1번 메시지를 읽었다고 해보자. 이 메시지는 Bob의 메일 박스에서 사라져서 다른 기기에서는 읽을 수 없게 된다. 이와 달리 Download-and-keep 모드를 이용하면, 사용자는 메시지를 다운로드 한 후 메일 서버에 해당 메시지를 그대로 남겨 놓는다. 이 경우 Bob은 메시지를 다른 기기에서도 읽을 수 있게 된다.
사용자 에이전트와 메일 서버 사이의 POP3 세션 동안, POP3 서버는 어떤 사용자 메시지가 삭제되도록 마크됐는지 등의 상태 정보를 유지한다. 하지만 이 상태 정보는 POP3 세션 간에는 유지되지 않는다.
POP3 접근을 이용하면, Bob은 메시지를 로컬 머신으로 다운로드 한 후, 메일 폴더를 만들어 다운로드 한 메시지를 폴더로 이동시킬 수 있다. Bob은 다운로드한 메시지를 삭제하거나, 이동하거나, 탐색할 수 있지만, 이런 동작들은 모두 Bob의 로컬 머신에서만 일어난다. 그렇기 때문에 POP3는 폴더 계층 구조를 어떤 컴퓨터에서든 접근할 수 있는 원격 서버에서도 유지하고 싶은 사용자에게는 좋은 선택지가 되지 못한다.
위와 같은 문제를 해결하기 위해 만들어진 게 IMAP이다. IMAP은 POP3와 같은 메시지 접근 프로토콜인데, POP3보다 더 많은 기능들을 제공하는 대신 조금 더 복잡하다.
IMAP 서버는 메시지를 폴더로 관리한다. 메시지는 서버에 도착하고 나서 수신자의 INBOX 폴더에 들어간다. 수신자는 메시지를 새로운, 사용자가 생성한 폴더로 이동시킬 수도 있고, 메시지를 삭제할 수도 있으며, IMAP 프로토콜은 이런 동작들을 위한 여러 커맨드들도 제공한다. IMAP은 또한 원격 폴더에서 특정 기준에 맞는 메시지들을 검색하기 위한 커맨드도 제공한다.
IMAP의 경우, 사용자 에이전트가 메시지의 컴포넌트를 얻을 수 있도록하는 커맨드도 제공한다. 예를 들어 사용자 에이전트는 메시지의 헤더를 얻을 수도 있고, 멀티파트 MIME 메시지의 한 파트만을 얻을 수도 있다. 이러한 기능은 사용자 에이전트와 메일 서버 사이에 저-대역폭 연결만이 가능한 경우 유용하게 쓰일 수 있다.
오늘날에는 많은 사람들이 웹 브라우저를 통해 이메일에 접근한다. 이 서비스를 이용하는 경우, 사용자 에이전트는 웹 브라우저가 되고, 사용자는 HTTP를 통해 원격 메일 박스와 통신한다. 수신자가 메일 박스의 메시지에 접근하고 싶을 때, 이메일 메시지는 POP3나 IMAP 프로토콜이 아니라 HTTP를 통해 밥의 브라우저로 메시지를 전송한다. 송신자가 이메일 메시지를 보낼 때에도 마찬가지로, 이메일 메시지는 HTTP를 통해 사용자 브라우저에서 메일 서버로 전송된다. 다만 송신자 메일 서버에서 수신자 메일 서버로 메시지를 보낼 때는 여전히 SMTP를 사용한다.