# 一篇讀懂 email ## 目錄 - [TL;DR](#TLDR) - [EMail 送出流程,以 SMTP 為例](#EMail-送出流程,以-SMTP-為例) - [Sender IP](#Sender-IP) - [Email 格式解讀](#Email-格式解讀) - [EML header](#eml-header) - [MIME boundary](#MIME-boundary) - [建議](#建議) - [信件驗證機制](#信件驗證機制) - [SPF](#SPF) - [DKIM](#DKIM) - [DMARC](#DMARC) - [ESMTP](#ESMTP) - [小節](#小節) ## TL;DR * 持續保持符合信件規範 * 同時提供 text/html & text/plaintext 格式的信件 * [參閱此章節](#建議) * 統一控管郵件發送量/頻率,避免短時間內大量的郵件觸發防護機制 * 對於高頻率/低頻率的不同使用者,應有不同的寄件策略 * 按照使用者習慣的語言寄出對應信件 * 非使用者常用語言的信件有高機率被分到垃圾信 * 我在垃圾桶撈到了南非荷蘭文的 Tiktok 認證信,我很疑惑 * 透過驗證碼及各類優惠等互動信件引導使用者幫 tiktok train 屬於他們自己信箱的 filter,透過分類/點擊/回覆 等行為,提高 tiktok 信件的優先權 * [參考這裡](https://support.google.com/mail/answer/186543?hl=zh-Hant) * 持續追蹤信件送達率,主要分類率,不重複點擊率,重複點擊率,首次閱讀時間差等數據,持續改進送信方案。 * 送達率:成功寄出信件的機率 * 主要分類率:送達且分類在主要信件的機率 * 不重複點擊率:用戶點開同一信件至少一次的機率 * 重複點擊率:用戶點開同一信件不只一次的機率 * 首次閱讀時間差:信件抵達及用戶第一次點開的時間間隔 ## EMail 送出流程,以 SMTP 為例 SMTP 的全名是:Simple Mail Transfer Protocol SMTP 是 string stream 組成的協議,因此即便用 telnet 也可以送出信件。 以最常見的 SMTP 舉例,假設今天有以下情景 rance_jen@outlook.com 想要寄信給 daniel_Yu@outlook.com,同時副本給 xu_hsin_liu@outlook.com。 S 指 Server, C 指 Client [參考這裏](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_transport_example) ``` C: HELO trendmicro.com S: 250 smtp.example.com, I am glad to meet you C: MAIL FROM:<rance_jen@trendmicro.com> S: 250 Ok C: RCPT TO:<daniel_Yu@outlook.com> S: 250 Ok C: RCPT TO:<xu_hsin_liu@outlook.com> S: 250 Ok C: DATA S: 354 End data with <CR><LF>.<CR><LF> C: {EML_DATA} C: . S: 250 Ok: queued as 12345 C: QUIT S: 221 Bye {The server closes the connection} ``` > 這裡的一切資訊都是不可信的,SMTP 沒有驗證機制,因此 Client 可以聲稱自己來自任何 Domain。 以下一一解讀各個 command,以及可以檢驗的機制 [參考這裏](https://hweily.pixnet.net/blog/post/22585552) > Following commands are not case sensitive * HELO {HELO_DOMAIN} Client 連線後第一個指令,用以告訴對方自己的 Domain。 * MAIL FROM:<{SMPT_FROM}> 用來告訴對方這封 mail 是來自什麼 Domain > SMPT_FROM 不是信件上面顯示的 From Address,這個資訊通常只有 Mail Server 知道。 * RCPT TO:<{SMTP_TO}> 用來告訴對方這封 Mail 要寄給哪個 address > `SMPT_FROM` 不是信件上面顯示的 to Address,這個是伺服器真正用來決定要把信寄到哪個 address 的欄位。 `SMTP_TO` 和 eml 內的收件人(To)是可以不一樣的,所以即便收到一個收件人看起來不是自己的信也是正常的。 * DATA 代表接下來開始傳輸信件內容,就是以下的 EML 格式 * QUIT 結束傳輸。 ### Sender IP 由於 SMTP 是基於 TCP 的協定,Mail Server 在收到 TCP 連線時會取得 Client 的 IP,此資訊通常稱 `Sender IP`,由於是在 SMTP 通訊中少數較難偽造的資訊,因此常用於驗證信件合法性,下面會再提到。 ## Email 格式解讀 詳細 spec 可以[參考這裡](https://docs.fileformat.com/email/eml/) 以下是一個 eml ```eml User-Agent: Microsoft-MacOutlook/10.10.2.180910 Date: Fri, 8 Jan 2021 10:48:18 +0800 Subject: Test Subject From: "Rance Jen (Joker-TW)" <rance_jen@trendmicro.com> To: "Daniel Yu (RoleModel-TW)" <daniel_Yu@outlook.com> CC: "Xu Hsin Liu (Monster-TW)" <xu_hsin_liu@outlook.com> Message-ID: <E84DE573-6B9F-4C1C-95BD-E6CD40633C14@outlook.com> Thread-Topic: Test Subject MIME-Version: 1.0 Content-type: multipart/alternative; boundary="B_3692947709_2010432344" --B_3692947709_2010432344 Content-type: text/plain; charset="UTF-8" Content-transfer-encoding: 7bit Test body --B_3692947709_2010432344 Content-type: text/html; charset="UTF-8" Content-transfer-encoding: quoted-printable <html xmlns:o=3D"urn:schemas-microsoft-com:office:office" xmlns:w=3D"urn:schema= s-microsoft-com:office:word" xmlns:m=3D"http://schemas.microsoft.com/office/20= 04/12/omml" xmlns=3D"http://www.w3.org/TR/REC-html40"> <head> <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8"> <meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)"> <style><!-- /* Font Definitions */ @font-face {font-family:=E6=96=B0=E7=B4=B0=E6=98=8E=E9=AB=94; panose-1:2 2 5 0 0 0 0 0 0 0;} @font-face {font-family:"Cambria Math"; panose-1:2 4 5 3 5 4 6 3 2 4;} @font-face {font-family:Calibri; panose-1:2 15 5 2 2 2 4 3 2 4;} @font-face {font-family:"\@=E6=96=B0=E7=B4=B0=E6=98=8E=E9=AB=94"; panose-1:2 1 6 1 0 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {margin:0cm; margin-bottom:.0001pt; font-size:12.0pt; font-family:"Calibri",sans-serif;} a:link, span.MsoHyperlink {mso-style-priority:99; color:#0563C1; text-decoration:underline;} a:visited, span.MsoHyperlinkFollowed {mso-style-priority:99; color:#954F72; text-decoration:underline;} span.EmailStyle17 {mso-style-type:personal-compose; font-family:"Calibri",sans-serif; color:windowtext;} .MsoChpDefault {mso-style-type:export-only; font-family:"Calibri",sans-serif;} @page WordSection1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.WordSection1 {page:WordSection1;} --></style> </head> <body lang=3D"ZH-TW" link=3D"#0563C1" vlink=3D"#954F72"> <div class=3D"WordSection1"> <p class=3D"MsoNormal"><span lang=3D"EN-US">Test body<o:p></o:p></span></p> </div> </body> </html> --B_3692947709_2010432344-- ``` 我們可以從上往下一點一點來解讀這封 Mail ### eml header ```eml User-Agent: Microsoft-MacOutlook/10.10.2.180910 Date: Fri, 8 Jan 2021 10:48:18 +0800 Subject: Test Subject From: "Rance Jen (Joker-TW)" <rance_jen@outlook.com> To: "Daniel Yu (RoleModel-TW)" <daniel_Yu@outlook.com> CC: "Xu Hsin Liu (Monster-TW)" <xu_hsin_liu@outlook.com> Message-ID: <E84DE573-6B9F-4C1C-95BD-E6CD40633C14@outlook.com> Thread-Topic: Test Subject MIME-Version: 1.0 Content-type: multipart/alternative; boundary="B_3692947709_2010432344" ``` 這邊屬於 eml header,郵箱顯示的 `寄件人/收件人/副本/主旨` 一般都是由這裡產生的。 雖然這裡的收件人可以亂填,但大多數郵件系統都會比對 `SMTP_FROM` 和 EML 內的 `From` 是否來自相同 Domain,以及 Domain 對應的 SPF 和 `Sender IP`是否符合。 > SPF 是一種特定格式的 DNS Record,詳細請見下方章節。 以下只列出比較常顯示的 Header * Date: 寄件時間 * Subject: 標題 * From: 寄件人 * To: 收件人 * CC: 副本 * Thread-Topic: 所屬信件群組 * MIME-Version: 信件格式版本 [MIME 的 RFC](https://tools.ietf.org/html/rfc1341) * Content-type: 內文格式 * boundary: 內文區塊 > 再強調一次,這裡的資訊都是「顯示用」的,郵件信箱實際要把信件寄給誰是按照 `SMTP_TO` 是判斷的。 > 這也是為何按下回覆信件時,我們甚至可以去修改下面顯示的過往信件內容/時間/寄件者,就是因為這些顯示的真的都只是純文字而已。 ### MIME boundary MIME 將信件內文分割成多個 boundary,並且一個 boundary 可以有多個表示形式,需連在一起表示並用 `--{BOUNDARY_NAME}--` 作為結尾 ```eml --B_3692947709_2010432344 Content-type: text/plain; charset="UTF-8" Content-transfer-encoding: 7bit Test body ``` 上敘是 `B_3692947709_2010432344` 的 `text/plain` 類型表示方式。 ``` --B_3692947709_2010432344 Content-type: text/html; charset="UTF-8" Content-transfer-encoding: quoted-printable 以下過多省略 ... ... ... --B_3692947709_2010432344-- ``` 上敘是 `B_3692947709_2010432344` 的 `text/html` 類型表示方式。 由於現在的信箱大多支援 html email,因此寄件者通常會提供一種以上的顯示方式讓郵件顯示器來選擇。 ### 建議 通常提供了 `text/plain` 的正規郵件(指非詐騙/釣魚/垃圾等信件) 被封鎖的機率較低,因為針對 `text/plain` 的掃描遠比 `text/html` 容易也更成熟,並且也可以透過比對 `text/html`。 * Reference:[MailChimp-Why Bother With Plain-Text Emails?](https://web.archive.org/web/20121130234935/http://kb.mailchimp.com/article/why-bother-with-plain-text-emails) 以 Tiktok 的驗證碼信件來說 ```eml From: TikTok <noreply@account.tiktok.com> Message-ID: <2d8e30ff-44f9-470e-8417-8c18662ecb22.noreply@account.tiktok.com> Subject: Verifieer jou e-posadres met TikTok Reply-To: noreply@account.tiktok.com To: bodommoon@gmail.com X-Entity-ID: /iZ+76rxFgdu7mfOm3BSYQ== Content-Type: multipart/alternative; boundary="GoBoundary" --GoBoundary Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: 8bit ``` Nope, 沒有 `text/plain` 的部分,根據目前遇到的問題不同,建議可以在這方面再多做研究及測試。 > 根據目的為「避免被分類為垃圾信件」或「希望被分到主要信件」,這兩種需求的不同,參考方向也會不同。 不過以個人信箱,以下幾種創作平台的通知信件為例子。 * ci-en * patreon * fanita * pixivFanbox 同樣是訊息的創作推送,同樣是非中文,僅有包含 `ontent-Type: text/plain; charset=UTF-8` 的 pixivFanbox 會自動被分類到「主要信件」,其他被分到了次要顯示的「最新快訊」去,以個人經驗來說確實有測試的價值。 ## 信件驗證機制 以下三種檢驗機制都是基於 DNS 來實作的。 ### SPF [參考這裡](https://support.google.com/a/answer/33786?hl=zh-Hant) 驗證發送方的可靠性 當 Google.com(Receiver) 收到宣稱來自 outlook.com(Sender) 的信件時,Receiver 會做去查詢對方的 txt record ``` dig outlook.com txt outlook.com. 299 IN TXT "v=spf1 include:spf-a.outlook.com include:spf-b.outlook.com ip4:157.55.9.128/25 include:spf.protection.outlook.com include:spf-a.hotmail.com include:_spf-ssg-b.microsoft.com include:_spf-ssg-c.microsoft.com ~all" ``` 其中這段 `ip4:157.55.9.128/25` 直接說明了 outlook.com 的合法 Sender IP,也可以透過 `include:spf-a.hotmail.com` 這種字串來指向其他 domain 的 txt record example: 當我今天想打開 Tiktok 收到的驗證信 ![](https://i.imgur.com/CYImQMD.jpg) 為什麼通過 SPF 呢? 因為 ``` dig account.tiktok.com txt "v=spf1 include:mailgun.org include:spf1.dm.aliyun.com include:spf.onlarksuite.com include:_netblocks.m.feishu.cn ~all" ``` 然後取 `mailgun.org` 的 spf 來看 ``` dig mailgun.org txt "v=spf1 include:spf1.mailgun.org include:spf2.mailgun.org -all" ``` 再取對應的 `spf2.mailgun.org` 的 spf 來看 ``` dig spf2.mailgun.org txt "v=spf1 ... ... ip4:69.72.32.0/20 ~all" ``` 而 69.72.44.186 確實在 69.72.32.0/20 的 cidr range 之內,故 spf 認證通過。 ### DKIM [參考這裡](https://support.google.com/a/answer/174124?hl=zh-Hant) 驗證內文的可靠性,完整性 DKIM 的格式參考 [RFC 4871](https://www.ietf.org/rfc/rfc4871.txt) 具體可從 eml header 直接組出來,具體如下 ``` DKIM-Signature: a=rsa-sha256; v=1; c=relaxed/relaxed; d=register.account.tiktok.com; q=dns/txt; s=krs; t=1610072789; h=Mime-Version: Content-Type: Subject: From: To: Reply-To: Message-Id: Date: Content-Transfer-Encoding: X-Feedback-Id; bh=XxxII4AYM0IUdadfjM2cN5gYFMs9HOGPdJ1KPieNa14=; b=a2Sxf2Fu/ySkkSoVF0SVMno1KbAQJbTmyU+1EE2UmdzzeGfIzmI4xjN2wRl97+vd8m4DIr0A TxVaGvcEad0oeIAA5GqytInDkXtkzPQI4NqZ/s3MFtiA/32/MDXXBYicdzfkwyY6ITKMYRDV QJck7KEo+y34aQ4kuz2Vj2FbBwk= ``` 對應查詢指令為 `dig {s}._domainkey.{d} txt` 也就是 `dig krs._domainkey.register.account.tiktok.com txt` 就會拿到對應的 public key `"k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwLL5RYgbmUEqP7mz40wLuYuiNZfuFqrEJIwNr7LpcszmY2+zF1Pa8XBgDyoVKoAMC+IcKmRUR6pso4MhR3fuwJmXCZXHMezXi0WyRdmPsGAqG++1z3Y6hxafm0LYIJaaXRQ0iZQEdZkKwn5OWmTQGpSIEi6TF4rW6H37EUar1twIDAQAB"` 並用此 public key 去驗證 `b=` 和 `bh=` 欄位的簽名即可。 ### DMARC [參考這裡](https://support.google.com/a/answer/2466563?hl=zh-Hant) 針對上敘 SPF/DKIM 驗證失敗信件的處理方式。 格式一樣參考 [RFC 7489](https://tools.ietf.org/html/rfc7489) 一樣用案例出發 ``` dig _dmarc.tiktok.com txt "v=DMARC1; p=quarantine; pct=100; rua=mailto:mailauth-reports@bytedance.com" ``` 就代表了上面的 SPF 跟 DKIM 若是沒通過建議發到垃圾信箱去,處理的比例為 100%,並且同時把信件資料回報到 `mailauth-reports@bytedance.com` ## ESMTP 全名是:Extended SMTP SMTP 的擴充協議,增加了以下幾種指令。 * EHLO * ESMTP 的 HELO,用來詢問對方是否支援 ESMTP * 後續還有 AUTH/HELP 等不同伺服器支援的不同操作 * STARTTLS * 開始進行 TLS 交握,SMTP 加密的第一步,加密後方可執行 SMTP AUTH 等指令。 `STARTTLS` 尤其重要,如果沒有在連線後首先進行 TLS 交握,後續信件內容及認證內容都會以明文傳遞的。 ## email tracking SMTP ![](https://i.imgur.com/DzbhgT8.png) ![](https://i.imgur.com/N19DQyu.png) 現在常見的手法,藉由是否拉下特定圖片為 tracking 的標的 以 tiktok 驗證信為例,在整個 html mail 的最後面有 ``` </html><img src=3D"https://tiktok.email-messaging.com/tracking/1/open/F1GLt= 796"> ``` 這目的就明顯啦,或是像 shopback 也有 ``` <tr> <td height=3D"1" style=3D"font-size: 1px; line-height: 1px; padding: 0px;"> <br><img src=3D"https://email.shopback.com/pub/as?_ri_=3DX0Gzc2X%3DAQpglLjH= JlTQGsvBzd8KzfN2kGTMJBzcRzg6kygKzdJotqzg92YLFvC4dzbBhKe1KYqKzgOVXHkMX%3Dw&_= ei_=3DEolaGGF4SNMvxFF7KucKuWPDjrbedtEC1CFTWQ01D8khfLu9ztEv0-B7m2NRhKcM-v7Af= TLpHLoRe4D6F5Nh8t9YlTe4VcLEgkWV6tzR-DWNYaCNqtylSR9l2kiSthZTL2c8MELRRYw0."><= /img> </td> </tr> ``` 特別顯示的超小,這意圖我覺得就很明顯 la ## 小節 見 [TL;DR](#TLDR)