warrenchen 7647a8cb3b feat: Add initial database migration for SendEngine with new tables and relationships
- Created migration file for rebaseline of the database schema.
- Added tables: auth_clients, tenants, auth_client_keys, webhook_nonces, events_inbox, lists, campaigns, subscriptions, send_jobs, delivery_summary, and send_batches.
- Defined relationships and constraints between tables.
- Updated DbContext and model snapshot to reflect new entities and their configurations.
- Removed deprecated ListMember entity and its references.
- Introduced Dockerfile for building and running the SendEngine application.
- Enhanced installer program to support tenant creation and webhook client management with Member Center integration.
2026-02-19 17:21:06 +09:00

116 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flows
本文件描述 Send Engine 的三條核心流程:訂閱事件同步、發送排程、退信處理。
本引擎只負責事件控制與執行,不提供 UI。
ESP 介接暫定為 Amazon SES。
Member Center 為多租戶架構,信件內容由各租戶網站產生後送入 Send Engine。
## 1. 訂閱事件同步流程
目的:以事件為主,建立/更新本地名單快照tenant/list scope不做跨租戶查詢。
流程:
1. Member Center 發出事件(`subscription.activated` / `subscription.unsubscribed` / `preferences.updated`)。
2. Send Engine 的 Event Ingest 接收事件Webhook 或 Queue 擇一)。
3. 進行驗證tenant scope、簽章/Token、重放保護
4. 事件落地為 Inboxappend-only標記 `received_at`
5. Consumer 依 `event_id` 去重並處理:
- activated → upsert 訂閱者、掛到 tenant/list
- unsubscribed → 標記狀態為 unsubscribed保留歷史
- preferences.updated → 更新偏好欄位與訂閱狀態
6. 寫入 List Store 快照(只增量更新,不拉全量)。
7. 記錄處理結果與版本號(供重播與對帳)。
錯誤與重試:
- 驗證失敗 → 直接拒絕401/403不寫入 Inbox。
- DB 暫時性錯誤 → 重試(含指數退避),仍失敗則進 DLQ。
- 事件格式錯誤 → 標記為 invalid記錄原因。
## 1b. 全量名單同步流程(由 Member Center 主動推送)
目的:避免 Send Engine 透過 API 拉取名單,降低名單外流風險。
流程:
1. Member Center 內部管理介面或排程觸發「名單重建」。
2. Member Center 以 webhook 推送全量名單或分批名單事件(推薦分批)。
3. Send Engine 驗證來源與 tenant scope。
4. 事件寫入 InboxConsumer 以批次方式重建 List Store。
5. 重建完成後標記同步版本sync_version
注意事項:
- 建議以批次/游標推送,避免單筆 payload 過大
- 可於 Send Engine 端提供 `sync_received` 回應與進度回報
## 2. 發送排程流程
目的:從租戶網站送入的內容建立 Send Job切分送信任務並控速。
流程:
1. 租戶網站以 Member Center 簽發的 token 呼叫 Send Engine API 建立 Campaign/Send Job
- 必填tenant_id、list_id、內容subject/body/template 其一或組合)
- 選填排程時間、發送窗口、追蹤設定open/click
2. Send Engine 驗證 tenant scope 與內容完整性,建立 Send Job。
- tenant_id 以 token 為準body 的 tenant_id 僅作一致性檢查
- tenant 必須預先存在(建議由 Installer 建立)
- list_id 若不存在Send Engine 會在該 tenant 下自動建立 listplaceholder
3. Scheduler 在排程時間點啟動 Send Job
- 讀取 List Store 快照
- 依規則過濾已退訂、bounced、黑名單
4. 切分成可控批次batch寫入 Outbox。
5. Sender Worker 取出 batch轉成 SES API 請求。
6. 發送時必帶:
- SES Configuration Set
- Message Tags至少含 campaign_id / site_id / list_id
- Newsletter 類型需帶 `List-Unsubscribe``List-Unsubscribe-Post` headers
7. SES 回應 message_id → 記錄 delivery log。
8. 更新 Send Job 進度(成功/失敗/重試)。
控速策略(範例):
- 全域 TPS 上限 + tenant TPS 上限
- SES provider-specific rate limit
- 超限則延後批次並回寫排程
內容管理注意事項:
- Send Engine 不負責內容產生與編輯,僅接收已渲染或可渲染內容
- 若採模板渲染,模板需由租戶網站提供並由 Send Engine 以 tenant scope 渲染
## 3. 退信處理流程
目的:處理 ESP 回報的 bounce/complaint並回寫本地名單狀態。
流程:
1. SES 事件由 Configuration Set 發送至 SNS再落到 SQS。
2. ECS Worker 輪詢 SQS解析 SNS envelope 與 SES payload。
3. 將事件寫入 Inboxappend-only
4. Consumer 解析事件:
- hard bounce → 立即標記 blacklisted同義於 `suppressed`
- soft bounce → 累計次數,達門檻(預設 5才標記 blacklisted`suppressed`
- complaint → 立即取消訂閱並標記 blacklisted`suppressed`
- suppression 事件 → 直接對應為 `suppressed`(即黑名單)
5. 更新 List Store 快照與投遞記錄。
6. 回寫 Member Center僅在以下條件
- hard bounce已設黑名單
- soft bounce達門檻後設黑名單
- complaint取消訂閱
- suppression設黑名單
補充:
- Unknown event 不應使 worker crash應記錄後送入 DLQ
- Throttle/暫時性網路錯誤使用指數退避重試
回寫規則:
- Send Engine 僅回寫「停用原因」與必要欄位
- Member Center 需提供可標註來源的欄位(例如 `disabled_by=send_engine`
- 回寫原因碼固定為:
- `hard_bounce`
- `soft_bounce_threshold`
- `complaint`
- `suppression`
名詞定義:
- `blacklisted``suppressed` 同義,表示此收件者不可再發送
資料一致性:
- 任何狀態改變需保留歷史append-only events + current snapshot
- 避免以 ESP 回報反向新增不存在的訂閱者(僅更新已存在者)
多租戶安全性補充:
- 所有事件與 Send Job 必須攜帶 tenant_id且不可跨租戶讀寫
- API 僅允許 tenant scope 的操作list/read/write