メールアドレス検証が思った以上に難しい理由

メールアドレスの形式はRFC 5322およびその前身で定義されています。完全な仕様では、技術的に有効ながら驚くほど奇妙に見えるアドレスが許可されています:

  • "spaces in quotes"@example.com — ローカル部分のクォート文字列
  • user+tag@example.com — プラスアドレッシング(Gmailのフィルタリングで広く使用)
  • user@subdomain.example.co.uk — 複数のサブドメインとccTLD
  • user@[192.168.1.1] — ドメインとしてのIPアドレスリテラル
  • very.unusual."@".unusual.com@example.com — 技術的に有効だがほぼ見かけない
  • user@xn--nxasmq6b.com — 国際化ドメイン名(Punycode)

完全にRFC 5322準拠の正規表現はこれらすべてを処理しつつ、基本的なフォーマットルールに違反するアドレスを拒否する必要があります。結果として1000文字のパターンとなり、本質的にメンテナンス不可能です。実際には、目標は完全なRFC準拠ではなく、タイプミスをキャッチしつつ実在するアドレスをブロックしないことです。

シンプルな正規表現(99%のケースで十分)

ほとんどのアプリケーションでは、この最小限のパターンが正しい選択です。本質的な構造 — @の前に何か、ドメイン、TLD — を検証し、正当なアドレスに対する誤検知がほとんどありません:

/^[^\s@]+@[^\s@]+\.[^\s@]+$/

パターンの分解:

  • ^ — 文字列の先頭
  • [^\s@]+ — 空白と@以外の1文字以上(ローカル部分)
  • @ — リテラルのアットマーク
  • [^\s@]+ — 空白と@以外の1文字以上(ドメイン)
  • \. — リテラルのドット
  • [^\s@]+ — 1文字以上(TLD)
  • $ — 文字列の末尾

正しく受理・拒否するもの:

有効(受理):

✓ user@example.com
✓ first.last@company.co.uk
✓ user+tag@gmail.com
✓ 123@numbers.org

無効(拒否):

✗ plainstring
✗ @nodomain.com
✗ user @example.com(スペース)
✗ user@

主な弱点はuser@@example.coma@b.c(技術的に有効だが疑わしい)を受け入れることです。ほとんどのサインアップフォームやAPI入力では、これは許容可能なトレードオフです。

包括的な正規表現(RFC 5322準拠)

より厳密な検証が必要な場合 — 例えば、無効なアドレスがバウンスを引き起こし送信者レピュテーションを損なうメール送信システム — には、より徹底的なパターンが適切です。これは広く引用されるRFC 5322由来のパターンです:

/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

このパターンは長いですが、はるかに多くのケースを処理します。主な部分の分解:

  • ローカル部分(@の前):許可された文字のシーケンスとオプションのドット区切りセグメント[^<>()...]+(\.[^<>()...]+)*、またはクォート文字列".+""john doe"@example.comのようなアドレス用)。
  • ドメイン(@の後):ブラケット内のIPアドレスリテラル\[[0-9]{1,3}...\]、または2文字以上のTLDを持つ標準ホスト名([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}
  • TLD最小2文字:1文字のTLDを拒否しつつ、.museum.technologyのような長いTLDを受理。

このパターンが正しく処理する追加ケース:

✓ "quoted string"@example.com
✓ user@[192.168.1.1]
✓ user@subdomain.long-domain.co.uk

正しく拒否されるもの:

✗ user@example(TLDなし)
✗ user @example.com(@の前にスペース)
✗ user@@example.com(@@二重)

JavaScriptでのメールアドレス検証

以下は両方のパターンを使用した実用的なコピー&ペースト可能なJavaScript実装です:

シンプルな検証関数

/**
 * ほとんどのユースケースに対応するメール形式検証。
 * 高速で読みやすく、偽陰性がほとんどない。
 */
function isValidEmail(email) {
  const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return pattern.test(email.trim());
}

// Usage
isValidEmail("user@example.com");      // true
isValidEmail("not-an-email");          // false
isValidEmail("user+tag@gmail.com");    // true

厳密な検証(詳細なフィードバック付き)

function validateEmail(email) {
  const trimmed = email.trim();

  if (!trimmed) {
    return { valid: false, error: "Email is required" };
  }

  if (trimmed.length > 254) {
    return { valid: false, error: "Email address too long (max 254 characters)" };
  }

  if (!trimmed.includes("@")) {
    return { valid: false, error: "Missing @ symbol" };
  }

  const [local, ...domainParts] = trimmed.split("@");
  const domain = domainParts.join("@");

  if (!local || local.length > 64) {
    return { valid: false, error: "Invalid local part (before @)" };
  }

  if (!domain || !domain.includes(".")) {
    return { valid: false, error: "Invalid domain (missing TLD)" };
  }

  const rfcPattern = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  if (!rfcPattern.test(trimmed)) {
    return { valid: false, error: "Invalid email format" };
  }

  return { valid: true, email: trimmed.toLowerCase() };
}

// Usage
const result = validateEmail("  User@Example.COM  ");
// { valid: true, email: "user@example.com" }

HTML5メールアドレス検証

JavaScriptに手を出す前に、ブラウザが無料で提供してくれるものを検討してください。input type="email"要素にはフォーム送信時に実行される組み込みの検証があります:

<!-- 基本的な組み込み検証 -->
<input type="email" name="email" required>

<!-- カスタムパターンによるより厳密な検証 -->
<input
  type="email"
  name="email"
  required
  pattern="[^\s@]+@[^\s@]+\.[^\s@]+"
  title="有効なメールアドレスを入力してください"
>

ブラウザのtype="email"の組み込みアルゴリズムはHTML5仕様(RFC 5322の簡略化サブセット)に従います。明らかな非メールを拒否し、ネイティブのバリデーションツールチップを表示し、JavaScriptなしで動作します。pattern属性で追加の制約を重ねることができます。

注意点:組み込み検証はフォーム送信時にのみ発火し、ユーザーの入力中には発火しません。リアルタイムフィードバックにはJavaScriptが必要です。また、:invalid CSS疑似クラスで無効な入力をスタイリングできますが、:not(:placeholder-shown)も併用しないと未操作の空フィールドでもトリガーされます:

/* ユーザーが操作した後にのみ赤い枠線を表示 */
input[type="email"]:not(:placeholder-shown):invalid {
  border-color: #ef4444;
  outline-color: #ef4444;
}

input[type="email"]:not(:placeholder-shown):valid {
  border-color: #34d399;
}

よくあるエッジケース

これらはほとんどのメールアドレス検証実装でつまずくケースです:

プラスアドレッシング(サブアドレッシング)

user+tag@gmail.comは有効で広く使用されています。Gmail、Outlook、ほとんどのモダンなメールプロバイダーがフィルタリング用にサポートしています。正規表現はローカル部分の+文字を拒否してはいけません。制限が厳しすぎるパターンの多くはここで失敗します。

サブドメイン

first.last@mail.company.co.ukはローカル部分とドメインの両方にドットを含む有効なメールです。ドメインは3階層あります。良いパターンはドメイン内の複数のドット区切りラベルを処理できる必要があります。

長いTLD

TLDはもはや2〜3文字に制限されていません。.museum.photography.technologyなど数百のgTLDが有効です。TLDの最大長を4文字や6文字に制限する正規表現は偽陰性を生み出します。最小値(2文字)のみ強制し、最大値は設けないでください。

国際化ドメイン(IDN)

ドメイン名にはPunycodeとしてエンコードされた非ASCII文字を含めることができます。user@munchen.deは有効で、ASCII形式ではxn--mnchen-3ya.deです。ほとんどの正規表現パターンはPunycodeを直接検証できません。IDNサポートが必要な場合は専用のメールアドレス検証ライブラリを使用してください。

IPアドレスリテラル

user@[192.168.1.1]はRFC 5321上は技術的に有効ですが、実際にはほとんど使用されません。SMTP実装を構築しない限り、このケースは安全に無視できます。

過剰な検証をしない

この記事で最も重要な実用的アドバイスです:クライアントサイドのメールアドレス検証の目的はタイプミスをキャッチすることであり、配信可能性の検証ではありません。

メールアドレスが実際に存在するか、受信箱がメールを受け付けているかを正規表現で判断することはできません。それを確認できるのは実際にメールを配信することだけです。過剰な検証(正規表現が誤って無効とフラグを立てたアドレスの拒否)は実際のユーザーを失います。不十分な検証(形式の壊れたアドレスの受理)はバウンスのコストがかかります。

正しいアプローチは2層戦略です:

  1. シンプルな正規表現で明らかなフォーマットエラー(@の欠落、TLDの欠落、スペース)をキャッチ。
  2. 確認メールまたは検証メールを送信して、アドレスが実在しユーザーが管理していることを確認。

確認メールが唯一信頼できるゲートです。他はすべてヒューリスティクスです。user@localhosta@b.ioを拒否すべきかどうか悩んでいるなら、立ち止まってください — メールを送信してSMTPレイヤーに処理させましょう。

一般的なメール正規表現パターンの比較

最もよく使用されるパターンのトレードオフを含む実用的な比較です:

パターン タイプ 利点 欠点 カバー率
/^[^\s@]+@[^\s@]+\.[^\s@]+$/ シンプル 読みやすく、偽陰性が最小、高速 a@@b.cを受理、非常に緩い ~95%
/^[\w.+-]+@[\w-]+\.[\w.]{2,}$/ 一般的 短く、大半の実用メールを処理 ローカル部分の有効な特殊文字を拒否 ~92%
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ バランス型 広く使用され、厳密さとカバー率のバランスが良い 有効なクォート文字列ローカルを一部拒否 ~97%
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}...)/ RFC 5322 クォート文字列、IPリテラル、長いTLDに対応 長く読みにくい、それでも100% RFC準拠ではない ~99%

「バランス型」パターンの全体

上記テーブルの3番目のパターンは、シンプルパターン以上だがRFC 5322の完全な複雑さは不要な場合に最も実用的な選択肢です:

/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/

パターンの分解:

  • [a-zA-Z0-9._%+-]+ — ローカル部分:英数字と._%+-
  • @ — 必須のアットマーク
  • [a-zA-Z0-9.-]+ — ドメイン:英数字、ハイフン、ドット(サブドメイン対応)
  • \. — TLDの前のドット区切り
  • [a-zA-Z]{2,} — TLD:最低2文字、最大なし(長いTLDに対応)

自前で書く代わりにライブラリを使用

メールアドレスの有効性が重要なアプリケーション(トランザクションメールシステム、B2B SaaSサインアップフロー)では、自前で正規表現を書くよりも専用の検証ライブラリの使用を検討してください:

// Node.js — validator.js (most popular)
import validator from 'validator';
validator.isEmail('user@example.com'); // true

// Node.js — email-validator (lightweight)
import { validate } from 'email-validator';
validate('user@example.com'); // true

// Python — email-validator (RFC-compliant)
# pip install email-validator
from email_validator import validate_email, EmailNotValidError
try:
    info = validate_email("user@example.com")
except EmailNotValidError as e:
    print(str(e))

メール正規表現をテストする

正規表現を理解する最良の方法は、包括的なテスト入力セット — 受理を期待する有効なアドレスと拒否すべき無効なアドレスの両方 — に対して実行することです。当社のRegexテスターを使えば、上記のパターンをペーストし、テストスイートを構築し、マッチ結果をリアルタイムでハイライト表示できます。

メール正規表現をライブテスト

メール正規表現パターンをペーストして、独自の入力に対してテスト。フラグ、キャプチャグループ、複数行マッチングに対応。ブラウザ内で完全に動作。

Regexテスターを開く →

関連開発者ツール