Yuki's Tech Blog

仕事で得た知見や勉強した技術を書きます。

電子メールとSMTPについてざっくりまとめる

目次

概要

メールの仕組みついて雰囲気で理解していたので、ちゃんと理解しようと思い、まとめました。

電子メール(Email)とは

電子メールとはインターネット上で送る郵便のようなものです。文章や画像データなど、コンピュータで扱えるさまざまな情報を送ることができます。

電子メール送信の仕組み

初期の電子メールでは、電子メールの送信者が利用しているコンピュータと宛先のコンピュータ間で、直接TCPコネクションが貼られて電子メールが配送されていました。この時メールの送信にはSMTPプロトコルが使用されています。

しかし、この方法では両方のホストの電源が入っていて、常にインターネットに接続されていなければメールを配信できません。仮に相手のコンピュータの電源が入っていなくて通信できない場合、しばらく時間を置いてから再度メールの配信を試みていました。 メールを送信するコンピュータの電源を落とした場合、電源が入るまでメールを送信することはできません。 また、メールを受信するコンピュータは電源が落ちているとメールを受信できません。そしてメールを受信するコンピュータのホストがインターネットに接続していないときにはメールを受信できません。

この直接TCPコネクションを貼る方法は電子メールの信頼性を高める上では非常に良い方法でした。しかし、時差の影響だったり両方のホストが稼働しててインターネットに接続していないといけないなどのデメリットもありました。そのため、電子メールの送信者のコンピュータと受信者のコンピュータの間で直接TCP接続をするのではなく、電源を切らないメールサーバーを経由してメールを送信するようになりました。この仕組みを採用したことで、送信者はメールサーバーにsmtpプロトコルでメールを送るだけで良くなり、受信者はメールサーバーからPOP3プロコトルでメールを受信するだけで良くなりました。この仕組みを採用するにあたって、受信者がメールサーバーから電子メールを受け取るPOPというプロトコルが標準化されました。

メールアドレスの構造とMXレコードについて

  • メールアドレスは、「ユーザー名@ドメイン名」という構造になっています。
  • メールアドレス中のどこにもメールサーバを示す情報がないです。そのため、まずはDNSサーバーにアクセスして、DNSサーバが管理しているMXレコードを参照して、メールアドレスのドメイン名からメールサーバーのドメイン名を特定します。その後、またDNSサーバーにアクセスして、取得したメールサーバのドメイン名に対応するAレコードを参照して、メールサーバーのドメイン名に紐づくIPアドレスを取得します。
  • DNSサーバーは、Aレコード(ドメインに紐づくIPアドレスの対応関係)を管理しています。ホスト名の問い合わせに対してはこのAレコードが参照されます。DNSサーバーが管理しているMX(Mail eXchanger)レコードは、メールアドレスのドメインに紐づくメールサーバーのドメインの対応関係を管理しています。
  • DNSサーバには名前とアドレスを対応づけた“Aレコード”という情報が登録されており、ホスト名の問い合わせに対してこのAレコードが参照される。しかし「メールサーバはどちら?」とコンピュータの役割で問い合わせられても、Aレコードのどれが対応しているのか判断はできない。そのためDNSサーバには、そのドメインのメールサーバ名を定義する「MX(Mail eXchanger)レコード」が別に登録されています。

メーラ(メールクライアント)とは

メーラとは、電子メールを送受信するためのソフトウェアやアプリケーションのことです。メーラはメールクライアントとも呼ばれます。 メールクライアントの代表的な例は、gmailoutlookです。

SMTPプロトコルとHTTPプロトコルの関係性

SMTPプロトコルもHTTPプロトコルと一緒で、「何を伝えるか」に関するプロトコルです。「何を」伝えるか に関するプロトコルは、提供したいサービスによって変わってきます(メールを送るサービスならSMTPWebサービスであればHTTP)。これらのプロトコルでは、送る側も受け取る側も、メッセージは「漏れなく順序よく」届くことは大前提として作られています。つまり、HTTPもSMTPもPOPも、 TCP通信を行うことを前提としたプロトコル ということになります。 (TCP以下のプロトコルではどうやって送るかを定めていて、TCPより上のプロコル(HTTPやSMTP)では、何を伝えるかを定めている)

SMTP(Simple Transfer Protocol)とは

SMTPは、メールサーバーにメールを送信するためのプロトコルです。 具体的にいうと、SMTPクライアントとSMTPサーバー間で TCP プロトコルを使用してメッセージをやり取りしながら、メールを送信するプロトコルです。 SMTPには2つの使い方があります。

ユーザーがメールクライアント(メーラ)を利用して、メールサーバにメールを送信するという使い方

メールを送信する際には、差出人のメールアドレスのドメインから、そのドメインに紐づくメールサーバーを特定して、SMTPに従ってメールを送信します。その後、もし、送信者と受信者が同じメールサーバーを使っていた場合、メール受信者はPOP3プロトコルを利用して、同じメールサーバからメールを取得できます。この場合、送信者と受信者のメールアドレスのドメイン名が同じである必要があります(MXレコードでドメイン名にメールサーバが紐づいているから)。

メールサーバから別のメールサーバに対してメールデータを送信するという使い方

これは、送信者と受信者が別々のメールサーバを使うときに行われます。 何故これでメールが相手に届くかというと、SMTPサーバがメールを転送してくれるためである。 SMTPによるメールの転送はバケツリレーに例えられます。SMTPサーバは、とりえあず受け取ったメールを最適と思われる次のSMTPサーバに送ります。 メールを受け取ったSMTPサーバは自分宛でなければさらに次のSMTPサーバに送ります。 このような処理が繰り返されてメールは、相手が利用しているメールアドレスのドメインに紐づくメールサーバに到達します。 これがメールサーバ間で利用されるSMTPです。

SMTPが転送するもの

SMTPでは、メールオブジェクトを転送します。メールオブジェクトはエンベロープとコンテンツで構成されています。

エンベロープ

エンベロープは一連のSMTPセッションの中で送信されます。 エンベロープは、発信者アドレス、一つまたは複数の受信者アドレス、オプションのプロトコル拡張の要素の3つで構成されています。 (オプションのプロコル拡張がイマイチわかっていないので、いずれ調べます)

コンテンツ

コンテンツは、SMTPセッション内のDATAコマンドの中で送信されます コンテンツは、ヘッダ部とボディの2つの部分で構成されます。 (ヘッダとボディは空行によって仕切られます) ヘッダ部はヘッダフィールドの集合から構成されています。ボディはMIMEに従って定義する必要があります。

ボディにテキスト以外にHTMLを書くことができます(これが俗にいうHTMLメール)。 その場合は、content-typeにmultipart/alternativeというMIMEタイプを指定する必要があります。 メールの世界ではテキストメールが基本の形式です。HTMLはあくまで表現力を増すだけのものなので、テキストメールも必ず一緒に送りましょう。そうすることで、テキストメールしか対応していないクライアントでもメールを表示することができます。 contet-typeがmultipartのメールを受信したメーラは、設定や機能に応じてテキストパートもしくはHTMLパートを選択的に表示してくれます。

(注意)エンベロープとコンテンツのどちらにも発信者のメールアドレスと受信者のメールアドレスを設定する必要がある

エンベロープとコンテンツのどちらにも発信者のメールアドレスと受信者のメールアドレスを設定する必要があります。 エンベロープの方のアドレスは、メールの配送で使われます。配送が完了すると破棄されます。コンテンツの方のアドレスは、メールクライアント上でメールのアドレスを確認する際に使います。

メールサーバの内部について

この画像を見ることで、メールサーバーとは特別なサーバーというわけではなくて、SMTPサーバーソフトウェアとPOPサーバーソフトウェアがインストールされたホストなんだなということが分かります。

画像引用 (https://www.infraexpert.com/info/server13.html)

SMTPクライアントとSMTPサーバについて

SMTPサーバはSMTPクライアントは、SMTPに従って通信のやりとりをします。 SMTPクライアントは、電子メールを送信するために使用されるSMTPプロトコルを実装したソフトウェアやアプリケーションのことです。Gmailなどのソフトウェアが代表的です。つまり、メールクライアント(メーラ)はSMTPプロトコルを実装している実装しているソフトウェアでもあることが分かります。

SMTP クライアントを経由して、SMTPサーバーに対してメールを転送します。HTTPサーバと通信するクライアントのことをHTTPクライアントというのと同じようなものです。

                  +----------+                +----------+
      +------+    |          |                |          |
      | User |<-->|          |      SMTP      |          |
      +------+    |  Client- |Commands/Replies| Server-  |
      +------+    |   SMTP   |<-------------->|    SMTP  |    +------+
      | File |<-->|          |    and Mail    |          |<-->| File |
      |System|    |          |                |          |    |System|
      +------+    +----------+                +----------+    +------+
                   SMTP client                SMTP server

SMTPで通信する際の手順

以下にSMTPクライアントとSMTPサーバーがどのように通信しているかの図を示します。

SMTPクライアントは、サーバーにテキストベースのコマンドを送信します。SMTPサーバーはクライアントから送信されたコマンドへの応答としてコマンドが「成功」か「失敗」かを表すコードをリプライします。 SMTPクライアントはSMTPサーバからリプライが返ってくるまでは、次のコマンドを実行しません。つまり、SMTPプロトコルはシーケンシャルな(連続的な)プロトコルであることが分かります。

図の手順を文字でまとめます。

  1. smtpクライアントとsmtpサーバーがtcpコネクションを確立すると、smtpサーバーから220 Greetingが返される

  2. smtpクライアントは、EHLOコマンドを送信する。このコマンドを送信することで、smtpセッションが開始する。

  3. EHLOコマンドを受け取ったsmtpサーバは、特に問題がなければ250 OKを返す。
  4. smtpクライアントは、MAILコマンドを送信する。このMAILコマンドを実行することで、発信者のメールアドレスをエンベロープに設定できる。
  5. MAILコマンドを受け取ったsmtpサーバは、特に問題がなければ250 OKを返す。
  6. smtpクライアントは、RCPTコマンドを送信する。このRCPTコマンドを実行することで、受信者のメールアドレスをエンベロープに設定できる。
  7. RCPTコマンドを受け取ったsmtpサーバは、特に問題がなければ250 OKを返す。
  8. smtpクライアントは、DATAコマンドを送信する。このDATAコマンドを実行することで、コンテンツを設定できる。
  9. DATAコマンドを受け取ったsmtpサーバは、特に問題がなければ354 Readyを返す。 10 354 Readyを受け取ったsmtpクライアントは、コンテンツを入力していって、.でコンテンツの終了をsmtpサーバに知らせる。
  10. コンテンツを受け取ったsmtpサーバーは、特に問題がなければ250 OKを返す。
  11. smtpクライアントはQUITを送信する。このQUITを送信することで、SMTPセッションを終了できる。
  12. smtpサーバーから221 Closingが返されて、tcpの接続が切られる。

メールの送信は非同期でやった方が良い

SMTPはシーケンシャルなプロトコルです。クライアントとサーバーが何回もやり取りしてやっとクライアントが用意したメールをメールサーバーに送信できます。Webサービスでは新規会員登録後にメールを送信すると思いますが、そのメール送信をもし同期的にやってしまうと、smtpのシーケンシャルなやり取りが完了するまで新規会員登録完了のHTTPレスポンスを返せません。HTTPレスポンスを待っている間、ブラウザの画面が一時的に真っ白になってしまいユーザーが離脱する原因になってしまうでしょう。そのため、メールの送信は非同期でやった方が良いです。

telnetコマンドで実際にSMTPに接続してみた

まず、メールサーバーを用意します。MailHogを使って、簡易的なsmtpサーバーを構築します。

MailHogはGo製のツールです。smtpサーバ(ポート1025)をインストールしているサーバーを立ててくれます。 このサーバに対してポート1025でhttpリクエストすれば、レスポンスで管理画面が返されて、smtpサーバーに送信されたメールを確認できます。

MailHogのDockerイメージが提供されているので、そのイメージから、Dockerコンテナを構築します。

version: "3"
services:
  #メールサーバのコンテナ
  # 本来の SMTPでは、port:25が一般的
  mail:
    image: mailhog/mailhog
    container_name: mailhog
    ports:
      # HTTPサーバーのポートをバインド
      - "8025:8025"
      # SMTPサーバーのポートをバインド
      - "1025:1025"
    environment:
      MH_STORAGE: maildir
      MH_MAILDIR_PATH: /tmp
    volumes:
      - mail-volumes:/tmp
volumes:
  # mailhog はメールをメモリ上に保存するため、Docker のコンテナを停止するとメールが消えてしまう。
  # そのため、メールのデータをボリュームに保存しておく
  mail-volumes:

その後、telnetコマンドを使って、このコンテナのポート1025とTCPコネクションを確立します。Telnetとは、遠隔地にあるサーバーやルータを端末から操作するための通信プロトコル(このプロトコルはアプリケーション層のプロトコル)です。または、そのプロトコルを利用するソフトウェアのことです。

その後、以下のようにやりとりをするとメールを送信できます。

telnet localhost 1025
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 mailhog.example ESMTP MailHog
EHLO
250-Hello
250-PIPELINING
250 AUTH PLAIN
MAIL FROM: <sender@sample.com>
250 Sender sender@sample.com ok
RCPT TO: <receiver@sample.com>
250 Recipient receiver@sample.com ok
DATA
354 End data with <CR><LF>.<CR><LF>
To: receiver@sample.com
From: hoge@sample.com
Subject: hello test

test test 12345
.
250 Ok: queued as R4G48Dv2B9HYsqHz64sKBci2ZByne4Vc1gM___KoCg0=@mailhog.example
QUIT
221 Bye
Connection closed by foreign host.

管理画面で送られてきたメールを見ることで、メールがちゃんと送信されていることと、メールの中身が正しく表示されていることを確認できます。

goでSMTPクライアントを作ってみた

goではsmtpパッケージを使うことで、簡単にSMTPクライアントを作れます。 以下にテキストメールを送信した場合と、HTMLメールを送信した場合の実装コードのURLを貼ります。

テキストメールを送るsmtpクライアント github.com

htmpメールを送るsmtpクライアント

github.com

開発環境でのメールの送信について

開発環境でのテストはsmtpで送ったメールがちゃんと表示されているかがわかれば良いです。そのため、smtpサーバーに送られた内容が確認できれば良いです。メールはメールサーバーに格納されます。結局ユーザーはメールクライアントを使えば、メールクライアントが勝手にメールサーバーから、POP3プロトコルを使って、メールを取得することができるので、そこの手順まで開発環境でテストする必要はないです。そのため、SMTPの方が重要だなと思い、POP3についてはあまり深ぼっていないです。

独自ドメインでメールを出したい

gmailドメインとかではなくて、独自ドメインのメールアドレスを差出人としてメールを送信させたいなら、SendGridやAWS SESを使った方が良さそうです。

終わり

今までRailsのメーラの挙動はいまいちわかりませんでしたが、実際に使われているsmtpプロトコルをふかぼったり、Goでsmtpクライアントを実装したことで、だいぶSMTPを理解できたなと思いました。

参考記事

「エンベロープFrom」と「ヘッダFrom」の違いとは? - ベアメールブログ

メールサーバとは、SMTP/POP/IMAPとは

Goでメールサーバーを作る

HTMLメールとテキストメールを同時に送信するマルチパートメールとは | SendGridブログ

RFC5321(Simple Mail Transfer Protocol)

SMTPで送信するメールの形式 - Customers Mail Cloud ブログ