@h-ai/reach 0.1.0-alpha.55 → 0.1.0-alpha.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +175 -0
- package/README.ja.md +175 -0
- package/README.md +4 -0
- package/package.json +4 -4
package/README.en.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @h-ai/reach
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[Framework home](../../README.en.md) · Related modules: [@h-ai/cache](../cache/README.en.md) · [@h-ai/core](../core/README.en.md) · [@h-ai/reldb](../reldb/README.en.md)
|
|
6
|
+
|
|
7
|
+
A notification module supporting multiple providers (email, SMS, API callbacks) at once. Templates bind to providers for scenario-based routing. Includes do-not-disturb (DND) policies and configuration-defined templates.
|
|
8
|
+
|
|
9
|
+
## Dependencies
|
|
10
|
+
|
|
11
|
+
- `@h-ai/reldb`: optional persistence for delivery logs and templates; enabled automatically when initialized.
|
|
12
|
+
- `@h-ai/cache`: optional distributed locks for DND delay; enabled automatically when initialized.
|
|
13
|
+
|
|
14
|
+
## Supported providers
|
|
15
|
+
|
|
16
|
+
| Provider | Channel | Description |
|
|
17
|
+
| ------------ | ------- | -------------------------------------------------- |
|
|
18
|
+
| `console` | General | Console output (development/tests) |
|
|
19
|
+
| `smtp` | Email | SMTP email (requires nodemailer) |
|
|
20
|
+
| `aliyun-sms` | SMS | Alibaba Cloud SMS through HTTP API, without an SDK |
|
|
21
|
+
| `api` | General | HTTP API callback (generic webhook) |
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
### Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add @h-ai/reach
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Install `nodemailer` for email delivery.
|
|
32
|
+
|
|
33
|
+
### Initialize multiple providers
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { cache } from '@h-ai/cache'
|
|
37
|
+
import { reach } from '@h-ai/reach'
|
|
38
|
+
import { reldb } from '@h-ai/reldb'
|
|
39
|
+
|
|
40
|
+
// 1. 初始化可选依赖(按需)
|
|
41
|
+
await reldb.init({ type: 'sqlite', database: './data.db' }) // 可选,启用发送日志与模板持久化
|
|
42
|
+
await cache.init({ type: 'memory' }) // 可选,启用 DND delay 策略分布式锁
|
|
43
|
+
|
|
44
|
+
// 2. 初始化触达模块(自动检测已初始化的 reldb/cache 单例)
|
|
45
|
+
await reach.init({
|
|
46
|
+
providers: [
|
|
47
|
+
{ name: 'email', type: 'smtp', host: 'smtp.example.com', from: 'noreply@example.com' },
|
|
48
|
+
{ name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '某某科技' },
|
|
49
|
+
{ name: 'webhook', type: 'api', url: 'https://api.example.com/notify' },
|
|
50
|
+
],
|
|
51
|
+
templates: [
|
|
52
|
+
{ name: 'welcome_email', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎!' },
|
|
53
|
+
{ name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
|
|
54
|
+
],
|
|
55
|
+
dnd: { enabled: true, strategy: 'delay', start: '22:00', end: '08:00' },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// 通过 provider 字段选择发送渠道
|
|
59
|
+
await reach.send({
|
|
60
|
+
provider: 'email',
|
|
61
|
+
to: 'user@example.com',
|
|
62
|
+
template: 'welcome_email',
|
|
63
|
+
vars: { userName: '张三' },
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// 发送短信(通过 extra 传递 Provider 特有参数)
|
|
67
|
+
await reach.send({
|
|
68
|
+
provider: 'sms',
|
|
69
|
+
to: '13800138000',
|
|
70
|
+
extra: { templateCode: 'SMS_123456' },
|
|
71
|
+
vars: { code: '123456' },
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// 关闭所有 Provider
|
|
75
|
+
await reach.close()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`reach.config` returns a **redacted configuration snapshot**. Embedded credentials in `api.url` / `endpoint`, `headers.Authorization`, SMTP `pass`, SMS keys, and other secrets are hidden automatically.
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
### Console (development/tests)
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const config = { name: 'dev', type: 'console' }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### SMTP
|
|
89
|
+
|
|
90
|
+
| Field | Type | Required | Default | Description |
|
|
91
|
+
| -------- | --------- | -------- | ------- | ----------------------- |
|
|
92
|
+
| `name` | `string` | ✅ | — | Provider name |
|
|
93
|
+
| `type` | `string` | ✅ | — | Fixed `'smtp'` |
|
|
94
|
+
| `host` | `string` | ✅ | — | SMTP server address |
|
|
95
|
+
| `port` | `number` | — | `465` | SMTP port |
|
|
96
|
+
| `secure` | `boolean` | — | `true` | Whether to use TLS |
|
|
97
|
+
| `user` | `string` | — | — | Authentication username |
|
|
98
|
+
| `pass` | `string` | — | — | Authentication password |
|
|
99
|
+
| `from` | `string` | ✅ | — | Sender address |
|
|
100
|
+
|
|
101
|
+
### Alibaba Cloud SMS
|
|
102
|
+
|
|
103
|
+
| Field | Type | Required | Default | Description |
|
|
104
|
+
| ----------------- | -------- | -------- | ----------------------- | -------------------- |
|
|
105
|
+
| `name` | `string` | ✅ | — | Provider name |
|
|
106
|
+
| `type` | `string` | ✅ | — | Fixed `'aliyun-sms'` |
|
|
107
|
+
| `accessKeyId` | `string` | ✅ | — | AccessKey ID |
|
|
108
|
+
| `accessKeySecret` | `string` | ✅ | — | AccessKey Secret |
|
|
109
|
+
| `signName` | `string` | ✅ | — | SMS signature |
|
|
110
|
+
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | API endpoint |
|
|
111
|
+
|
|
112
|
+
### API callbacks
|
|
113
|
+
|
|
114
|
+
| Field | Type | Required | Default | Description |
|
|
115
|
+
| --------- | ------------------------ | -------- | ------- | ----------------------- |
|
|
116
|
+
| `name` | `string` | ✅ | — | Provider name |
|
|
117
|
+
| `type` | `string` | ✅ | — | Fixed `'api'` |
|
|
118
|
+
| `url` | `string` | ✅ | — | HTTP(S) callback URL |
|
|
119
|
+
| `method` | `'POST' \| 'PUT'` | — | `POST` | HTTP method |
|
|
120
|
+
| `headers` | `Record<string, string>` | — | — | Custom request headers |
|
|
121
|
+
| `timeout` | `number` | — | `10000` | Timeout in milliseconds |
|
|
122
|
+
|
|
123
|
+
API callback addresses are trusted server configuration. Logs contain only the callback origin, not URL credentials, query parameters, recipients, or third-party failure bodies. For tenant/user-supplied addresses, applications must enforce a domain allowlist; enterprise deployments should also block internal metadata access through egress proxies or network policies.
|
|
124
|
+
|
|
125
|
+
### DND (do not disturb)
|
|
126
|
+
|
|
127
|
+
| Field | Type | Required | Default | Description |
|
|
128
|
+
| ---------- | ---------------------- | -------- | --------- | ------------------------------------- |
|
|
129
|
+
| `enabled` | `boolean` | — | `false` | Enable DND |
|
|
130
|
+
| `strategy` | `'discard' \| 'delay'` | — | `discard` | discard: drop / delay: send after DND |
|
|
131
|
+
| `start` | `string` | — | `00:00` | Start time (HH:mm) |
|
|
132
|
+
| `end` | `string` | — | `00:00` | End time (HH:mm) |
|
|
133
|
+
|
|
134
|
+
## Error handling
|
|
135
|
+
|
|
136
|
+
All operations return `HaiResult<T>`. Error codes are defined in `HaiReachError`:
|
|
137
|
+
|
|
138
|
+
| Error code | code | Description |
|
|
139
|
+
| -------------------------------------- | --------------- | ------------------------- |
|
|
140
|
+
| `HaiReachError.SEND_FAILED` | `hai:reach:001` | Delivery failed |
|
|
141
|
+
| `HaiReachError.TEMPLATE_NOT_FOUND` | `hai:reach:002` | Template not found |
|
|
142
|
+
| `HaiReachError.TEMPLATE_RENDER_FAILED` | `hai:reach:003` | Template rendering failed |
|
|
143
|
+
| `HaiReachError.INVALID_RECIPIENT` | `hai:reach:004` | Invalid recipient |
|
|
144
|
+
| `HaiReachError.PROVIDER_NOT_FOUND` | `hai:reach:005` | Provider not registered |
|
|
145
|
+
| `HaiReachError.DND_BLOCKED` | `hai:reach:006` | Dropped by DND |
|
|
146
|
+
| `HaiReachError.DND_DEFERRED` | `hai:reach:007` | Queued for DND delay |
|
|
147
|
+
| `HaiReachError.NOT_INITIALIZED` | `hai:reach:010` | Not initialized |
|
|
148
|
+
| `HaiReachError.UNSUPPORTED_TYPE` | `hai:reach:011` | Unsupported type |
|
|
149
|
+
| `HaiReachError.CONFIG_ERROR` | `hai:reach:012` | Invalid configuration |
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const result = await reach.send({ provider: 'email', to: 'user@example.com', body: 'hello' })
|
|
153
|
+
if (!result.success) {
|
|
154
|
+
// result.error.code / result.error.message
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Distributed locks
|
|
159
|
+
|
|
160
|
+
Across nodes, flushing pending DND-delay messages is protected by `@h-ai/cache` distributed locks, allowing only one node to flush at a time.
|
|
161
|
+
|
|
162
|
+
- Uses `cache.lock.acquire('reach:flush-pending')` with a 300-second TTL.
|
|
163
|
+
- Claims pending records in batches. In-progress records expire, allowing another node to take over after failure and reducing duplicate delivery risk.
|
|
164
|
+
- Without initialized `@h-ai/cache`, distributed locking is disabled automatically; single-node operation continues.
|
|
165
|
+
- A stable process-level owner identifier prevents releasing another owner's lock.
|
|
166
|
+
|
|
167
|
+
## Tests
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pnpm --filter @h-ai/reach test
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
Apache-2.0
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @h-ai/reach
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/cache](../cache/README.ja.md) · [@h-ai/core](../core/README.ja.md) · [@h-ai/reldb](../reldb/README.ja.md)
|
|
6
|
+
|
|
7
|
+
メール・SMS・API 通知など複数の Provider を同時登録できる通知モジュールです。テンプレートと Provider の対応で用途に応じて配信します。通知抑制(DND)と設定ファイルによるテンプレート定義を備えます。
|
|
8
|
+
|
|
9
|
+
## 依存関係
|
|
10
|
+
|
|
11
|
+
- `@h-ai/reldb`:送信ログ・テンプレートの永続化(任意)。初期化済みなら自動有効化。
|
|
12
|
+
- `@h-ai/cache`:DND delay 用の分散ロック(任意)。初期化済みなら自動有効化。
|
|
13
|
+
|
|
14
|
+
## 対応 Provider
|
|
15
|
+
|
|
16
|
+
| Provider | チャネル | 説明 |
|
|
17
|
+
| ------------ | -------- | -------------------------------------------------- |
|
|
18
|
+
| `console` | 汎用 | コンソール出力(開発・テスト用) |
|
|
19
|
+
| `smtp` | メール | SMTP メール(nodemailer が必要) |
|
|
20
|
+
| `aliyun-sms` | SMS | Alibaba Cloud SMS(HTTP API を直接使用、SDK 不要) |
|
|
21
|
+
| `api` | 汎用 | HTTP API 通知(汎用 webhook) |
|
|
22
|
+
|
|
23
|
+
## クイックスタート
|
|
24
|
+
|
|
25
|
+
### インストール
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add @h-ai/reach
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
メール送信には `nodemailer` をインストールしてください。
|
|
32
|
+
|
|
33
|
+
### 複数 Provider の初期化
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { cache } from '@h-ai/cache'
|
|
37
|
+
import { reach } from '@h-ai/reach'
|
|
38
|
+
import { reldb } from '@h-ai/reldb'
|
|
39
|
+
|
|
40
|
+
// 1. 初始化可选依赖(按需)
|
|
41
|
+
await reldb.init({ type: 'sqlite', database: './data.db' }) // 可选,启用发送日志与模板持久化
|
|
42
|
+
await cache.init({ type: 'memory' }) // 可选,启用 DND delay 策略分布式锁
|
|
43
|
+
|
|
44
|
+
// 2. 初始化触达模块(自动检测已初始化的 reldb/cache 单例)
|
|
45
|
+
await reach.init({
|
|
46
|
+
providers: [
|
|
47
|
+
{ name: 'email', type: 'smtp', host: 'smtp.example.com', from: 'noreply@example.com' },
|
|
48
|
+
{ name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '某某科技' },
|
|
49
|
+
{ name: 'webhook', type: 'api', url: 'https://api.example.com/notify' },
|
|
50
|
+
],
|
|
51
|
+
templates: [
|
|
52
|
+
{ name: 'welcome_email', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎!' },
|
|
53
|
+
{ name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
|
|
54
|
+
],
|
|
55
|
+
dnd: { enabled: true, strategy: 'delay', start: '22:00', end: '08:00' },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// 通过 provider 字段选择发送渠道
|
|
59
|
+
await reach.send({
|
|
60
|
+
provider: 'email',
|
|
61
|
+
to: 'user@example.com',
|
|
62
|
+
template: 'welcome_email',
|
|
63
|
+
vars: { userName: '张三' },
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// 发送短信(通过 extra 传递 Provider 特有参数)
|
|
67
|
+
await reach.send({
|
|
68
|
+
provider: 'sms',
|
|
69
|
+
to: '13800138000',
|
|
70
|
+
extra: { templateCode: 'SMS_123456' },
|
|
71
|
+
vars: { code: '123456' },
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// 关闭所有 Provider
|
|
75
|
+
await reach.close()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`reach.config` は**機密情報をマスクした設定スナップショット**を返します。`api.url` / `endpoint` 内の認証情報、`headers.Authorization`、SMTP の `pass`、SMS の鍵などは自動的に非表示になります。
|
|
79
|
+
|
|
80
|
+
## 設定
|
|
81
|
+
|
|
82
|
+
### Console(開発・テスト用)
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const config = { name: 'dev', type: 'console' }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### SMTP
|
|
89
|
+
|
|
90
|
+
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
91
|
+
| ---------- | --------- | ---- | ------ | ----------------------- |
|
|
92
|
+
| `name` | `string` | ✅ | — | Provider 名 |
|
|
93
|
+
| `type` | `string` | ✅ | — | 固定値 `'smtp'` |
|
|
94
|
+
| `host` | `string` | ✅ | — | SMTP サーバーのアドレス |
|
|
95
|
+
| `port` | `number` | — | `465` | SMTP ポート |
|
|
96
|
+
| `secure` | `boolean` | — | `true` | TLS を使用するか |
|
|
97
|
+
| `user` | `string` | — | — | 認証ユーザー名 |
|
|
98
|
+
| `pass` | `string` | — | — | 認証パスワード |
|
|
99
|
+
| `from` | `string` | ✅ | — | 送信元アドレス |
|
|
100
|
+
|
|
101
|
+
### Alibaba Cloud SMS
|
|
102
|
+
|
|
103
|
+
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
104
|
+
| ----------------- | -------- | ---- | ----------------------- | --------------------- |
|
|
105
|
+
| `name` | `string` | ✅ | — | Provider 名 |
|
|
106
|
+
| `type` | `string` | ✅ | — | 固定値 `'aliyun-sms'` |
|
|
107
|
+
| `accessKeyId` | `string` | ✅ | — | AccessKey ID |
|
|
108
|
+
| `accessKeySecret` | `string` | ✅ | — | AccessKey Secret |
|
|
109
|
+
| `signName` | `string` | ✅ | — | SMS 署名 |
|
|
110
|
+
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | API endpoint |
|
|
111
|
+
|
|
112
|
+
### API 通知
|
|
113
|
+
|
|
114
|
+
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
115
|
+
| ---------- | ------------------------ | ---- | ------- | ------------------------ |
|
|
116
|
+
| `name` | `string` | ✅ | — | Provider 名 |
|
|
117
|
+
| `type` | `string` | ✅ | — | 固定値 `'api'` |
|
|
118
|
+
| `url` | `string` | ✅ | — | HTTP(S) 通知 URL |
|
|
119
|
+
| `method` | `'POST' \| 'PUT'` | — | `POST` | HTTP メソッド |
|
|
120
|
+
| `headers` | `Record<string, string>` | — | — | 独自のリクエストヘッダー |
|
|
121
|
+
| `timeout` | `number` | — | `10000` | タイムアウト(ミリ秒) |
|
|
122
|
+
|
|
123
|
+
API 通知先は信頼できるサーバー設定です。ログには origin のみを記録し、URL 認証情報、クエリ、宛先、外部の失敗応答本文は残しません。テナント・ユーザー入力の URL にはアプリでドメインの allowlist を設け、企業環境では出口プロキシやネットワーク方針でも内部メタデータへの接続を防いでください。
|
|
124
|
+
|
|
125
|
+
### DND(通知抑制)
|
|
126
|
+
|
|
127
|
+
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
128
|
+
| ---------- | ---------------------- | ---- | --------- | --------------------------------------- |
|
|
129
|
+
| `enabled` | `boolean` | — | `false` | 有効化するか |
|
|
130
|
+
| `strategy` | `'discard' \| 'delay'` | — | `discard` | discard:破棄 / delay:DND 終了後に送信 |
|
|
131
|
+
| `start` | `string` | — | `00:00` | 開始時刻(HH:mm) |
|
|
132
|
+
| `end` | `string` | — | `00:00` | 終了時刻(HH:mm) |
|
|
133
|
+
|
|
134
|
+
## エラー処理
|
|
135
|
+
|
|
136
|
+
すべての操作は `HaiResult<T>` を返します。エラーコードは `HaiReachError` で定義します:
|
|
137
|
+
|
|
138
|
+
| エラーコード | code | 説明 |
|
|
139
|
+
| -------------------------------------- | --------------- | ------------------------ |
|
|
140
|
+
| `HaiReachError.SEND_FAILED` | `hai:reach:001` | 送信に失敗 |
|
|
141
|
+
| `HaiReachError.TEMPLATE_NOT_FOUND` | `hai:reach:002` | テンプレートが存在しない |
|
|
142
|
+
| `HaiReachError.TEMPLATE_RENDER_FAILED` | `hai:reach:003` | テンプレート描画に失敗 |
|
|
143
|
+
| `HaiReachError.INVALID_RECIPIENT` | `hai:reach:004` | 無効な宛先 |
|
|
144
|
+
| `HaiReachError.PROVIDER_NOT_FOUND` | `hai:reach:005` | Provider が未登録 |
|
|
145
|
+
| `HaiReachError.DND_BLOCKED` | `hai:reach:006` | DND により破棄 |
|
|
146
|
+
| `HaiReachError.DND_DEFERRED` | `hai:reach:007` | DND により一時保留 |
|
|
147
|
+
| `HaiReachError.NOT_INITIALIZED` | `hai:reach:010` | 未初期化 |
|
|
148
|
+
| `HaiReachError.UNSUPPORTED_TYPE` | `hai:reach:011` | 未対応の種類 |
|
|
149
|
+
| `HaiReachError.CONFIG_ERROR` | `hai:reach:012` | 設定エラー |
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const result = await reach.send({ provider: 'email', to: 'user@example.com', body: 'hello' })
|
|
153
|
+
if (!result.success) {
|
|
154
|
+
// result.error.code / result.error.message
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 分散ロック
|
|
159
|
+
|
|
160
|
+
複数ノードでは DND delay の保留メッセージの flush を `@h-ai/cache` の分散ロックで保護し、同時に 1 ノードのみが実行します。
|
|
161
|
+
|
|
162
|
+
- `cache.lock.acquire('reach:flush-pending')` を使用し、TTL は 300 秒。
|
|
163
|
+
- 保留レコードを一括で claim します。処理中レコードには期限があり、ノード異常終了後にほかのノードが引き継いで重複送信のリスクを抑えます。
|
|
164
|
+
- `@h-ai/cache` が未初期化なら分散ロックは自動無効化し、単一ノードで動作します。
|
|
165
|
+
- 安定したプロセス単位の owner 識別子で、ほかの所有者のロックの誤解除を防ぎます。
|
|
166
|
+
|
|
167
|
+
## テスト
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pnpm --filter @h-ai/reach test
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
Apache-2.0
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @h-ai/reach
|
|
2
2
|
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[框架首页](../../README.md) · 相关模块:[@h-ai/cache](../cache/README.md) · [@h-ai/core](../core/README.md) · [@h-ai/reldb](../reldb/README.md)
|
|
6
|
+
|
|
3
7
|
用户触达模块,支持同时注册多个 Provider(邮件、短信、API 回调等),通过模板绑定 Provider 实现按场景路由发送。内置免打扰(DND)机制,支持通过配置文件定义模板。
|
|
4
8
|
|
|
5
9
|
## 依赖
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/reach",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.57",
|
|
5
5
|
"description": "Hai Framework user reach module for email, SMS, and webhook channels.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"zod": "^4.4.3",
|
|
27
|
-
"@h-ai/
|
|
28
|
-
"@h-ai/
|
|
29
|
-
"@h-ai/reldb": "0.1.0-alpha.
|
|
27
|
+
"@h-ai/core": "0.1.0-alpha.57",
|
|
28
|
+
"@h-ai/cache": "0.1.0-alpha.57",
|
|
29
|
+
"@h-ai/reldb": "0.1.0-alpha.57"
|
|
30
30
|
},
|
|
31
31
|
"optionalDependencies": {
|
|
32
32
|
"nodemailer": "^8.0.1"
|