diff --git a/docs/newsletter_integration_memo.md b/docs/newsletter_integration_memo.md index 11d7199..793cd36 100644 --- a/docs/newsletter_integration_memo.md +++ b/docs/newsletter_integration_memo.md @@ -1,6 +1,6 @@ # 電子報介接備忘錄(租戶端 / Wagtail) -最後更新:2026-02-11 +最後更新:2026-02-18 ## 1. 目標與前提 @@ -204,3 +204,204 @@ - `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 或事件回寫機制處理。 + +## 10. 下一階段演進備忘(先記錄,待試用回饋後再排) + +說明: +- 本節為「規劃中」項目,先記錄方向,不立即實作。 +- 優先等待:電子報排版產出 + 實際 user 試用回饋。 + +### 10.1 編輯器能力演進 + +現況: +- 目前使用 Wagtail Draftail 作為 HTML 編輯器,已可插入圖片(圖庫選擇 / 上傳)。 + +待回饋後評估: +- 若編輯需求提升(table、欄位版型、拖拉區塊、進階 email 元件),再評估導入更完整的 newsletter editor。 +- 原則:先確認真實編輯痛點,再導入新編輯器,避免過早複雜化。 + +### 10.2 電子報樣式(CSS)策略 + +建議方向: +- 採「固定樣式基底 + 內容編輯」模式。 +- CSS 不建議只放 `