mass_mail_engine/docs/OPENAPI.md

7.7 KiB
Raw Blame History

OpenAPI Notes

本文件描述 Send Engine 對外 API、Webhook 驗證與與 Member Center 的介接規則。 目標是讓 Member Center 與租戶網站可以清楚交換資料與責任邊界。

Auth 與驗證

1. 租戶網站 → Send Engine API

使用 OAuth2 Client Credentials 或 JWT由 Member Center 簽發)。

必要 claims

  • tenant_id
  • scope(至少 newsletter:send.write

規則:

  • tenant_id 只能取自 token不接受 body 覆寫
  • list_id 必須屬於該 tenant

2. Member Center → Send Engine Webhook

使用簽名 WebhookHMAC或 OAuth2 Client Credentials建議簽名

Header 建議:

  • X-Signature: hex(hmac_sha256(secret, body))
  • X-Timestamp: Unix epoch seconds
  • X-Nonce: UUID
  • X-Client-Id: Auth client UUID對應 auth_clients.id

驗證規則:

  • timestamp 在允許時間窗內(例如 ±5 分鐘)
  • nonce 不可重複(重放防護)
  • signature 必須匹配
  • client 必須存在且為 active
  • auth_clients.tenant_id 必須與 payload tenant_id 一致
  • 不使用任何 X-Client-Id fallback缺少 tenant 對應 client 時應由來源端跳過發送
  • 預設拒絕 auth_clients.tenant_id = NULL 的通用 client可透過 Webhook__AllowNullTenantClient=true 明確開啟)

3. Send Engine → Member Center 回寫

使用 OAuth2 Client CredentialsSend Engine 作為 client

scope 最小化:

  • newsletter:events.write(停用回寫)
  • newsletter:list.read(若未來仍需查詢)

實作約定:

  • 優先走 token endpointclient credentials
  • ApiToken 僅作暫時 fallback建議逐步移除

通用欄位

Timestamp

  • 欄位:occurred_at
  • 格式RFC3339例如 2026-02-10T09:30:00Z

ID

  • tenant_idlist_idsubscriber_idevent_id 皆為 UUID

通用錯誤格式

{
  "error": "string_code",
  "message": "human readable message",
  "request_id": "uuid"
}

WebhookMember Center → Send Engine

A. 訂閱事件(增量)

用途:同步新增/取消/偏好變更。

Endpoint

  • POST /webhooks/subscriptions

Scope

  • newsletter:events.write

事件型別:

  • subscription.activated
  • subscription.unsubscribed
  • preferences.updated

Request Body示意

{
  "event_id": "uuid",
  "event_type": "subscription.activated",
  "tenant_id": "uuid",
  "list_id": "uuid",
  "subscriber": {
    "id": "uuid",
    "email": "user@example.com",
    "status": "active",
    "preferences": { "topic": "news" }
  },
  "occurred_at": "2026-02-10T09:30:00Z"
}

Response

  • 200 OKaccepted
  • 401/403:驗證失敗
  • 409event_id 重複或 nonce 重放
  • 422:格式錯誤

B. 全量名單同步

用途:由 Member Center 主動推送全量或分批名單,避免 Send Engine 拉取名單。

Endpoint

  • POST /webhooks/lists/full-sync

Scope

  • newsletter:events.write

Request Body分批示意

{
  "sync_id": "uuid",
  "batch_no": 1,
  "batch_total": 10,
  "tenant_id": "uuid",
  "list_id": "uuid",
  "subscribers": [
    { "id": "uuid", "email": "a@example.com", "status": "active" },
    { "id": "uuid", "email": "b@example.com", "status": "unsubscribed" }
  ],
  "occurred_at": "2026-02-10T09:30:00Z"
}

Response

  • 200 OKaccepted
  • 401/403:驗證失敗
  • 409sync_id + batch_no 重複
  • 422:格式錯誤

API租戶網站 → Send Engine

C. 建立 Send Job

用途:租戶網站送入內容,建立 Campaign/Send Job 並排程。

Endpoint

  • POST /api/send-jobs

Scope

  • newsletter:send.write

Request Body

{
  "tenant_id": "uuid",
  "list_id": "uuid",
  "name": "Weekly Update",
  "subject": "Weekly Update",
  "body_html": "<p>Hello</p>",
  "body_text": "Hello",
  "template": null,
  "scheduled_at": "2026-02-11T02:00:00Z",
  "window_start": "2026-02-11T02:00:00Z",
  "window_end": "2026-02-11T05:00:00Z",
  "tracking": { "open": true, "click": false }
}

欄位規則:

  • subject 必填,最小長度 1
  • body_html / body_text / template 至少擇一
  • window_start 必須小於 window_end(若有提供)

Response

{
  "send_job_id": "uuid",
  "status": "pending"
}

C-1. Sending Proxy Submit Job整合規格

用途:對齊內容網站/會員平台呼叫發信代理的標準接口。

Endpoint

  • POST /v1/send-jobs

Request Body欄位

  • message_typenewsletter | transactional
  • from:發件人
  • to:收件人陣列
  • subject:主旨
  • htmlHTML 內容
  • text:純文字內容
  • headers:自定義 header白名單
  • list_unsubscribe.url:退訂 URL
  • list_unsubscribe.mailto:可選
  • tags.campaign_id / tags.site_id / tags.list_id / tags.segment
  • idempotency_key:冪等鍵

Response

  • job_id
  • status=queued

規則:

  • 必須帶 Configuration Set + Message Tags 後才能呼叫 SES
  • newsletter 類型需帶:
  • List-Unsubscribe
  • List-Unsubscribe-Post: List-Unsubscribe=One-Click

D. 查詢 Send Job

Endpoint

  • GET /api/send-jobs/{id}

Scope

  • newsletter:send.read

Response

{
  "id": "uuid",
  "tenant_id": "uuid",
  "list_id": "uuid",
  "campaign_id": "uuid",
  "status": "running",
  "scheduled_at": "2026-02-11T02:00:00Z",
  "window_start": "2026-02-11T02:00:00Z",
  "window_end": "2026-02-11T05:00:00Z"
}

E. 取消 Send Job

Endpoint

  • POST /api/send-jobs/{id}/cancel

Scope

  • newsletter:send.write

Response

{
  "id": "uuid",
  "status": "cancelled"
}

WebhookSES → Send Engine

F. SES 事件回報

用途:接收 bounce/hard_bounced/soft_bounced/complaint/suppression/delivery/open/click 等事件。

推薦架構(正式):

  • SES Configuration Set -> SNS -> SQS -> ECS Worker
  • 由 Worker 消費事件,不要求對外公開 webhook

相容模式(可選):

  • POST /webhooks/ses

驗證:

  • 依 SES/SNS 規格驗簽(可用 Ses__SkipSignatureValidation=true 暫時略過)

Request Body示意

{
  "event_type": "bounce",
  "message_id": "ses-id",
  "tenant_id": "uuid",
  "email": "user@example.com",
  "bounce_type": "hard",
  "occurred_at": "2026-02-10T09:45:00Z"
}

Response

  • 200 OK

事件對應規則(固定):

  • hard_bounced:立即設為黑名單(suppressed
  • soft_bounced:累計達門檻後設為黑名單(suppressed
  • complaint:取消訂閱並回寫 Member Center
  • suppression:設為黑名單(suppressed

回寫 Member Center 條件:

  • hard_bounced:設黑名單後回寫
  • soft_bounced:達門檻設黑名單後回寫
  • complaint:立即回寫
  • suppression:設黑名單後回寫

回寫原因碼(固定):

  • hard_bounce
  • soft_bounce_threshold
  • complaint
  • suppression

外部 APISend Engine → Member Center

以下為 Member Center 端提供的 API非 Send Engine 的 OpenAPI 規格範圍。

G. 停用訂閱回寫

用途:因 hard bounce / complaint 停用訂閱,並在 Member Center 註記來源。

EndpointMember Center 提供):

  • POST /subscriptions/disable

Scope

  • newsletter:events.write

Request Body示意

{
  "tenant_id": "uuid",
  "subscriber_id": "uuid",
  "list_id": "uuid",
  "reason": "hard_bounce",
  "disabled_by": "send_engine",
  "occurred_at": "2026-02-10T09:45:00Z"
}

狀態碼與錯誤

通用錯誤:

  • 401/403Auth 或 scope 不符
  • 409重放或事件重複nonce / event_id
  • 422:資料格式錯誤
  • 500:伺服器內部錯誤

Retry 策略(整合規格)

  • Throttle指數退避重試
  • Temporary network error重試
  • Hard failure不重試
  • Retry 上限可設定(例如 5 次)

相關環境參數

  • Bounce__SoftBounceThresholdsoft bounce 轉黑名單門檻(預設 5