# 電子報介接備忘錄(租戶端 / Wagtail) 最後更新:2026-02-18 ## 1. 目標與前提 本備忘錄用於整理租戶端(Wagtail)接下來的實作方向,重點是先完成可運行流程,再逐步補完細節。 ## 2. 已確認決策(本次會議結論) 1. Member Center 雖為共用平台,但目前只有一個租戶。 2. 現階段「發確認信」與「訂閱/退訂頁面」先做在 Wagtail。 3. Wagtail 與 Member Center 的溝通以 API 為主。 4. SMTP relay 僅負責寄出一次,重送、token 安全、簽章等由其他系統或工具負責,不屬 Wagtail 責任。 5. 工作大項與順序固定為: 1. 準備工作(電子報系統設定) 2. 訂閱流程 3. 退訂流程 4. 電子報 app 5. 發信流程(排程) ## 3. 工作大項與執行順序(含範圍) ## 3.1 準備工作(電子報系統設定,第一優先) 範圍: - 建立 Member Center 連線設定(base URL、tenant_id、list_id、API timeout 等)。 - 建立 Send Engine 連線設定(base URL、OAuth scope、API timeout 等)。 - 建立一般發信設定(SMTP relay、寄件者名稱/信箱、reply-to、預設編碼等)。 - 建立模板設定: - 訂閱確認信 template - 訂閱確認(成功)頁面 template - 取消訂閱頁面 template 重點: - 設定集中管理,避免散落在程式碼或多處後台欄位。 - 區分「系統連線設定」與「內容模板設定」,便於權限與維運。 完成標準(DoD): - 可在單一設定入口完成上述設定並通過基本驗證。 - 模板可被後續訂閱/退訂流程直接引用。 ## 3.2 訂閱流程(第二優先) 範圍: - 在站台頁面區塊(暫定 Footer)提供 email 訂閱入口。 - 後端接收 email,呼叫 Member Center 訂閱 API。 - 由 Wagtail 送出訂閱確認信(透過 SMTP relay)。 - 使用者點信內連結回到 Wagtail 確認頁。 - 確認頁自動呼叫 Member Center 確認 API。 重點: - 流程中 token 需一路帶入與驗證。 - 確認信內容採 HTML(可附純文字 fallback)。 完成標準(DoD): - 可從站台成功發起訂閱。 - 可收到確認信並完成確認。 - 確認成功/失敗頁有明確訊息與追蹤記錄。 ## 3.3 退訂流程(第三優先) 範圍: - 電子報底部提供退訂連結,導向 Wagtail 退訂頁。 - 進入退訂頁時,先向 Member Center 申請退訂 token。 - 使用者按確認退訂後,呼叫 Member Center 退訂 API 完成流程。 重點: - token 不做長期保存。 - 頁面需可處理 token 失效/不存在等錯誤狀態。 完成標準(DoD): - 使用者能從信件連結完成退訂。 - 退訂結果頁可正確呈現成功/失敗狀態。 ## 3.4 電子報 app(第四優先) 範圍: - 在 Wagtail 後台提供電子報內容管理能力。 - 提供 HTML 編輯器建立/編修內容。 - 支援可替換參數(例如 token、email)。 重點: - 編輯器中的連結參數以佔位符表示,發送前由 backend 替換。 - 若有 Member Center API 相關連結需在內容中可配置,也採相同參數機制。 完成標準(DoD): - 編輯器可存草稿與更新內容。 - 內容中參數可在送出前正確替換。 ## 3.5 發信流程(排程,第五優先) 範圍: - 可建立發送任務與排程時間。 - 排程前可持續編輯內容與調整時間。 - 到時觸發時:先取 Member Center auth,再將內容包固定 header/footer,送到 Send Engine。 重點: - 「內容定稿」與「實際送出」拆開。 - 發送前的模板組裝由 Wagtail backend 負責。 完成標準(DoD): - 排程到點可成功建立 Send Engine send job。 - 失敗可記錄原因並可人工重試。 ## 4. 參數替換規則(先行約定) 建議佔位符格式(待實作時可再定版): - `{{token}}` - `{{email}}` - `{{list_id}}` - `{{tenant_id}}` - `{{confirm_url}}` - `{{unsubscribe_url}}` 替換時機: - 發信前 backend 最後一步統一替換。 注意事項: - URL 參數需做 URL encode。 - 缺少必要參數時,中止發送並記錄可追蹤錯誤。 ## 5. URL 與系統歸屬標註規範(文件撰寫規則) 後續文件凡提到網址,必須明確標註「是哪個系統」: 1. Wagtail(租戶站台)網址 - 例:`https://{tenant-site}/newsletter/confirm?token=...` - 用途:使用者互動頁、站台前台/後台頁面。 2. Member Center API 網址 - 例:`https://{member-center}/newsletter/subscribe` - 用途:訂閱、確認、退訂 token、退訂等 API。 3. Send Engine API 網址 - 例:`https://{send-engine}/api/send-jobs` - 用途:建立/查詢/取消發信任務。 4. SMTP Relay 連線端點 - 用途:Wagtail 寄送確認信。 - 備註:僅作為寄送通道,不承擔重送與安全機制。 ## 6. 非 Wagtail 責任邊界(本階段) 以下不納入目前 Wagtail 工作範圍: - SMTP 後續重送策略。 - token 簽章與高階安全策略。 - 跨系統風控與防濫用機制。 ## 7. 下一步(實作啟動清單) 1. 先完成電子報系統設定(Member Center / Send Engine / SMTP / 模板)。 2. 再完成訂閱流程 API 串接與確認頁。 3. 再完成退訂頁與退訂 API 串接。 4. 建立電子報 app 與 HTML 編輯器資料模型。 5. 最後接排程任務與 Send Engine 發信。 ## 8. Member Center API 實際規格(以 member_center 程式碼 / OpenAPI 為準) 資料來源(2026-02-17 比對): - `../member_center/src/MemberCenter.Api/Controllers/NewsletterController.cs` - `../member_center/docs/openapi.yaml` ### 8.1 Base URL 與路徑 - OpenAPI `servers.url` 為 `/api`,部署時實際呼叫通常為: - `https://{member-center}/api/newsletter/...` - 若部署已在 gateway 做 path rewrite,也可為: - `https://{member-center}/newsletter/...` - 租戶端必須以實際部署路徑設定 `member_center_base_url`。 ### 8.2 Endpoint / Method / Request 1. 訂閱 - `POST /newsletter/subscribe` - JSON body(必要): - `list_id`(Guid) - `email`(string) - JSON body(可選): - `preferences`(object) - `source`(string) - 回傳包含 `confirm_token` 2. 訂閱確認(雙重驗證) - `GET /newsletter/confirm?token=...` - 注意:**confirm 是 GET,不是 POST** 3. 單一名單退訂 - `POST /newsletter/unsubscribe` - JSON body(必要): - `token`(string) 4. 申請退訂 token - `POST /newsletter/unsubscribe-token` - JSON body(必要): - `list_id`(Guid) - `email`(string) - 回傳: - `unsubscribe_token`(string) ### 8.3 與租戶端目前實作對齊結果 - `subscribe`:已使用 `POST`。 - `confirm`:已改為 `GET + query token`(修正 HTTP 405 問題)。 - `unsubscribe`:已使用 `POST`,且 body 僅送 `token`。 - `unsubscribe-token`:已使用 `POST`,且 body 送 `list_id + email`。 ## 9. List-Unsubscribe One-Click 規範(發信流程階段導入) 說明: - 本節為「電子報 app + 排程發信」階段的目標規範。 - 已完成的訂閱/退訂流程維持現況,不回頭重做;待發信流程上線時再整合 one-click。 ### 9.1 目標 - 提供電子報編輯與排程。 - 產生 `List-Unsubscribe` one-click header。 - 呼叫發信代理 API。 - 提供 unsubscribe endpoint(one-click + 人類點擊頁)。 ### 9.2 Unsubscribe Token 設計 建議使用 JWT 或 HMAC token。 Token 內容建議: - `subscriber_id` - `list_id` - `site_id` - `exp` - `nonce`(optional) 安全要求: - 不需登入即可退訂。 - 必須可 idempotent。 - Token 需有過期時間。 - Token 必須簽章驗證。 ### 9.3 Unsubscribe Endpoint 1. One-click endpoint - `POST /u/unsubscribe` - Request:`token` - 流程: - 驗證 token - 呼叫會員平台 API - 回傳 `200` - 不可: - 要求登入 - 要求二次確認 2. 人類點擊頁面 - `GET /u/unsubscribe?token=xxx` - 流程: - 驗證 token - 顯示退訂成功頁面 ### 9.4 發信 Header 規則 Newsletter 必須包含: - `List-Unsubscribe: ` - `List-Unsubscribe-Post: List-Unsubscribe=One-Click` 註記: - Transactional email 不應包含 List-Unsubscribe。 ### 9.5 與會員平台整合(one-click 目標介面) 呼叫 API: - `POST /api/subscriptions/unsubscribe` Request: - `subscriber_id` - `list_id` - `source: "one_click"` - `campaign_id` 回傳: - `success` - `already_unsubscribed` ### 9.6 測試案例 1. Token 驗證 - 正常 token -> 成功退訂 - 過期 token -> `400` 或 `410` - 已退訂 -> `200` - 簽章錯誤 -> `400` 2. Header 驗證 - Newsletter 發送時必定包含 `List-Unsubscribe` - Transactional email 不包含 `List-Unsubscribe` - Header 格式正確 ### 9.7 安全與 UX 安全: - Unsubscribe endpoint 不受 CSRF 限制 - Token 驗證必須 server-side - 記錄 audit log - 不顯示 `subscriber_id` 在 URL 明文 UX: - 退訂成功頁面簡潔 - 提供重新訂閱入口 - 不強迫填問卷 ### 9.8 目前實作狀態(Wagtail) - 已提供 one-click endpoint: - `POST /u/unsubscribe`(token 可由 query/body 提供) - `GET /u/unsubscribe?token=...`(人類點擊頁) - 已實作 token 驗證(HMAC + `exp`)與 idempotent 行為(重複退訂維持 200)。 - 已實作 server-side audit log(`OneClickUnsubscribeAudit`)。 - 已提供 one-click URL + Header 產生工具: - `List-Unsubscribe` - `List-Unsubscribe-Post` ### 9.9 Send Engine 發送介接(依 `../mass_mail_engine/docs/openapi.yaml` 對齊) 已對齊的 API: 1. 建立 send job - `POST /api/send-jobs` - request 以 `CreateSendJobRequest` 為主(`list_id`、`subject`、`body_html/body_text/template`) - response 讀取: - `send_job_id` - `status` 2. 查詢 send job - `GET /api/send-jobs/{id}` - 若建立後狀態非最終態,持續輪詢直到最終態或超過輪詢上限。 狀態對應(Wagtail campaign): - Send Engine `completed` -> campaign `sent` - Send Engine `failed` / `cancelled` -> campaign `failed` - 建立失敗 / 輪詢逾時 / 輪詢錯誤 -> campaign `failed` Send Engine 最終態(terminal): - `completed` - `failed` - `cancelled` 備註: - 目前 dispatch 紀錄改為「每次送 job / 重試一次一筆」,不再是逐收件者一筆。 - 若要做投遞到單一收件者的最終監控(delivery/bounce/complaint),仍建議接 Send Engine webhook 或事件回寫機制處理。 ### 9.10 One-Click Token 模式(目前採用) 目前採用:`MemberCenter token relay` 流程: 1. CMS 發送 payload 時同時提供: - `template.list_unsubscribe_url_template`(給 Send Engine 產生 `List-Unsubscribe` header) 信內可點連結由 `body_html/body_text` 直接使用 `{{unsubscribe_token}}` 組 URL。 例如:`https://{cms}/u/unsubscribe?token={{unsubscribe_token}}` 2. Send Engine 於發送時向 Member Center 取得每位收件者的退訂 token,並替換 `{{unsubscribe_token}}`。 3. 使用者點擊信內退訂(或 mailbox one-click)後,進入 CMS `/u/unsubscribe`。 4. CMS 不自行驗簽 token,直接以 S2S 呼叫 Member Center `unsubscribe(token)` 完成退訂。 備註: - 舊的 CMS 自簽 HMAC one-click token 保留工具函式作為備援,但主流程已切到 relay 模式。 ## 10. 下一階段演進備忘(先記錄,待試用回饋後再排) 說明: - 本節為「規劃中」項目,先記錄方向,不立即實作。 - 優先等待:電子報排版產出 + 實際 user 試用回饋。 ### 10.1 編輯器能力演進 現況: - 目前使用 Wagtail Draftail 作為 HTML 編輯器,已可插入圖片(圖庫選擇 / 上傳)。 待回饋後評估: - 若編輯需求提升(table、欄位版型、拖拉區塊、進階 email 元件),再評估導入更完整的 newsletter editor。 - 原則:先確認真實編輯痛點,再導入新編輯器,避免過早複雜化。 ### 10.2 電子報樣式(CSS)策略 建議方向: - 採「固定樣式基底 + 內容編輯」模式。 - CSS 不建議只放 `