メール・到達率

メールアドレスの検証:正規表現で証明できること、できないこと

「検証」という一つの言葉で4種類のチェックが売られており、それらはこの順に難しくなっていきますが、本当に知りたかったことを証明するのは最後の一つだけです。パターンで証明できること、DNSルックアップ1回が何を付け加えるのか、メールサーバーに尋ねることが見た目ほど価値を持たない理由、そして4つのうち本物の答えと呼べるのはどれかを、ここで説明します。

  • 中級
  • 読了21分
大きな青いチェックマークの付いた灰色のクリップボードのそばに浮かぶ青い封筒。その手前には、バツ印の付いた、傾いた小さめの灰色のカードが置かれている。

1つの言葉に隠れた4つの問い

「これは有効なメールアドレスか?」は1つの問いではありません。実際には4つあり、この順番で難しくなっていき、誰もが本当に知りたかった答えは最後の1つだけです。だからこそ、検証コードの多くは最初の問いにだけ手が込んでいて、残りについては何も語らないのです。

サインアップフォームが出会う順に、その4つを並べます。

アドレスの形をしているか?
構文チェックです。ブラウザの中で動き、コストはゼロで、打ち間違えたコンマや抜け落ちた@を捕まえます。単独で正直に何かを拒否してよいと言えるのは、この層だけです — それでも、たいていのパターンよりずっと少ないものしか拒否すべきではありません。
そのドメインはそもそもメールを受け取れるか?
DNSチェックです。1回の問い合わせで、@より後ろの部分宛てのメールを、どこかの何かが受け取る気があるかどうかが分かります。文字が1つ抜けたドメイン名や、昨年失効したドメインを捕まえますが、メールボックスについては何一つ教えてくれません。
そのメールボックスは存在するか?
SMTPチェックです — そして、このガイドが最も長く「信用するな」と言い続けることになるものでもあります。尋ねることはできます。しかしその答えは、すべての名前を受け入れるサーバーからの丁寧な「はい」だったり、わざとの一時的な失敗だったり、受理しておいて数分後にバウンスさせることだったりと、さまざまです。
それは本人のアドレスか?
技術の側には、これに答えられるものが何もありません。アドレスは完璧で、配信可能で、それでいて他人のものであることがあります — 1文字打ち間違えた場合もあれば、あなたの目をごまかすためにわざと入力された場合もあります。決着をつけられるのは、届いて実際に使われたメッセージだけです。
パターンブラウザの中で動く文字列の形世界については何もわからないコストはゼロDNSルックアップMX、次にAドメインは受信できるメールボックスは分からないクエリ1回サーバープローブ尋ねてから切るほとんど何もないキャッチオールは全部に「はい」遅く、リスクもある確認済みメッセージリンクを一度だけ使うそこに誰かがいる唯一持つ価値のある答え本物のメッセージ1通どの行も、その上の行よりコストが高く、その分だけ価値も高くなっています。
4つの層と、それぞれが決着させること。人物についての事実を生み出すのは一番下の行だけであり、その上にある3つは、メッセージを送る価値があるところまで対象を絞り込んでいるにすぎません。

最初の3つは安く、証明することはわずかです。その名にふさわしいのは4つ目だけであり、メッセージ1通分のコストがかかるのもそれだけです。これから先の話はすべて、高価な1つを無駄にしないために、安い3つをうまく使うことについてです。

第1層:パターンと、それが知りえない4つのこと

メールアドレスのための公式な正規表現は存在せず、存在しようがありません。RFC 5322が定めているのはパターンではなく文法であり、その文法は、丸括弧内のコメントや、折り返された空白、ほとんどどんな文字でも含みうる引用文字列を許しています — そのどれも、実際のサインアップフォームが受け入れるべきものではなく、それでいて、忠実なパターンならすべて受け入れなければならないものです。

実際に存在するのは、ブラウザがすでに実装している、意図的に狭めた定義です。HTML仕様が<input type="email">フィールドに与えているものがそれです。これは標準の書き写しではなく、あえて選ばれた妥協であり、あなたのコードが1行動く前から、あなたのフォームはすでにこれに従わされています。そして、一息に読み切れるほど短いものです。

regex
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/

それを使うか、あなたのプラットフォームがすでに同梱しているものを使いましょう。以下の4つはすべて同じ層であり、どれを選ぶかは、そのあとに何をするかに比べれば小さな決定です。

環境すでに使えるチェック見落としがちな挙動
ブラウザの中で、コードを一切書かずに<input type="email" required>という指定あなたのJavaScriptがそのフィールドを目にするより前に上のパターンを適用し、訪問者自身の言語でメッセージを表示します。
PHPで、何もインストールせずにfilter_var($a, FILTER_VALIDATE_EMAIL)の1行HTMLのものより厳格です。ドメインにドットがあることを要求するため、1台のマシンの中でしか通用しないアドレスを拒否します。
Pythonで、小さなパッケージを1つ使ってemail_validator.validate_email(a)という呼び出し構文チェックを行い、許可すれば第2層のドメインルックアップも行います — 1回の呼び出しの裏に、安い2つの層が隠れています。
JavaまたはKotlinで、Bean Validationを通して@Emailというアノテーション意図的にかなり緩めです。明らかにおかしいものだけを捕まえるためのアノテーションであり、一度も存在したことのないドメインでも平然と受け入れます。

どちらを使うにせよ、大事なのはそれが何について語らないかです。合格と判定するパターンは、1つのことを教えてくれる一方で、4つのことについては何も教えてくれません。

ドメインが存在することではない
誰も登録したことのないドメインのアドレスでも、このセクションのパターンはすべて通過します。何も調べられておらず、何にも接続されていません。正規表現がネットワークにリクエストを送ったことは一度もないのです。
メールボックスが存在することではない
実在するドメインであっても、パターンは@より前の部分については何の意見も持ちません。その部分は受信側のサーバーだけのものであり、まさにDNSが決して教えてくれないことでもあります。
配信可能であることではない
ドメインが完璧なレコードを持ちながら、そのメールサーバーは1か月前から止まっている、ということもあります。構文は文字列の性質であり、配信可能性は、送信ボタンを押した瞬間の世界の性質です。
入力した本人のものであることではない
どんなデータベースにも共通して最も多い不正なアドレスは、サインアップした覚えのない誰かが所有する、実在し配信可能なアドレスです。1文字打ち間違えたせいであったり、もっともらしい嘘でフォームが埋められたせいであったりします。

第2層:1回のルックアップが決着させること

@より後ろの部分はドメインであり、ドメインには、メールの置き場所があるか、ないかのどちらかしかありません。1回のDNSクエリが数ミリ秒でそれに答えてくれ、これはこのガイド全体の中で最も価値の高いチェックです — というのも、これが捕まえる間違い、つまりわずかに打ち間違えられたドメイン名こそが、実際の失敗としては断トツで最もよくあるものだからです。

shell
$ dig +short MX example.com

答えが返るということは、ホストが指名されているということです。答えが返らないことは、メールが受け取れないことを意味しません。MXレコードがなくてもAレコードがあれば、送信側がそちらにフォールバックするため、依然として受信できます。結果は4通りあり、そのうち失敗と呼べるのは2つだけです。

ルックアップが返すもの受信できるか?フォームがすべきこと
MXレコードが1つ以上はい受け入れましょう。これは圧倒的多数のアドレスに当てはまり、送信前にこれ以上することは何もありません。
MXはないが、AまたはAAAAレコードがあるはい、フォールバックで受け入れましょう。珍しいケースですが完全に合法であり、送信側はそこへ配信します。これを拒否すれば、機能しているアドレスを失った顧客に変えてしまいます。
値全体が0 .である、たった1つのレコードいいえ、しかも意図的に拒否し、理由を伝えましょう。これはnull MXです。ドメインの所有者が、それを公開する唯一の方法で、「ここではメールを一切受け取らない」ことを公表しているのです。
ドメインがまったく解決しないいいえ拒否し、一番近いものを提案しましょう。人間が30秒前に入力した名前に対するNXDOMAINは、ほぼ常に1文字の間違いです。

このチェックがうまくいかなくなるのは、ルックアップそのものが原因であることは決してありません。原因は、そのルックアップをどこに置いたか、です — クリティカルパスの上、キー入力のたびに、しかも失敗すれば送信をブロックしてしまう形で置かれているのです。これを使えるものにしておくための6つのルールです。

  1. フィールドへの入力が終わったときに実行し、入力中には実行しない。フォーカスが外れたときに1回、あるいは送信時に1回。キー入力のたびにクエリを送れば、それはキー入力のたびのクエリでしかありません。
  2. まずドメインを小文字化する。DNSは気にしませんが、あなたのキャッシュは気にします。同じドメインの2通りの綴りは、2回のクエリではなく1回のクエリになります。
  3. MXを尋ね、Aにフォールバックする。2回のクエリのうち、条件付きなのは2回目だけです。MXだけをチェックするライブラリは、機能しているドメインまで拒否してしまいます。
  4. 答えを数分間キャッシュする。どこであっても、サインアップの大半はひと握りのドメインが占めているため、ほとんどのルックアップは、実質ルックアップなしになります。
  5. 失敗したら通す。リゾルバがタイムアウトしたら、アドレスを受け入れましょう。DNSプロバイダの不調な1分が、誰もかれもを追い返すフォームになってはいけません。
  6. 修正を提案する。決して自分で適用しない。ドメインがよくあるものから1文字違いのとき、その修正案をクリックできる形で提示しましょう。誰かが入力したものを黙って書き換えることは、確認リンクが見知らぬ相手に届いてしまう原因になります。

このルックアップには、知っておく価値のあるコストが1つあります。訪問者が入力した内容について、あなたのフォームが外部の世界に向けて話しかける最初の瞬間になる、ということです。それが問題になる場面では、代わりにこの層をまるごと飛ばし、メッセージそのものにチェックの全部を任せるという選択肢もあります。

第3層:サーバーに尋ねること、そしてその答えが答えになっていない理由

実際には何も送らずに、メールサーバーへ、特定のアドレスを受け取るかどうかを尋ねる方法があります。会話を開始し、送信者を名乗り、受信者を名指しし、そのコマンド1つへの返答を読み取って、メッセージの手前で電話を切るのです。

smtp
220 mx1.example.com ESMTP ready
EHLO checker.example.net
250 mx1.example.com
MAIL FROM:<probe@example.net>
250 2.1.0 Ok
RCPT TO:<someone@example.com>
250 2.1.5 Ok
QUIT
221 2.0.0 Bye

RCPT TOのあとに返る250こそ、世界中のアドレス検証サービスが最終的に売っているものです。それがどれほど価値の乏しいものかを、正確に言っておく価値があります。

キャッチオールドメインは何にでも「はい」と言う
すべての名前を受け入れるよう設定されたドメイン — このサービス自身がまさにそうであり、非常に多くの企業ドメインもそうです — は、誰も使ったことのないアドレスに対しても250を返します。その答えは真実であり、それでいて情報ではありません。
慎重なサーバーはわざと450を返す
グレーリスティングは、見知らぬ送信者の最初の試みを拒否し、しばらくしてから出直すよう求めます。本物の送信者はそうしますが、プローブは決してそうしません。この一時的な失敗はわざとの非回答であり、それを「そんなメールボックスは存在しない」と読み取ってしまうことこそ、まさにこの仕組みが誘発するように作られた間違いです。
先に受理し、あとから拒否するサーバーもある
大手プロバイダは、やり取りの最中にごく普通にメッセージを受け取り、判断はあとに回すことがよくあります。そのため拒否は、プローブが「問題なし」と返ってきた数分後に届くバウンスという形に変わります。
見覚えのない相手には誰にでも「いいえ」と言うサーバーもある
あなたのアドレスを見知らぬ相手だと判断したサーバーは、受信者とは何の関係もない理由で受信者を拒否することがあります。あなたが測っていたのは、相手のメールボックスではなく、自分自身のレピュテーションだったのです。
しかもそれは、守ろうとしていたはずのレピュテーションを消費する
受信者を名指ししながら何も送らない接続は、ディレクトリハーベストそのものの形をしています。それがまさにディレクトリハーベストだからです。自分のアドレスから大量に行うことは、ブロックリストに載る一番の近道であり、ブロックリストに載った送信者は、本物のメッセージすら届かなくなります。

このプローブが本当に役立つ、狭いケースが1つだけあります。自分が運用している、あるいは触ってよい許可を得ているドメイン上で、手作業で1つのアドレスを確認する場合です。サインアップフォームの中の1ステップとしては、遅く、しばしば間違っており、それが守るために追加されたはずのものを、ときに傷つけてしまいます。

ロールアドレス、使い捨てドメイン、そしてこれから買おうとしているリスト

ドメインルックアップと本物のメッセージのあいだには、そもそも有効性とは何の関係もない一群のチェックがあります。それらは、あなたがこのアドレスを欲しいかどうかについてのものであり、技術の衣装をまとったビジネス上の判断です — その理由だけでも、周囲の3つの層とは切り分けて考える価値があります。

ロールアドレス
info@support@admin@のような名前です。実在するものであり、たいていは個人ではなく共有のメールボックスであるため、パスワードが絡むものを置くにはあまり向いていません。フラグを立てる価値はありますが、拒否する価値があることはめったにありません。
使い捨てドメイン
このサイトが配っているようなアドレスで、捨てられるために存在しているドメインの上にあります。そうしたドメインの公開リストと照合すること自体はまっとうですが、そのリストはどれもダウンロードした日の時点ですでに不完全で、少し古いということを正直に認めているならの話です。
無料プロバイダのアドレス
企業ドメインでないものはすべて拒否する、というビジネス向けフォームもあります。それは1つの方針であり、ときには正しい方針でもあり、「検証」と呼ばれるものの中に隠すのではなく、訪問者が読める1文として、方針として明示的に書かれるべきものです。
よくあるドメインの近似ミス
よく知られたプロバイダの名前が1文字だけ違うものです。これは他の3つとは違います。方針ではなく、本物の間違いを捕まえるものであり、本人はいつも喜んでくれます。ここに挙げた中で、実装する価値があるのはこれだけです。

最初の3つには、判断を難しくする共通の性質があります。通過させてしまったものは測れても、それが払わせているコストは測れないのです。

ここには私たち自身の明らかな利害が絡んでいるので、率直なところをお伝えします。あなたのフォームの裏にあるものが、高価な何かとセットになった無料トライアルなら、使い捨てドメインを拒否することはお金の節約になり、そうすべきです。ニュースレターやダウンロード、誰かがお金を払って持つアカウントであれば、あなたが主に課税しているのは慎重な人たちです — そして慎重な人たちこそ、あなたが送るものを実際に読んでくれる人たちです。

第4層:メッセージそのものがチェックである

ここまでのすべては対象を絞り込むだけです。唯一重要な事実 — このアドレスが、あなたの目の前にいる人に届くということ — を確立するものは、その中にはありません。それを確立するのはただ1つ、そこに何かを送り、それが使われるのを見届けることだけです。

これは、ほぼすべてのサインアップがすでに備えているループであり、その半分は単なる形式として扱っています。

  1. 1つの緩いチェックでアドレスを受け入れる。ブラウザのパターンだけであり、訪問者とボタンのあいだに他には何も置きません。
  2. 未確認のままアカウントを作成する。フォームを保留にするのでも、「保留中」の画面を出すのでもありません。本人はすでに中に入っており、まだできないのは、そのアドレスが本当に必要となる、ひと握りのことだけです。
  3. 使い捨てのリンクかコードを載せたメッセージを1通送る。1通だけ、有効期限付きで、そのアカウントとそのアドレスだけに結び付けます。
  4. リンクそのものを証拠にする。クリック、あるいは打ち返されたコードが、検証のすべてです。このガイドの中の他のどれも、これほど確かな答えを出すものはありません。
  5. 戻る手段を用意する。目に見える「再送信」ボタンと、アカウントを失わずにアドレスを変更できる手段です — というのも、リンクが一度もクリックされない最も多い理由は、本人が今になってようやく気づけるタイプミスだからです。
  6. 誰も確認しなかったものは失効させる。一定期間が過ぎたあとの静かな一斉削除が、タイプミスや使い捨てが積み重なって、誰も信用しないリストになってしまうのを防ぎます。

そしてこれが、実際的な問題を生みます。それこそが、このサイトが存在する理由です。このループは今やプロダクトの中で最も重要な経路になっており、それをテストするということは、自分が管理するアドレスで本物のメールを受け取ることを意味します — それも、パイプラインの中で、人間が受信箱を開くことなく、何度も繰り返して。

公開ドメインの1つにあるアドレスなら、サインアップもキーも要らず、そこに届いたものは1秒後にはHTTP経由で読み取れます。

shell
$ curl -sG https://grabmail.io/api/v1/mailbox \
  --data-urlencode "address=signup-42@grabmail.io"

そこから、テストスイートによって検証フロー全体をエンドツーエンドで動かすことも、自分が所有するドメインをここに向けて、実行のたびに一度も存在したことのないアドレスを手に入れることもできます。メッセージは5日間保持されたのちに削除され、それはテストのフィクスチャとしては正しい寿命であり、メールボックスとしては間違った寿命です。

フォームに実際に組み込むべきこと

コードが実行される順に、要点をまとめます。

  1. トリムする。それだけにとどめる。両端の空白は貼り付けの副産物であり、意図されたものでは決してありません。文字列について、それ以外にあなたが変えてよいことは何もありません。
  2. 1つの緩いパターンにマッチさせる。ブラウザのもの、あるいはあなたのプラットフォームのものです。抜け落ちた@と打ち間違えたコンマは拒否し、それ以外は何も拒否しません。
  3. 長さの上限を254にする。列にもフィールドにも同じ1つの数字を入れれば、それだけで、ある種類の入力についてまるごと考えなくて済むようになります。
  4. クリティカルパスの外で、失敗時は通す形で、ドメインをルックアップする。MX、続いてA、キャッシュ付きで、本来なら成功していたはずの送信をブロックする理由には決してしません。
  5. 修正を提案する。自分で適用はしない。よくあるドメインから1文字違いというのは、尋ねるべき問いであって、勝手に行動してよい結論ではありません。
  6. メッセージを送る。それこそが検証です。それより上にあったことはすべて、トリアージにすぎません。
  7. 何がおかしいのかを、言葉で伝える。これこそが、誰かが最後までたどり着けるかどうかを決める部分です。
何が起きたかフォームが普段言うこと代わりに言うべきこと
文字列に@が入っていない「有効なメールアドレスを入力してください」「メールアドレスには@が必要です — name@example.comのようなものではありませんか?」
ドメインが解決しない「有効なメールアドレスを入力してください」「そのドメインが見つかりません。スペルは正しいですか?」
ドメインがよくあるものから1文字違い何も言わない。フォームはそのまま送信される「もしかして…?」、押せるボタンとして修正済みのアドレスとともに
使うことを選んだブロックリストに載っている「有効なメールアドレスを入力してください」「これには、来月になっても読めるアドレスが必要です。」
メッセージは送信されたが、一度も確認されなかった何も言わない。アカウントはそのまま放置される「そのアドレスにリンクを送りました。届いていませんか?再送するか、アドレスを変更してください。」

いくつの行が、同じ間違ったことを言っているかに注目してください。「有効なメールアドレスを入力してください」がどこでも既定の文言になっているのは、それがすべてのケースについて真実でありながら、そのどれにおいても役に立たないからです。5つの行のうち3つでは、アドレスは実際に有効であり、それを入力した本人には、自分がどの行に当てはまっているのかを知る手立てがありません。

要約

  1. 文字列をトリムする。それ以外は何も変えない。
  2. 1つの緩いパターン — ブラウザのものか、プラットフォームのもの — にマッチさせ、そこで止める。自分で書かない。
  3. 254文字を超えるものはすべて拒否し、そのサイズの列に保存する。
  4. ドメインのMXをルックアップし、Aにフォールバックし、キャッシュし、クリティカルパスの外で行い、ルックアップが失敗したときはアドレスを受け入れる。
  5. 解決しないドメインと、null MXを公開しているドメインは拒否する。DNSが返してくるそれ以外のものはすべて受け入れる。
  6. ドメインが近似ミスのときはスペルの提案をする。決して自分で適用しない。
  7. 使い捨てアドレスやロールアドレスをブロックするかどうかは、別に、そして文書として決めておく — その理由とともに。
  8. 使い捨てリンクを載せたメッセージを送り、クリックを検証として扱い、再送信とアドレスの修正を簡単にしておく。
  9. そのループを、失敗する経路も含めて本物の受信箱でテストし、未確認のアカウントはスケジュールに沿って削除する。

9行のうち、何かを確立するのは最後の2行だけです。残りの7行は、その先にあるメッセージを送る価値のあるものにするために存在しています。

質問

メールアドレスのための公式な正規表現はありますか?

ありません。存在しようがないのです。RFC 5322が与えているのはパターンではなく文法であり、それに忠実なパターンは、どのプロバイダも発行しない、引用符で囲まれたスペースや丸括弧内のコメントまで受け入れてしまいます。HTML仕様がメールフィールドのために定義しているものか、あなたのプラットフォームがすでに同梱しているチェッカーを使い、そこで浮いた労力は確認メッセージのほうに使いましょう。

何も送らずに、メールアドレスが存在するかどうかを確認できますか?

尋ねることはできますが、知ることはできません。キャッチオールドメインはすべての名前を受け入れ、グレーリスティングはわざとの一時的な失敗を返し、大手プロバイダはやり取りの最中に受理しておいてあとからバウンスさせ、あなたを認識していないサーバーは、受信者ではなくあなた自身に関する理由で拒否することがあります。大量のプローブは、あなたの送信アドレスをブロックリストに載せることにもなります。

name+tag@example.comは有効なアドレスですか?

有効です。プラス記号はローカル部で普通に許可されている文字であり、たいていの大手プロバイダでは、その手前にあるメールボックスへとそのままルーティングされます。これこそがプラスアドレッシングを役立つものにしています。それを拒否するフォームは、合法なアドレスを拒否しながら、そうすることで自分自身について何かを宣伝してしまっています。

メールアドレスは大文字と小文字を区別しますか?

ドメインは決して区別しません。@より前の部分は、標準上は受信側のサーバーに委ねられています — そして実際には、あらゆる大手プロバイダが大文字・小文字を無視します。重複を検出するために小文字化したコピーを保持しつつ、送信先には入力されたままの文字列を使いましょう。

メールアドレスはどのくらいの長さまで許されますか?

@より前は64文字、ドメインは255文字、そしてサーバーが運ぶときのアドレス全体は254文字です。この3つのうち使うべき数字は最後のものです。それをデータベースの列とフィールドの両方に入れておけば、ある種類の入力がまるごと、あなたの問題ではなくなります。

使い捨てメールアドレスはブロックすべきですか?

無料トライアルの裏に高価な何かがあるなら、ブロックすべきです — そして、使うリストはどれも不完全だということを受け入れてください。ニュースレターやダウンロード、有料アカウントの場合、あなたが追い返している相手は主に慎重な人たちであり、その姿をログの中に見ることは決してありません。あなたの代わりに判断してくれるパッケージを入れる前に、トレードオフの全体を読んでおく価値があります。

本物のメールボックスなしで、サインアップの検証フローをテストするには?

公開されている使い捨てドメイン上のアドレスに送り、APIでそれを読み返しましょう — サインアップもキーも不要で、実行のたびに新しいアドレスが手に入ります。数百個必要なテストスイートには、自分が所有するドメインをキャッチオールの受信箱に向け、テストごとにアドレスを1つ作り出しましょう。詳しい手順はメール検証フローをエンドツーエンドでテストするにあります。

続きを読む

新しいうちに試してみてください

アドレスの取得はワンクリックで、アカウントもカードも不要です。このガイドの内容はすべて、そのアドレスですぐに試せます。

おかえりなさい

受信箱とドメインを、ひとつの場所に。