# Notification Service
# Notification Service — SRS/SDD Document
| Version | Version Date | Editor | Memo |
|---------|--------------|--------|------|
| 1.0.0 | 2026/05/21 | Gateway Team | 初版:Gateway Notification 模組 SDD |
---
## 1. Introduction
### 1.1 Purpose
Notification 模組為 TianTing Gateway 的**統一對外通知入口**,支援 Email 與 SMS 的同步寄送、異步佇列、冪等、租戶配額、指數退避重試與 DLQ(Dead Letter Queue)管理。
### 1.2 Scope
**範圍內:**
- 同步 `Send`:渲染模板後立即送達
- 異步 `Enqueue`:Mongo 寫入 pending + Redis ZSET 排程 + RetryWorker 背景投遞
- 冪等(Redis cache + Mongo unique index)
- 租戶每日 Email/SMS 配額
- 模板渲染(embed HTML / SMS / Subject,多語系)
- Provider chain(SMTP、SES、Mitake、Mock)
- Admin DLQ 查詢與手動重試
**範圍外:**
- Push / Webhook(enum 已定義,尚未實作)
- 專用 HTTP API(目前僅內部 logic 呼叫;擴充路徑見 README)
### 1.3 Definitions, Acronyms, and Abbreviation
| 縮寫 | 說明 |
|------|------|
| **NotifyKind** | 通知類型(verify_email、tenant_welcome 等) |
| **Channel** | 通道:email / sms / push / webhook |
| **DLQ** | Dead Letter Queue,超過 MaxRetry 的失敗通知 |
| **IdempotencyKey** | 冪等鍵,同 tenant+kind+key 不重複寄送 |
| **TargetHash** | SHA-256(target),Mongo 不存明文 email/phone |
| **RetryJob** | Redis ZSET 成員,含 target(僅 Redis,不落 Mongo) |
### 1.4 Technologies to be used
| 項目 | 技術 |
|------|------|
| Application Language | Go 1.22+ |
| Framework | go-zero |
| Database | MongoDB(notifications、notification_dlq) |
| Cache / Queue | Redis(冪等、配額、retry ZSET) |
| Templates | go:embed + Go text/html template |
| Email Providers | SMTP、AWS SES、Mock |
| SMS Providers | Mitake(三竹)、Mock |
### 1.5 Overview
Notification 為 library-style domain package,嵌入 Gateway process 內執行:
1. **Send**:Caller → 冪等檢查 → 配額 → Insert pending → Render → Provider chain → Update sent/failed
2. **Enqueue**:同上至 Insert → ZADD Redis → 回傳 pending;RetryWorker 背景 ClaimDue → 投遞
3. **DLQ**:attempts ≥ MaxRetry → status=dropped + 寫入 notification_dlq
---
## 2. System Overview
Notification 模組解耦業務邏輯與實際寄送:
- Auth 註冊 OTP、Member 業務驗證等 logic 層呼叫 `Notifier.Send`
- 歡迎信等可容忍延遲的通知可走 `Enqueue` + Worker
- 隱私設計:Mongo / DLQ 只存 `target_hash`,明文 target 僅在 Redis RetryJob 生命週期內存在
Worker 由 `ServiceContext.StartWorkers` 啟動,與 Gateway 同 process 運行。
---
## 3. System Architecture
### 3.1 System Architecture
```mermaid
flowchart TB
subgraph Callers["Internal Callers"]
AuthLogic[logic/auth]
MemberLogic[logic/member]
end
subgraph NotificationModule["internal/model/notification"]
Notifier[NotifierUseCase]
Admin[AdminNotifierUseCase]
Worker[RetryWorker]
Factory[Provider Factory]
Renderer[Template Renderer]
end
subgraph Providers["Providers"]
EmailChain[email.Chain
SMTP / SES / Mock]
SMSChain[sms.Chain
Mitake / Mock]
end
subgraph Storage["Storage"]
Mongo[(MongoDB)]
Redis[(Redis)]
end
AuthLogic --> Notifier
MemberLogic --> Notifier
Notifier --> Renderer
Notifier --> Factory
Notifier --> Mongo
Notifier --> Redis
Factory --> EmailChain
Factory --> SMSChain
Worker --> Redis
Worker --> Mongo
Worker --> EmailChain
Worker --> SMSChain
Admin --> Mongo
Admin --> Redis
```
### 3.2 Decomposition Description
| 套件 | 職責 |
|------|------|
| `config/` | Async、Rate、Email、SMS 設定 |
| `domain/entity/` | Notification、NotificationDLQ |
| `domain/enum/` | Channel、NotifyKind、NotifyStatus、Severity |
| `domain/repository/` | Notification、DLQ、Store、RetryQueue 介面 |
| `domain/usecase/` | Notifier、Admin 介面 + DTO |
| `domain/template/` | 模板 Spec、Renderer、Registry 介面 |
| `repository/` | Mongo + Redis 實作 |
| `usecase/` | Notifier、Admin、RetryWorker、factory、module |
| `provider/email/` | SMTP、SES、Mock sender + chain |
| `provider/sms/` | Mitake、Mock sender + chain |
| `template/` | embed 模板 + render |
### 3.3 Send(同步)
```mermaid
sequenceDiagram
participant C as Caller
participant N as NotifierUseCase
participant R as Redis
participant M as Mongo
participant P as Provider
C->>N: Send(SendRequest)
N->>R: Get idempotency
alt cache hit
N-->>C: cached DTO
else miss
N->>M: FindByIdempotency / Insert pending
N->>R: Incr quota
N->>N: Render template
N->>P: Email/SMS chain.Send
N->>M: UpdateDelivery (sent/failed)
N->>R: Set idempotency cache
N-->>C: NotificationDTO
end
```
### 3.4 Enqueue + RetryWorker(異步)
```mermaid
sequenceDiagram
participant C as Caller
participant N as NotifierUseCase
participant M as Mongo
participant R as Redis
participant W as RetryWorker
participant P as Provider
C->>N: Enqueue(SendRequest)
N->>M: Insert (pending)
N->>R: ZADD RetryJob (score=now)
N-->>C: pending DTO
loop every 1s
W->>R: ClaimDue (ZREM)
W->>M: FindByID
W->>P: Render + deliver
alt success
W->>M: status=sent
else attempts < MaxRetry
W->>M: status=retrying
W->>R: ZADD (score=now+backoff)
else
W->>M: status=dropped
W->>M: Insert DLQ
end
end
```
**退避序列(預設):** `[1, 5, 30, 300, 1800]` 秒
---
## 4. Data Design
### 4.1 Data Dictionary
#### notifications(MongoDB)
| Field | Type | Comment | Index |
|-------|------|---------|-------|
| _id | ObjectId | PK | PK |
| tenant_id | String | 租戶 ID | Unique(tenant_id, kind, idempotency_key) |
| uid | String | 會員 UID | (tenant_id, uid, occurred_at desc) |
| channel | String | email / sms | — |
| kind | String | NotifyKind | Unique(tenant_id, kind, idempotency_key) |
| target_hash | String | SHA-256(target) | — |
| template_key | String | 模板 key | — |
| locale | String | 語系 | — |
| body | String | 渲染後內容(OTP 類可省略) | — |
| provider | String | 實際使用的 provider | — |
| provider_message_id | String | 外部 message ID | — |
| status | String | pending/sent/failed/retrying/dropped | (status, attempts, occurred_at) |
| attempts | Int | 投遞嘗試次數 | — |
| last_error | String | 最後錯誤 | — |
| idempotency_key | String | 冪等鍵 | Unique(tenant_id, kind, idempotency_key) |
| severity | String | info/warn/critical | — |
| occurred_at | Int64 | 事件時間 ms | — |
| delivered_at | Int64 | 送達時間 ms | — |
| create_at | Int64 | 建立時間 ms | — |
| update_at | Int64 | 更新時間 ms | — |
#### notification_dlq(MongoDB)
| Field | Type | Comment | Index |
|-------|------|---------|-------|
| _id | ObjectId | PK | PK |
| notification_id | String | 原 notification ID | — |
| tenant_id | String | 租戶 ID | — |
| uid | String | 會員 UID | — |
| channel | String | 通道 | — |
| kind | String | 通知類型 | — |
| target_hash | String | target hash | — |
| last_error | String | 最後錯誤 | — |
| attempts | Int | 總嘗試次數 | — |
| payload | Object | 重試 metadata(locale、data 等,不含 target) | — |
| occurred_at | Int64 | 事件時間 ms | — |
| create_at | Int64 | 建立時間 ms | — |
#### Redis Keys
| Key Pattern | 用途 | TTL |
|-------------|------|-----|
| notif:idem:{tenant}:{kind}:{key} | 冪等 cache | 24h |
| notif:quota:{tenant}:{channel}:{yyyyMMdd} | 每日配額計數 | 25h |
| notif:retry:zset(可配置) | RetryJob ZSET(score=run_at_ms) | — |
#### NotifyKind 一覽
| Kind | 通道 | 說明 |
|------|------|------|
| verify_email | email | 業務 email OTP |
| verify_phone | sms | 業務 phone OTP |
| verify_registration_email | email | 註冊 email OTP |
| step_up_email | email | Email step-up |
| step_up_phone | sms | Phone step-up |
| account_suspended | email | 帳號停權通知 |
| tenant_welcome | email | 租戶歡迎信 |
---
## 5. API Design
**現況:** 尚無專用 HTTP API。Notification 由內部 logic 呼叫:
| 呼叫方 | 方法 | 用途 |
|--------|------|------|
| `logic/auth/register_logic.go` | `Notifier.Send` | 註冊 email OTP |
| `logic/member/verify_helper.go` | `Notifier.Send` | 業務 email/phone OTP |
| `cmd/notify-test` | Send / Enqueue / Admin | 本機測試 CLI |
**擴充 HTTP API 路徑:**
1. 在 `generate/api/` 定義路由
2. `make gen-api`
3. `internal/logic` 映射 types ↔ DTO
---
## 6. Resource
| 資源 | 路徑 |
|------|------|
| 模組原始碼 | `internal/model/notification/` |
| 開發 README | `internal/model/notification/README.md` |
| 測試指南 | `docs/notification-testing.md` |
| 測試 CLI | `cmd/notify-test`(`make notify-test`) |
| Worker | `internal/worker/notification_retry/` |
| 設定範例 | `etc/gateway.dev.example.yaml` → `Notification` 區塊 |
| 設計參考 | `docs/identity-member-design.md` §11 |