在 Zeabur 上為 Ghost CMS 設定 Email 發信功能(Brevo SMTP)

Ghost 預設的 Direct 發信在雲端環境幾乎不可靠。記錄我把 Ghost 改成 Brevo SMTP 的完整過程,以及如何找出容器真實的出站 IP 解決白名單問題。

分享
在 Zeabur 上為 Ghost CMS 設定 Email 發信功能(Brevo SMTP)
Photo by Mariia Shalabaieva on Unsplash

在把 Ghost CMS 部署到 Zeabur 之後,我一直沒有特別在意 Email 發信的問題——直到有一天想測試會員訂閱流程,才發現根本收不到任何確認信。翻了一下 Ghost 的設定,才知道預設的發信方式問題很大。這篇文章記錄我把 Ghost 的 Email 改成用 Brevo SMTP 發信的完整過程,以及中間踩到的一個讓我卡很久的坑。

為什麼 Ghost 預設的 Direct 發信不可靠

Ghost 預設的 mail__transportDirect,意思是 Ghost 會直接從伺服器連線到收件方的 Mail Server 送出信件,中間不經過任何 SMTP relay。這在本地開發環境偶爾能用,但在正式環境幾乎可以確定會失敗,原因有幾個:

  • 大多數雲端服務商(包含 Zeabur)的出站 IP 不在主流郵件服務商的信任名單內
  • 沒有 DKIM 簽章,信件會被 Gmail、Outlook 等判定為可疑
  • 容易被直接丟進垃圾信匣,甚至被拒收

結論是:只要是正式環境,一定要換成 SMTP relay

為什麼選擇 Brevo

市面上的 SMTP 服務很多,我選 Brevo(前身是 Sendinblue)有幾個理由:

  • 免費方案提供每天 300 封,對個人部落格完全夠用
  • 支援自訂寄件網域,搭配 DKIM/SPF/DMARC 設定後信件不容易進垃圾信
  • SMTP 設定簡單,不需要特殊設定就能用

設定寄件網域:透過 Cloudflare 自動驗證 DNS

在取得 SMTP 憑證之前,有一個步驟我覺得很值得特別說:設定寄件網域(Sender Domain)

這步驟不是必要的,跳過也能發信——但如果沒有正確設定 DKIM 和 SPF,信件仍然容易被判定為可疑。既然都花時間設定了,順手做完才值得。

流程是這樣的:

  1. 進入 Brevo 後台,點選左側 Settings → Senders & IPs → Domains
  2. Add a domain,輸入你的網域(例如 yourdomain.com
  3. Brevo 會列出幾筆需要新增的 DNS 記錄(DKIM、SPF、DMARC)

這裡有個讓我很驚喜的功能:因為我的網域是在 Cloudflare 購買和管理的,Brevo 提供了 直接登入 Cloudflare 自動設定 的選項。

點下 Connect to Cloudflare 後,頁面會跳轉到 Cloudflare 的 OAuth 授權頁面。登入 Cloudflare 帳號並授權之後,Brevo 會自動把所需的 DNS 記錄(包含 DKIM CNAME、SPF TXT、DMARC TXT)全部寫進 Cloudflare DNS,完全不用手動貼記錄。

授權完成後回到 Brevo,等幾分鐘讓 DNS 生效,再點 Verify 確認即可。驗證通過後,這個網域就可以作為合法的寄件來源。

注意:Cloudflare 的 DNS API 授權是 per-account 的,確認你用的 Cloudflare 帳號有該網域的管理權限。

申請帳號並取得 SMTP 憑證

brevo.com 註冊免費帳號,完成後進入後台:

  1. 點選左側選單的 Settings
  2. 找到 SMTP & API 頁面
  3. 在 SMTP 區塊可以看到 Server、Port、Login 等資訊
  4. Generate a new SMTP key 產生密碼(只會顯示一次,要立刻儲存)
Brevo SMTP settings page showing server, port, login and SMTP key generation

Brevo 後台的 SMTP 設定頁,記下 Server、Port、Login 和新產生的 SMTP Key

取得的資訊大致如下:

  • SMTP Serversmtp-relay.brevo.com
  • Port587
  • Login:你在 Brevo 申請的帳號 Email
  • SMTP Key:剛才產生的密碼

在 Zeabur 設定 Ghost 的環境變數

有了 SMTP 憑證之後,到 Zeabur Dashboard 的 Ghost 服務,切換到 Variables 標籤,新增或更新以下幾個變數:

變數名稱
mail__transport SMTP
mail__options__host smtp-relay.brevo.com
mail__options__port 587
mail__options__auth__user Brevo 帳號 Email
mail__options__auth__pass Brevo SMTP Key
mail__from 你希望寄件人顯示的 Email(需在 Brevo 驗證過的網域)

設定完記得重啟 Ghost 服務讓變數生效。

關鍵:IP 白名單要填對 IP

Brevo 的免費方案需要設定 IP 白名單,只有被允許的 IP 才能透過你的帳號發信。這裡我卡了一段時間,原因是我一開始填的 IP 填錯了

直覺上,我用 dig 查了 Ghost 網域的 A 記錄,拿到一個 IP 填進去,結果測試寄信時仍然收到 525 5.7.1 Unauthorized IP address 的錯誤。

問題在於:DNS 查到的 IP 是入站流量的 IP(通常是 Load Balancer),但 Ghost 發 SMTP 請求時走的是容器自己的出站 IP,兩個不一定相同。

正確的做法是直接在容器內部查詢出站 IP:

npx zeabur@latest service exec --id <ghost-service-id> -- sh -c "wget -qO- https://api.ipify.org"

這樣得到的才是 Ghost 連線到 Brevo SMTP 時實際使用的 IP。把這個 IP 填到 Brevo 的 Settings → Authorized IPs

Brevo authorized IPs whitelist settings page

Brevo 的 IP 白名單設定,要填容器的出站 IP 而不是 DNS 查到的入站 IP

測試成功

IP 白名單設定完成後,到 Ghost Admin 的 Posts,開啟任意一篇文章進入發布流程,在 Newsletter 選項下方會看到 Send a test email to yourself 按鈕,點下去應該就能收到測試信了。

Brevo dashboard showing successful email delivery stats

Brevo 後台確認信件成功發出

FAQ

設定之後重啟,IP 會不會變?不一定,Zeabur 有時候重啟後 IP 不變,但不保證永遠如此。如果之後又出現 Unauthorized IP,用同樣的方式重新查一次容器出站 IP,更新白名單即可。免費方案每天 300 封夠用嗎?對個人部落格來說完全夠用。Ghost 的 Email 主要用在會員訂閱確認、登入 magic link、Newsletter 發送等,一般流量下每天 300 封綽綽有餘。Zeabur Email(ZSend)和 Brevo 有什麼差別?Zeabur Email 是 HTTP API 形式的發信服務(類似 Resend),適合透過程式碼呼叫 API 發信。Ghost 使用的是 SMTP 協定,兩者無法直接相容,所以需要另外搭配支援 SMTP 的服務。

結語

整個設定流程不算複雜,但「入站 IP 和出站 IP 不同」這個細節確實容易踩坑。如果你也在 Zeabur 上跑 Ghost,遇到 Unauthorized IP 的錯誤,記得用 service exec 查容器內部的出站 IP 再填白名單。