# Notification 模組

統一對外通知入口（Email / SMS），支援同步 `Send`、異步 `Enqueue` + Redis 重試 Worker、冪等、配額、DLQ。

> 規格書（Data Dictionary `notifications` / `notification_dlq`、NotifyKind 一覽、API）→ [`SDD.md`](./SDD.md)

---

## 測試（本機）

單一 CLI：`cmd/notify-test`，Makefile 包一層：

```bash
make deps-up          # Mongo + Redis（異步必備）
make mongo-index      # 建索引
make notify-test METHOD=<名稱> [TO=] [PHONE=] [MOCK=1]
```

完整 METHOD 表見 [`docs/notification-testing.md`](../../../docs/notification-testing.md)。

### 同步 vs 異步

| 方式 | UseCase | 何時完成 | 需要 Redis |
|------|---------|----------|:----------:|
| **同步** | `Notifier.Send` | 呼叫當下送完（或失敗） | 建議（冪等／配額） |
| **異步** | `Notifier.Enqueue` | 先寫 Mongo `pending`，由 **RetryWorker** 從 Redis 佇列取出再送 | **必須** |

異步流程：

```mermaid
sequenceDiagram
  participant App
  participant Mongo
  participant Redis
  participant Worker
  participant Provider

  App->>Mongo: Insert status=pending
  App->>Redis: ZADD 排程 job（立即或退避）
  App-->>App: 回傳 pending DTO
  Worker->>Redis: ClaimDue 到期 job
  Worker->>Provider: Send 渲染後內容
  Worker->>Mongo: Update status=sent / failed / retrying
  Note over Worker,Mongo: 超過 MaxRetry → notification_dlq
```

### 測異步（Enqueue）

**前置：** `etc/gateway.dev.yaml` 要有 `Mongo`、`Redis`，且 `Notification.Async` 可維持預設（`Worker` ≥ 1）。

```bash
# Email 異步（歡迎信模板 tenant_welcome）
make notify-test METHOD=email-enqueue TO=you@example.com

# SMS 異步
make notify-test METHOD=sms-enqueue PHONE=0912345678

# mock：log 會印 [notification mock email/sms] 寄了什麼
make notify-test METHOD=email-enqueue TO=test@example.com MOCK=1
```

`notify-test` 在 `email-enqueue` / `sms-enqueue` 時會**暫時啟動** process 內 RetryWorker（與 `make run-dev` 相同邏輯），輪詢 Redis 佇列直到 Mongo 紀錄變 `sent` 或逾時（預設 45 秒，`-poll` 可改）。

**成功時輸出範例：**

```text
method=email-enqueue email=mock sms=mock
notification_id=... provider=mock status=sent
OK
```

**失敗常見原因：**

- 沒開 Redis → `async notification requires redis retry queue`
- Worker 沒跑、佇列卡住 → `timeout after 45s`
- `Email.From` 空白 → 啟動即失敗

**關於 log：** 若看到 `FindOne - fail(mongo: no documents in result)` 且 filter 含 `idempotency_key`，代表「第一次用這個 key 發送」，屬冪等查詢的正常結果，不是寄信失敗。新版 `FindByIdempotency` 已改用 `Find` 避免此誤導性 error log。

### 用 Gateway 長期跑 Worker（接近正式環境）

異步在 production 由 **同一個 Gateway process** 背景跑 Worker，不是只靠 CLI：

```bash
make run-dev   # ServiceContext.StartWorkers → NotificationRetry
```

此時由 API／其他 use case 呼叫 `Enqueue` 即可；本機若尚無 Notification HTTP，仍用 `notify-test METHOD=email-enqueue` 觸發。

### 查 Mongo 確認異步結果

```bash
mongosh gateway --eval '
  db.notifications.find({ tenant_id: "notify-test" })
    .sort({ occurred_at: -1 })
    .limit(3)
    .forEach(d => print(d.kind, d.status, d.provider, d.attempts))
'
```

| `status` | 意義 |
|----------|------|
| `pending` | 已 Enqueue，Worker 尚未送完 |
| `sent` | 已送達 |
| `retrying` | 失敗、等待退避重試 |
| `failed` / `dropped` | 放棄或進 DLQ |

DLQ：

```bash
make notify-test METHOD=admin-dlq
```

### Redis 佇列 key

預設 zset：`notif:retry:zset`（可由 `Notification.Async.QueueRedisKey` 覆寫，example 為 `notification:queue`）。

### 同步對照（順便測）

```bash
make notify-test METHOD=email-send TO=you@example.com MOCK=1
make notify-test METHOD=sms-send PHONE=0912345678 MOCK=1
```

---

## 設定摘要（`gateway.dev.yaml`）

```yaml
Notification:
  Async:
    QueueRedisKey: notification:queue  # 可省略，預設 notif:retry:zset
    Worker: 2
    MaxRetry: 5
    BackoffSeconds: [1, 5, 30, 300, 1800]
  Email:
    From: noreply@example.com   # 必填
    SMTP:
      Enable: true              # 真實 SMTP；MOCK=1 時 CLI 會關閉
```

Email provider 看 `SMTP.Enable` / `SES.Enable`，不是 `Provider: smtp` 字串。

---

## 擴充指南

### 新增 Email Provider

已內建：`smtp_sender.go`、`ses_sender.go`。

1. 在 `provider/email/` 實作 `Sender`（`Name`、`Sort`、`Send`）。
2. 在 `config/config.go` 加設定，並在 `usecase/factory.go` 的 `collectEmailSenders` 註冊。
3. 補 `provider/email/*_test.go`。

### 新增 NotifyKind

1. `domain/enum/kind.go` 常數。
2. embed 模板 + `template/registry.go`。
3. OTP 類業務呼叫時設 `DoNotPersistBody: true`。

### 新增 HTTP API

1. 在 `generate/api/` 定義路由。
2. `make gen-api`。
3. `internal/logic` 只呼叫 `domain/usecase`，做 types ↔ DTO 映射。

---

## 相關文件

- [`docs/notification-testing.md`](../../../docs/notification-testing.md) — `notify-test` METHOD 速查
- [`docs/model.md`](../../../docs/model.md) — domain 分包、Redis/Mongo 生命週期
- [`docs/identity-member-design.md`](../../../docs/identity-member-design.md) §8 — 在跨模組流程中的角色
- [`etc/README.md`](../../../etc/README.md) — Gateway 設定
- [`internal/library/redis/README.md`](../../library/redis/README.md)
- [`internal/library/mongo/README.md`](../../library/mongo/README.md)
