昨天,搜輯 Biashelf 有了自己的網域 biashelf.com。網站找得到了,但還有一件事沒處理:信。
一個網站會寄出、收到的信,比想像中多:登入驗證碼、通知信、使用者來信詢問,之後也可能有活動消息。這些信如果寄件人不是自己的網域,問題就來了:
support@biashelf.com 這樣的地址,而不是請大家寄到我的個人信箱。Supabase 內建的寄信服務是給開發測試用的:只會寄給事先加入專案團隊的信箱,整個專案每小時只能寄 2 封。這不是 Supabase 故意刁難,而是它的寄信服務一直被拿去寄垃圾信、釣魚信,所以限制越來越多,2026 年6起,新建的免費專案甚至不能修改信件範本。想正式寄信,就該用自己的寄信服務。
所以今天要讓 Biashelf 用自己的網域收信、寄信。但寄件人寫上自己的網域,不代表信就收得到。信寄出去就像漂流瓶,要不要撿起來、放進收件匣,是收件端決定的。
先從平常的寄信講起。假設我用 Gmail,寄一封信給朋友的 Outlook 信箱:

【圖 1|平常寄一封信的流程】
這裡出現了幾個名詞:
| 名詞 | 全名 | 作用 | 在上面的例子裡 |
|---|---|---|---|
| SMTP | Simple Mail Transfer Protocol | 把信送出去,在伺服器之間轉交 | Gmail 把信送到 Outlook |
| MX 紀錄 | Mail Exchanger | 寄到某個網域的信,要送到哪台伺服器 | 查到 @outlook.com 的信該送去哪裡 |
| IMAP | Internet Message Access Protocol | 讀信,信留在伺服器上,手機和電腦看到的一樣 | 朋友用 App 打開信箱 |
注意倒數第二步:信能不能進收件匣,是收信的那一端決定的。 寄信的人能做的,是讓對方有足夠的理由相信這封信。
網站寄信也是同一條路,只是按下寄出的不是人,而是程式。使用者輸入信箱、按下登入,網站就請寄信服務寄出一封登入信,最後由使用者的信箱服務(例如 Gmail)決定收不收。
一講到「用自己的網域寄信」,很容易想成開一個信箱就好。但網站其實有兩種不同的需求:
| 需求 | 例子 | 常見服務 |
|---|---|---|
| 收信、親自回信 | 使用者寄信到 support@biashelf.com,人親自回覆 |
Zoho Mail、Google Workspace、Microsoft 365;只想轉寄到自己的信箱,可以用 Cloudflare Email Routing |
| 系統自動寄信 | 登入驗證碼、重設密碼、通知信 | Resend、Amazon SES、Postmark、SendGrid、Mailgun、Brevo |
還有第三種是行銷電子報,通常會用另一類服務,也建議跟系統信分開寄。電子報被檢舉的機率比較高,不要讓它拖累登入信的信譽。
為什麼不直接用一般信箱(例如 Gmail 或 Zoho)的 SMTP 來寄系統信?因為一般信箱主要是設計給人收發信的,通常有寄送數量的限制;系統寄信服務則另外提供寄送紀錄、退信資訊、API 等功能,比較適合網站自動寄信。
寄件人的地址,其實誰都能填。任何人都可以用自己的伺服器寄一封信,寄件人寫 noreply@auth.biashelf.com。收信的一端要怎麼知道,這封信真的是我寄的?
答案是去查寄件人網域的 DNS,也就是 auth.biashelf.com(我預計拿來寄送登入驗證碼的子網域,第五部分會細說)以及它所屬的 biashelf.com。重點在於:不是用了某個寄信服務,就自動可信;而是我在自己網域的 DNS 上,授權了這個服務。 主要看三件事:
| 機制 | 直白的說法 | 比喻 | DNS 紀錄 |
|---|---|---|---|
| SPF | 我在 DNS 公布一份名單:哪些寄信來源,被這個網域授權可以替它送信。收信端檢查這封信的寄信來源在不在名單上 | 公司公告:只有這幾位員工,可以代表公司寄文件 | TXT |
| DKIM | 寄信服務寄出每封信時,都會蓋一個數位印章,這個章只有它拿得到;我把對應的「印鑑」公布在 DNS 上,收信端拿來比對 | 蓋章加上印鑑證明:章對得上,就是本人;文件被改過,章就對不上 | TXT(有些服務用 CNAME) |
| DMARC | 把前兩項的結果合起來判斷:至少一項通過,而且通過的網域跟信裡顯示的寄件人一致,就算過關;不符合時,要放行、丟進垃圾信件匣還是退回,要不要回報給我 | 公司的處理規定:看到冒用我們名義的文件,請直接銷毀,並通知我們 | _dmarc 的 TXT |
為什麼要求「網域一致」?因為冒名的人也可以讓他自己的網域通過 SPF 或 DKIM,再把信裡的寄件人寫成 noreply@auth.biashelf.com。只看有沒有通過,他就混過去了;要求通過的網域跟寄件人一致,才擋得住。
實際套到三種情境:
| 情境 | SPF | DKIM | DMARC |
|---|---|---|---|
| Resend 替搜輯 Biashelf 寄出的登入信 | ✅ 在名單上 | ✅ 章對得上 | ✅ 通過 |
詐騙者用自己的伺服器,寄件人填 noreply@auth.biashelf.com |
❌ 不在名單上 | ❌ 沒有我的章 | ❌ 不通過,依規則進垃圾信件匣或被退回 |
| 信在傳送途中被改過內容 | ✅ 可能還是通過 | ❌ 章對不上 | 看 SPF 有沒有通過、網域是否一致 |
這三項不是三個各自獨立的紅綠燈:SPF 和 DKIM 是兩種證明方式,DMARC 再把結果合起來判斷。反過來說,如果這三項都沒設定,收信端就少了幾個重要的身分證明。 它還是會看寄件來源的信譽、信件內容、寄送的行為來判斷,但信會更容易被拒收,或被當成垃圾信。

【圖 2|收件端的檢查】
近年 Gmail、Yahoo 等大型信箱業者,對寄件人的要求越來越嚴格。例如 Gmail 要求每天寄超過 5,000 封給 Gmail 使用者的寄件者,SPF、DKIM、DMARC 都要設定。沒有設定 SPF、DKIM 的信,更容易被擋下來。
不過,這三項通過,只代表「這封信真的是你寄的」,不保證一定進收件匣。寄件信譽、信件內容、寄送頻率,也都會影響結果。昨天提到「太冷門的後綴可能比較容易被過濾」,也是這個原因。
同一個名稱,只能有一筆 SPF 紀錄
這裡的「名稱」指的是完整的網址名稱:
biashelf.com和auth.biashelf.com是兩個不同的名稱,各自可以有一筆。但如果biashelf.com已經有一筆 SPF,又照另一個服務的教學再加一筆,兩筆都可能失效。想讓兩個服務都能用同一個名稱寄信,要把它們合併成一筆;或是像今天這樣,讓不同服務的紀錄放在不同的名稱底下。
常見的系統寄信服務:
| 服務 | 特色 | 比較適合 |
|---|---|---|
| Resend | 介面簡單,API 和 SMTP 都能用,對開發者友善 | 小型專案、想快速上手 |
| Amazon SES | 價格很低,但設定步驟多,新帳號要先申請解除限制 | 寄送量大、已經在用 AWS |
| Postmark | 專注在系統信的送達率 | 很在意送達率、願意付費 |
| SendGrid、Mailgun、Brevo | 功能多,系統信和行銷信都能寄 | 需要較完整的寄信功能 |
我選 Resend:設定簡單;免費方案目前是每月 3,000 封、每天 100 封,對現在的搜輯 Biashelf 來說夠用。
Resend 本身也能直接寄信:用 API、後台的編輯器,甚至寄電子報都可以。但登入信需要 Supabase 產生、驗證驗證碼,所以這次用 SMTP 讓 Supabase 接上 Resend。之後網站自己的通知信,可以直接用 Resend 的 API。
常見的收信服務:
| 服務 | 特色 |
|---|---|
| Zoho Mail | 有免費方案,可以用自己的網域開信箱;免費版只能用網頁和 Zoho 自己的 App |
| Google Workspace、Microsoft 365 | 功能完整,按人數月付 |
| Cloudflare Email Routing | 免費,但只能把信轉寄到你原本的信箱,不是真正的信箱 |
我選 Zoho Mail:我註冊時可以使用它的免費方案,有真正的信箱,可以用 support@biashelf.com 收信、回信。
Resend 雖然也能收信,但它是把收到的信交給程式處理,沒有讓人打開來讀、回覆的信箱,所以 support@ 還是交給 Zoho。
選好服務之後,搜輯 Biashelf 的分工是這樣:
| 地址 | 負責的服務 | 用途 |
|---|---|---|
support@biashelf.com |
Zoho Mail | 收信、親自回覆 |
noreply@auth.biashelf.com |
Resend | 寄登入驗證碼、系統通知(用 auth 子網域,理由在 5.2) |

【圖 3|同一個網域,兩種信】
接下來將依序設定 Zoho、Resend、DMARC,最後把 Supabase 接上 Resend。
biashelf.com
support@biashelf.com
目前有提供自動加入紀錄的功能,只要在登入 Cloudflare 的情況下,點擊後就會自動添加

【圖4|Zoho 建立基於域名的帳戶】
第一次登入/註冊 Zoho 時,會先要求你輸入信箱,要輸入的是一個可以收得到的現有信箱,不是你想創的基於域名的信箱

【圖5|永久免費方案】
順著步驟中間可能會遇到要你選方案的畫面,這個時候改從官方首頁 -> 定價 -> 往下滑(一定要)-> 永久免費方案,點擊後可以免費創立(但有使用量限制,需注意)。真的藏很深。

【圖6|新增網域】

【圖7|輸入網域、名稱、機構類型】

【圖8|開始進行域名驗證】

【圖9|選「登入DNS管理器」可以快速添加 DNS 紀錄】

【圖 10|自動跳出要添加的紀錄】

【圖 11|設定超級管理員帳號】

【圖 12|可以添加同一個網域下的其他用戶(可跳過)】
再下一步的設定組也可以依需求設定或跳過

【圖 13|自動 DNS 對應】

【圖 14|新增 DNS 】

【圖 15|會自動幫超級管理員設好信箱】
設定完,用另一個信箱寄信到 support@biashelf.com,確認收得到。

【圖 16|用自己的信箱嘗試寄信】
Resend 這邊,我不驗證根網域,而是驗證子網域 auth.biashelf.com,寄件地址是 noreply@auth.biashelf.com。這也是 Resend 官方的建議,理由有兩個:
auth.biashelf.com。根網域的信譽一旦被拖累,要很久才救得回來,還會連帶影響 support@ 寄出的信auth 一看就是登入、驗證類的信,跟其他信分開,比較好判斷要放哪裡Resend 和 Zoho 綁同一個根網域也可以
沒有規定兩個服務不能用同一個網域。Resend 驗證根網域時,會把自己的 SPF、MX 放在像
send.biashelf.com這樣的子網域、DKIM 放在resend._domainkey.biashelf.com,跟 Zoho 在根網域的紀錄名稱都不同,不會衝突,寄件地址也比較短。用子網域,是多換一層保護。
設定步驟:
auth.biashelf.com
resend._domainkey.auth 底下send.auth、rsend.auth 兩筆 CNAME(名稱和數量以 Resend 畫面為準)
【圖 17|在 Resend 添加網域】

【圖 18|自動認證】

【圖 19|添加DNS紀錄後,過幾分鐘就會顯示成功】
SPF 和 DKIM 是各個寄信服務要的紀錄,DMARC 則不一樣:它是整個網域的規則,不專屬於某一個寄信服務,所以獨立出來設定。
開啟 Cloudflare DMARC Management
biashelf.com 的 DNS 在 Cloudflare,所以我直接用 Cloudflare 的 DMARC Management(免費)。開啟並添加之後,它會把 DMARC 紀錄寫進 DNS,並幫忙收報告、整理成圖表。

【圖 20|Cloudflare DMARC Management】
回到 DNS,看看多了什麼
回到 DNS 紀錄列表,會多出一筆名稱是 _dmarc 的 TXT 紀錄,內容大概像這樣:
v=DMARC1; p=none; rua=mailto:xxxx@dmarc-reports.cloudflare.net
p=none:只觀察,先不要擋信。等確認所有寄信的服務都設定正確,再考慮調成更嚴格的規則rua:報告要寄到哪裡。這裡是 Cloudflare 產生的專屬地址兩個要注意的地方
_dmarc 紀錄。 如果原本已經有一筆,不要再自己加第二筆;Cloudflare 會把它的報告地址加進原本的紀錄(rua 可以同時寫好幾個地址,用逗號分開)auth.biashelf.com 預設也會套用同一套規則開啟之後,總覽頁上會出現幾個黃色、紅色的標籤,不用緊張:
| 項目 | 顯示 | 意思 |
|---|---|---|
| DMARC policy | None(Warning) | 規則是 p=none,只觀察、不擋信。這是刻意的 |
| SPF policy | Soft fail(Warning) | 根網域的 SPF 結尾用了常見的 ~all:不在名單上的來源,收信端會起疑,但不一定拒收 |
| BIMI in use | No(Fail) | BIMI 是讓收件匣顯示品牌 Logo 的進階功能,跟信能不能收到無關,這次不用處理 |
DMARC 的規則通常分階段調整:先用 p=none 觀察一兩週的報告,確認 Zoho、Resend 寄的信都通過,再調成 quarantine(沒通過的丟進垃圾信件匣),最後才是 reject(直接拒收)。一開始就設嚴格,萬一哪個正常的寄信來源設定錯了,被擋下的會是自己的信。
到這裡,biashelf.com 底下一共有 12 筆紀錄:
| 名稱 | 類型 | 用途 | 服務 |
|---|---|---|---|
biashelf.com |
CNAME | 網站 | Vercel(Day 23) |
www |
CNAME | 網站 | Vercel(Day 23) |
biashelf.com |
MX × 3 | 收信(三台伺服器,有優先順序) | Zoho |
biashelf.com |
TXT × 2 | SPF,以及驗證網域擁有權 | Zoho |
zmail._domainkey |
TXT | Zoho 寄出的信的簽名(DKIM) | Zoho |
send.auth、rsend.auth |
CNAME | 退信、SPF,交給 Resend 管理 | Resend |
resend._domainkey.auth |
TXT | Resend 寄出的信的簽名(DKIM) | Resend |
_dmarc |
TXT | 沒通過檢查時的處理規則、報告寄到哪裡 | Cloudflare DMARC Management |
同一個網域底下,網站、收信、寄信各自有自己的紀錄,互不干擾。這跟昨天的角色分工是同一件事:網域只是一個名字,DNS 負責告訴大家,每件事要去找誰。
最後,把 Supabase 寄登入信的方式,從內建的寄信服務換成 Resend:

【圖 21|設定前後寄信流程】
先在 Resend 建立一把 API key:
auth.biashelf.com 這個網域寄信這把 key 等一下會當成 SMTP 的密碼。它和 Supabase 的 secret key 一樣,不要貼進 AI 對話、截圖或 commit,也不能出現在瀏覽器的程式裡。

【圖 22|建立 Resend API key - 1】

【圖 23|建立 Resend API key - 2】
接著到 Supabase 後台的 Auth 設定,打開自訂 SMTP(Authentication -> Email -> SMTP Settings),填入:
| 欄位 | 填什麼 |
|---|---|
| 寄件地址 | noreply@auth.biashelf.com |
| 寄件人名稱 | 搜輯 Biashelf |
| 主機 | smtp.resend.com |
| 連接埠 | 465 |
| 使用者名稱 | resend |
| 密碼 | 剛剛建立的 API key |

【圖 24|Supabase SMTP 設定】
換成自訂 SMTP 之後,就不再受內建寄信每小時 2 封的限制。Supabase 另外有自己的寄信上限,目前預設每小時 30 封,可以在 Auth 的 Rate Limits 裡調整。
注意這是兩層不同的限制:Supabase Auth 先限制最多觸發幾封登入相關的信,Resend 的帳號再有自己的寄送額度。上限不要開太大,它也是一道防護:避免有人用登入表單,替你狂寄信給陌生人。
存檔之後,Supabase Auth 寄的所有信(註冊確認、登入、重設密碼等)都會自動改由 Resend 寄出,程式不用改。但信件內容不會變,還是英文預設範本,寄出去的也還是連結;要改內容和登入方式,是下一節的事。
稍微注意一下:
- 信裡的連結是會根據 siteURL 產生:如果 Supabase 的 Site URL 少打了 s,可能會顯示不安全的連線。記得檢查 Site URL 和 Redirect URLs 都是
https://- 開發測試的信也會用掉額度:開發和正式目前共用同一個 Supabase 專案,本機測試登入寄出的信,也會算進 Resend 的額度。
既然寄信的方式重做了,我也順便把 Day 18 的「點連結登入」換成「輸入驗證碼登入」。這一節只講這次的改動,不展開成完整的無密碼登入教學。
| 點連結登入 | 輸入驗證碼登入 | |
|---|---|---|
| 跨裝置 | 比較依賴在哪個瀏覽器、哪台裝置打開連結 | 只要把驗證碼輸回原本的登入畫面,電腦上登入、手機看信也可以 |
| 信箱自動掃描 | 有些公司、學校的信箱會先自動點開信裡的連結做安全檢查,一次性的登入連結可能因此被用掉(Resend 針對 Supabase 登入信的說明也提到這點) | 驗證碼不會被「點掉」 |
| 使用者多做的事 | 點一下 | 多輸入一組數字 |
驗證碼的風險是被人猜中,所以它會有有效時間,Supabase 也會限制嘗試的次數。
另外,寄信服務的「點擊追蹤」會改寫信裡的連結,有些信箱的安全掃描也會先造訪連結,對一次性的登入連結可能造成干擾。改用驗證碼之後,登入信裡就沒有需要點的連結了,這個問題也一起消失。

【圖 25|Supabase Email OTP 登入流程】
寄信用的函式其實沒變,一樣是 signInWithOtp。差別在信件範本:範本裡放 {{ .Token }},寄出去的是驗證碼;放 {{ .ConfirmationURL }},寄出去的是連結。
要注意,第一次用這個信箱的人,收到的是「註冊確認」範本,不是「登入」範本。所以兩個範本都要改,不然新使用者第一次登入,收到的還是連結。
前端則多了一個輸入驗證碼的步驟,用 verifyOtp 確認。
最後是信件本身。原本的登入信是 Supabase 的英文預設範本:主旨寫著「Confirm your email address」,內容只有一行字和一個指向 xxx.supabase.co 的連結。寄件人是 biashelf.com,連結卻是另一個網域,這種信在收件端看來,跟釣魚信很像。現在改成:
可以請 AI 幫忙生成。

【圖 26|Supabase Email 模板調整】
註冊信在 Confirm Sign Up 的信去改。

【圖 27|收到的信看起來比較不像詐騙,正式很多】

【圖 28|也需要請 Codex 同步調整登入相關的程式碼】
| 測試 | 預期結果 |
|---|---|
| 用不在 Supabase 團隊裡的 Gmail 登入 | 收到驗證碼信 |
| 用 Outlook 或其他信箱再試一次 | 收到驗證碼信 |
| 信件位置 | 成功送達;記錄實際進了收件匣還是垃圾信件匣 |
| 寄件人 | 顯示「搜輯 Biashelf」、noreply@auth.biashelf.com |
查看信件的原始內容(Gmail:「⋮」→「顯示原始郵件」;Mac 郵件 App:⌥⌘U,搜尋 Authentication-Results) |
SPF、DKIM、DMARC 的檢查結果符合預期(DMARC 為 PASS)。標頭裡會看到 SPF 查的是 rsend.auth、DKIM 查的是 auth,DMARC 則往上找到 biashelf.com |
| 信裡的連結(如果有) | 都是 https:// |
| 輸入正確的驗證碼 | 登入成功,回到原本的頁面 |
| 輸入錯誤的驗證碼 | 顯示錯誤,不會登入 |
| 驗證碼過期後再輸入 | 顯示過期,可以重新寄送 |
| 電腦上登入、手機看驗證碼 | 可以登入 |
| Google 登入 | 跟原本一樣正常 |
寄信到 support@biashelf.com |
Zoho 收得到 |
| 檢查 repo 和瀏覽器載入的程式 | 找不到 Resend 的 API key |
| 產品 | 系統信 | 收信 | 行銷信 |
|---|---|---|---|
| 電商 | 訂單確認、出貨通知、重設密碼 | 客服信箱 | 促銷活動 |
| SaaS | 登入驗證碼、帳單、用量提醒 | 支援信箱 | 新功能介紹 |
| 線上課程 | 報名成功、上課提醒 | 學員提問 | 新課程推廣 |
規模越大,這三種信越會由不同的服務、甚至不同的子網域負責。原因都一樣:它們的寄送量、被檢舉的機率不同,混在一起,最重要的系統信就可能被連累。
如果之後要寄行銷信,規矩跟系統信不一樣:要對方同意訂閱才能寄、要能一鍵取消訂閱,也建議用另一個子網域寄,例如 news.biashelf.com,不要讓被檢舉的機率拖累登入信。Resend 的 Broadcasts 就是做這件事的。
今天的新觀念:
| 觀念 | 一句話 |
|---|---|
| SMTP | 寄信用的規則 |
| 系統信(交易信) | 使用者的操作觸發、自動寄出的信 |
| SPF | 哪些伺服器可以代表這個網域寄信 |
| DKIM | 信件的簽名,證明內容沒被改過 |
| DMARC | SPF、DKIM 至少一項通過且網域一致才算過關,以及不符合時的處理規則 |
| 驗證碼登入(OTP) | 用一次性的數字代替一次性的連結 |
信寄出去之後,能不能被收到,決定權在收件端。寄信的人能做的,是把身分證明準備好:名單、簽名、處理規則,全都寫在 DNS 裡。
到這裡,網站有了網址,也能寄信了。但每次改完程式,我都還是手動測一輪,確認沒有改壞東西。功能越來越多,這件事也越來越累。
明天,我們要讓這些檢查變成每天固定會做的事。
我們明天見。