innovedus_cms/docs/newsletter_integration_memo.md
Warren Chen 6ea501dc62 feat: Implement one-click unsubscribe feature and newsletter campaign management
- Added one-click unsubscribe functionality with token generation and verification.
- Introduced a new model for tracking one-click unsubscribe audits.
- Enhanced newsletter campaign management with the ability to send campaigns immediately.
- Implemented a scheduler for dispatching due newsletter campaigns.
- Updated views and templates to support one-click unsubscribe and campaign previews.
- Added management commands for running the newsletter scheduler.
- Removed obsolete SSL certificate file.
- Updated entrypoint script to handle different application roles.
2026-02-25 14:42:46 +09:00

13 KiB
Raw Blame History

電子報介接備忘錄(租戶端 / 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=...
  • 用途:使用者互動頁、站台前台/後台頁面。
  1. Member Center API 網址
  • 例:https://{member-center}/newsletter/subscribe
  • 用途:訂閱、確認、退訂 token、退訂等 API。
  1. Send Engine API 網址
  • 例:https://{send-engine}/api/send-jobs
  • 用途:建立/查詢/取消發信任務。
  1. 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_idGuid
    • emailstring
  • JSON body可選
    • preferencesobject
    • sourcestring
  • 回傳包含 confirm_token
  1. 訂閱確認(雙重驗證)
  • GET /newsletter/confirm?token=...
  • 注意:confirm 是 GET不是 POST
  1. 單一名單退訂
  • POST /newsletter/unsubscribe
  • JSON body必要
    • tokenstring
  1. 申請退訂 token
  • POST /newsletter/unsubscribe-token
  • JSON body必要
    • list_idGuid
    • emailstring
  • 回傳:
    • unsubscribe_tokenstring

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 endpointone-click + 人類點擊頁)。

9.2 Unsubscribe Token 設計

建議使用 JWT 或 HMAC token。

Token 內容建議:

  • subscriber_id
  • list_id
  • site_id
  • exp
  • nonceoptional

安全要求:

  • 不需登入即可退訂。
  • 必須可 idempotent。
  • Token 需有過期時間。
  • Token 必須簽章驗證。

9.3 Unsubscribe Endpoint

  1. One-click endpoint
  • POST /u/unsubscribe
  • Requesttoken
  • 流程:
    • 驗證 token
    • 呼叫會員平台 API
    • 回傳 200
  • 不可:
    • 要求登入
    • 要求二次確認
  1. 人類點擊頁面
  • GET /u/unsubscribe?token=xxx
  • 流程:
    • 驗證 token
    • 顯示退訂成功頁面

9.4 發信 Header 規則

Newsletter 必須包含:

  • List-Unsubscribe: <https://domain/u/unsubscribe?token=xxx>
  • 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 -> 400410
  • 已退訂 -> 200
  • 簽章錯誤 -> 400
  1. 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/unsubscribetoken 可由 query/body 提供)
    • GET /u/unsubscribe?token=...(人類點擊頁)
  • 已實作 token 驗證HMAC + exp)與 idempotent 行為(重複退訂維持 200
  • 已實作 server-side audit logOneClickUnsubscribeAudit)。
  • 已提供 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_idsubjectbody_html/body_text/template
  • response 讀取:
    • send_job_id
    • status
  1. 查詢 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 不建議只放 <head><style>,實務上需考慮 email client 相容性,通常會在發送前做 CSS inlining將重要樣式轉為 inline style
  • 可保留少量 head style例如 media query但核心排版建議以 inline 為主。

目標:

  • 提供可編輯的 newsletter shell例如 layout.html),內含 head、header、footer 與內容插槽。

建議實作概念:

  • 內容編輯區僅負責 body 內文。
  • 發送時由 backend 組裝:shell + content + one-click headers/links
  • 預覽也走同一組裝流程,避免「預覽長得跟實際寄出不一致」。

10.4 退訂連結與個人化參數責任分工

共識方向:

  • 退訂連結需與收件者身分綁定(每位收件者唯一 token / URL
  • 內容模板需預留替換位(例如 {{unsubscribe_url}})。

責任分工建議:

  • CMS產內容、產 placeholder、組裝信件與 one-click header。
  • Send Engine執行發送、接收投遞回報bounce/complaint 等)。
  • SES或下游 provider最終投遞與回執來源。

10.5 文章區塊直接引入

需求方向:

  • 支援從既有文章中挑選區塊或全文片段匯入電子報。

建議:

  • 第一階段可先做「選文章 -> 帶入標題/摘要/首圖/連結」。
  • 第二階段再考慮「可編輯快照」或「保持動態同步」策略。

10.6 預覽能力

需求方向:

  • 內文編輯預覽。
  • 樣式shell編輯預覽。
  • 能注入假資料(假收件者、假 token、假文章看最終結果。

建議:

  • 做「最終寄出 HTML 預覽」功能,預覽管線與正式發送使用同一套 renderer。
  • 後續可加多裝置寬度與常見 email client 快速檢視模式(若使用者回饋有需要)。