違反しているメールアドレスってなんだ？！

こんにちは、そろそろ家の更新時期が迫ってきて引越し先の物件に悩んでいるしっぽくんです。最近業務でメールアドレスについて触れる機会があったので今回はそのお話をしようかと思います。
私自身あまり馴染みのなかった「RFC違反メールアドレス」について深堀っていく記事になりますので、ご興味あればご一読いただけると幸いです。

# この記事で触れること
- RFC違反メールアドレスとはどういうことなのか
- HTML標準やバリデーションライブラリにおけるメールアドレスについて

# 違反しているメールアドレス??

さてこの中に仲間外れがあります。どれでしょう?

1. 「test.test.@example.com」
1. 「test.test@example.com」
1. 「test..test@example.com」

正解は2です。これ以外は違反しているメールアドレスです!具体的にどんな違反メールアドレスなのでしょうか?

前置きを踏まえて、私達の日頃の業務の中でお客様からの問い合わせで回答の難しいものが開発チームに回ってくることがあります。弊社のシステムの中でメール送信を扱うものがあり、今回の問い合わせはそれでした。

一口にメールが届かないと言っても原因は様々ありますが、今回の問い合わせはどうやら「RFC違反メールアドレス」というものでした。（ここでは原因の特定には触れません）

> 「:thinking: はて、RFC違反ってなんだ??」

あいにくとメール送信に関する知識は遠い昔の学生時代に触れた程度で、「RFC」という存在も恥ずかしながら知りません。

**知らないなら知る機会！** 

一個ずつ調べてみることにしました。

# RFCとは？

全くわからない概念なのでChatGPTさんに聞いてみることに。
> 「RFCって何?要約して教えて下さい」
> > RFCとは、"Request for Comments"の略で、インターネット技術に関する文書の標準化プロセスを定義する文書のことです。これは、インターネットのプロトコルや手順に関する提案や仕様を共有し、議論するためのものです。RFCは、インターネット技術の進化と標準化を促進するために広く使用されています。

なるほど、プロトコルを定義しているもの。今回だとメールアドレスのプロトコルが記載されているRFCがあると推察。

>「メールアドレスに関するRFCはありますか?」
>> はい、メールアドレスに関するRFCはいくつか存在します。その中でも代表的なものはRFC5321（Simple Mail Transfer Protocol）とRFC 5322（Internet Message Format）です。これらのRFCは、電子メールの送信や受信に関するプロトコルやフォーマットについて定義しています。

お目当てのものが見つかりました。さてここからはどんな内容が違反になっているのかを調べてみましょう。

余談ですが、日頃触れているフレームワークなどでも採用されています。言われてみると確かに見てましたね…。

https://github.com/reactjs/rfcs

https://github.com/vuejs/rfcs

# RFC5322を追え🏃‍♂

[RFC5321](https://datatracker.ietf.org/doc/html/rfc5321
)はメール送信に関するプロトコルについて書かれているので、フォーマットに関して書かれている[RFC5322](https://datatracker.ietf.org/doc/html/rfc5322)の特に[section-3#Syntax](https://datatracker.ietf.org/doc/html/rfc5322#section-3)を見て正しいメールアドレスのフォーマットを調べてみます。

:::note warn
以降の要約などは自分の解釈に基づいたもののため、一部間違っている可能性があります。
もし間違っている場合は大変勉強になるためご指摘いただけると幸いです。
:::

さて何もわからないので、順番に上から見ていきます。

## 前提（RFC5234）

> The following primitive tokens that are used but otherwise unspecified are taken from the "Core Rules" of [RFC5234], Appendix B.1: CR, LF, CRLF, HTAB, SP, WSP, DQUOTE, DIGIT, ALPHA, and VCHAR.

どうやら[RFC5234](https://datatracker.ietf.org/doc/html/rfc5234)なるものがでてきました。どうやら独自定義があるようです。しかし見てみると意外とこれまで見聞きしたものがいくつかでてきていますね。

[RFC5234#appendix-B.1](https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1)から引用。
また述べられているものだけピックアップしています。

```
         ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z

         CR             =  %x0D
                                ; carriage return

         CRLF           =  CR LF
                                ; Internet standard newline

         DIGIT          =  %x30-39
                                ; 0-9

         DQUOTE         =  %x22
                                ; " (Double Quote)

         HTAB           =  %x09
                                ; horizontal tab

         LF             =  %x0A
                                ; linefeed

         SP             =  %x20

         VCHAR          =  %x21-7E
                                ; visible (printing) characters

         WSP            =  SP / HTAB
                                ; white space
```



> In some of the definitions, there will be non-terminals whose names start with "obs-".  These "obs-" elements refer to tokens defined in the obsolete syntax in section 4.

廃止された定義もある。

## Atom
[section 3.2.3](https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.3)では基本となる構成について書かれていました。

> Several productions in structured header field bodies are simply strings of certain basic characters.  Such productions are called atoms.

`atoms` と呼ばれる基本構成があるとのこと。先程述べたRFC5234のプリミティブトークンと合わせて使うのでしょうか？

> Some of the structured header field bodies also allow the period character (".", ASCII value 46) within runs of atext.  An additional "dot-atom" token is defined for those purposes.

`atext` と呼ばれるものの中に `.` を許可するものもあり、それを `dot-atom` と呼ばれるそうです。いよいよわからない単語が増えてきましたね。

ここまででてきたワードを[整理している表](https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.3)があったので引用します。

```
atext           =   ALPHA / DIGIT /    ; Printable US-ASCII
                       "!" / "#" /        ;  characters not including
                       "$" / "%" /        ;  specials.  Used for atoms.
                       "&" / "'" /
                       "*" / "+" /
                       "-" / "/" /
                       "=" / "?" /
                       "^" / "_" /
                       "`" / "{" /
                       "|" / "}" /
                       "~"

   atom            =   [CFWS] 1*atext [CFWS]

   dot-atom-text   =   1*atext *("." 1*atext)

   dot-atom        =   [CFWS] dot-atom-text [CFWS]

   specials        =   "(" / ")" /        ; Special characters that do
                       "<" / ">" /        ;  not appear in atext
                       "[" / "]" /
                       ":" / ";" /
                       "@" / "\" /
                       "," / "." /
                       DQUOTE
```

上記の表を見ると、RFC5234で出てきたプリミティブトークンと特定の文字を組み合わせて構成されているみたいですね。

`atext`　はアルファベット（A-Z, a-z）と数値（0-9）、```!#$%&'*+-/=?^_`{|}~```を含むものと定義されています。

`dot-atom-text`（≒ `dot-atom`）は １文字以上の`atext`を含み、0個以上の`.`と`atext`で連なったものと定義されています。

## Quoted characters & Quoted Strings

[section 3.2.1](https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.1)と[section 3.2.4](https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.4)では少し似たような内容に触れていたので合わせて紹介します。

```
   quoted-pair     =   ("\" (VCHAR / WSP)) / obs-qp
```

3.2.1の`quoted-pair`では `\`と `VCHAR` もしくはスペースで構成されており、エスケープを意味しています。

```
   qtext           =   %d33 /             ; Printable US-ASCII
                       %d35-91 /          ;  characters not including
                       %d93-126 /         ;  "\" or the quote character
                       obs-qtext

   qcontent        =   qtext / quoted-pair

   quoted-string   =   [CFWS]
                       DQUOTE *([FWS] qcontent) [FWS] DQUOTE
                       [CFWS]

```

`qtext` は `VCHAR`から `\` と `"` を除いたもので構成され、`qcontent` は `qtext` もしくは `quoted-pair` によって構成されています。
さらに `quoted-string` は `"` で囲まれた０個以上の `qcontent` になっています。


## Addr-Spec Specification

[section 3.4](https://datatracker.ietf.org/doc/html/rfc5322#section-3.4)の`addr-spec`、ついにメールアドレスのフォーマット定義にたどり着きました、長かったです。

```
   addr-spec       =   local-part "@" domain
```

`addr-spec` は `@` で仕切られた`local-part`と`domain`に分かれています。

```
   local-part      =   dot-atom / quoted-string / obs-local-part

   domain          =   dot-atom / domain-literal / obs-domain
```

更に `local-part` は `dot-atom` または `quoted-string` によって構成されています。
今回の主題である「違反しているメールアドレス」はこの`local-part`の定義を紐解けば理解できそうです、燃えてきました。

## まとめ

これまでの調べでメールアドレスは `local-part` @ `domain` に分かれていることがわかりました。
更に `local-part` は `dot-atom` もしくは `quoted-string` によって成り立っています。
`dot-atom` は １文字以上の ```atext(アルファベット（A-Z, a-z）と数値（0-9）、!#$%&'*+-/=?^_`{|}~)``` を含み、0個以上の.とatextで連なったものと定義されています。

つまり、 `.` 開始ではなく、`.` が２つ以上連続していない、 `atext` に定義されている文字で構成されたものであれば正しいメールアドレスと言えそうです。

ただし `quoted-string` はこれに該当せず、 `"`で囲まれた `VCHAR(ただし\と"は除く)` とエスケープによって構成されているので、基本的には `"` で囲めばだいたい処理されそうですね。

最後に簡易的なテーブルでまとめてみました。

| 要素 | 説明 | 例 |
| --- | --- | --- |
| `local-part` | `@` の前の部分 <br/> `dot-atom` もしくは `quoted-string` で構成 | `test.test` |
| `domain` | `@` の後の部分 <br /> `dot-atom` もしくは `domain-literal` で構成 | `example.com` |
| `dot-atom` | 1文字以上の `atext` を含み、0個以上の`.`と`atext`で連なったもの | `test.test` |
| `quoted-string` | `"` で囲まれた文字列 | `"test.test"` |
| `atext` | アルファベット（A-Z, a-z）、数値（0-9）、<br /> ```!#$%&'*+-/=?^_`{\|}~```を含む文字 | `test` |



# 余談：メールサービスの対応

メールアドレスといえば各キャリアが提供する「キャリアメール」もありますね。
各キャリアでは「RFC違反メールアドレス」をどう扱っているのでしょうか?

https://www.docomo.ne.jp/service/docomo_mail/rfc_add/
> RFC違反アドレスに関しては現在ご利用が推奨されていないため、今後メールサービスでの障害が発生する場合があります。

https://www.au.com/support/faq/detail/20/a00000000820/
> auメール（@au.com／@ezweb.ne.jp）を送信できないことがあります。

https://www.softbank.jp/support/faq/view/57268
> メールアプリによってはサポートされていません。

どこも非推奨と書かれています。
しかしながら利用ユーザーは最初に作ったメールアドレスを変更する手間もあるため、非推奨と言えど利用されている現状です。

# 更に余談：開発視点から見たメールアドレス

メールアドレスと言えばブラウザ上で入力する機会も多いですね。私達が開発しているシステムもそうです。
ではHTMLの仕様ではどう定義されているのでしょうか？

https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address

```
email         = 1*( atext / "." ) "@" label *( "." label )
label         = let-dig [ [ ldh-str ] let-dig ]  ; limited to a length of 63 characters by RFC 1034 section 3.5
atext         = < as defined in RFC 5322 section 3.2.3 >
let-dig       = < as defined in RFC 1034 section 3.5 >
ldh-str       = < as defined in RFC 1034 section 3.5 >
```

見慣れた文字が存在しますね・・・。
丁寧なことに正規表現も記載されています。
```
/^[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])?)*$/
```

しかし先程まで見ていた定義と少し異なり、 `local-part` が 1個以上の `atext` もしくは `.` となっています。
なぜ異なっているのかの理由までは調べきれなかったのですが、今回の問い合わせ同様既に作られているメールアドレスを入力できない状態を避けるためだと思われます。

# もういっちょ余談: バリデーションライブラリ

ついでバリデーションライブラリについても調べてみました。
- Yup
    - https://github.com/jquense/yup/blob/5c77e0d4f9373151bcf0cd558c95986b6e4800d7/README.md#L1338-L1359
    - https://github.com/jquense/yup/blob/5c77e0d4f9373151bcf0cd558c95986b6e4800d7/src/string.ts#L19-L22
        - 見たところHTML標準に近い内容でした
- Zod
    - https://github.com/colinhacks/zod/blob/03f2f080c4bb0ccd4434748a6e930ebdd1bd75a6/src/__tests__/string.test.ts#L110-L114
    - RFC違反メールアドレスは弾いていますね
- Go言語のnet/mail（バリデーションライブラリではないですが）
    - https://cs.opensource.google/go/go/+/master:src/net/mail/message_test.go;l=1183-1202
    - エラーケースとして書かれていますね

# 最後に

メールアドレス1つとっても規格は様々で一概にこれでOKということはなさそうですが、送れないケースの1つとして認識しておいたほうがよさそうです。
また非推奨になっているもののまだ利用されているユーザーはいるため、対応の可否が求められそうです。

# PR

株式会社HRBrainでは、一緒に働く仲間を募集しています！

https://www.hrbrain.co.jp/recruit