com um clique
hai-reach
使用 @h-ai/reach 进行邮件、短信和 API 回调发送;当需求涉及用户触达、消息通知、验证码发送、多渠道模板管理时使用。
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Menu
使用 @h-ai/reach 进行邮件、短信和 API 回调发送;当需求涉及用户触达、消息通知、验证码发送、多渠道模板管理时使用。
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Baseado na classificação ocupacional SOC
Use when: using @h-ai/ai, LLM calls, chat completion, tool calling, function calling, MCP server, streaming, memory management, context compression, summarization, token estimation, RAG, knowledge base, AI client, embeddings, reasoning, rerank, file parsing, speech recognition ASR, speech synthesis TTS, audio, A2A agent-to-agent. 使用 @h-ai/ai 进行 LLM 调用、工具定义、MCP 服务器、流式处理、记忆管理、上下文压缩、知识库、推理引擎、Rerank、文件解析、语音识别与合成、A2A 与会话持久化。
Use when: using @h-ai/ai for LLM calls, tools, MCP, streaming, memory/context, RAG, audio, A2A, or the AI client. 当需求涉及 AI 对话、工具、Audio、会话、知识库或 AI 客户端时使用。
Use when: creating or extending apps in hai-framework, adding routes, pages, API endpoints, service workspaces, mobile app, H5 app, admin console. 在 hai-framework 中创建或扩展应用或 API service workspace,包含路由、API 端点、typed contract、服务层与 UI 脚手架代码。
Use when: creating a new module, new package, scaffold, add sub-feature, add provider, create repository, module structure, tsup config, error codes, NotInitializedKit pattern. 在 hai-framework 中创建新模块(package)。
Use when: reviewing app code in hai-framework, auditing app quality, checking app conventions, reviewing routes, reviewing API service workspaces, app security, app i18n review. 对 hai-framework 应用层代码进行审查:路由安全 → 认证授权 → i18n → 组件使用 → API 端点 / service workspace → 服务层 → 性能。
Use when: reviewing code, code review, auditing module quality, checking hai-framework conventions, verifying HaiResult<T> usage, reviewing module structure, PR review, checking naming consistency, verifying NotInitializedKit pattern, auditing performance, security, distributed systems. 对 hai-framework 模块进行全维度代码审查:架构 → 命名 → 类型 → 注释 → 性能 → 分布式 → 安全 → 日志 → 测试 → 文档。
| name | hai-reach |
| description | 使用 @h-ai/reach 进行邮件、短信和 API 回调发送;当需求涉及用户触达、消息通知、验证码发送、多渠道模板管理时使用。 |
| 项目 | 契约 |
|---|---|
| 能力 | 使用 @h-ai/reach 进行邮件、短信和 API 回调发送;当需求涉及用户触达、消息通知、验证码发送、多渠道模板管理时使用。 |
| 适用场景 | 当任务与 hai-reach 的能力描述匹配,并且需要遵循本 Skill 的流程和边界时 |
| 输入 | 模块配置、类型化业务参数、依赖初始化状态和目标运行环境 |
| 输出 | 符合模块公共 API 的实现或示例;业务结果使用 HaiResult,并同步必要测试与文档 |
| 限制 | 遵守 init → use → close 生命周期与运行环境边界;不绕过类型、授权、输入校验或敏感信息保护 |
@h-ai/reach提供统一的用户触达接口,支持同时注册多个 Provider(SMTP 邮件、短信、API 回调),内置模板引擎与免打扰(DND)机制。
⚠️ 服务端模块(Node.js only)。 浏览器端不直接发送邮件/短信,而是通过 API 端点触发服务端
reach.send()。
| 模块 | 用途 | 是否必需 | 初始化要求 |
|---|---|---|---|
@h-ai/reldb | 数据库(发送日志与模板持久化) | 可选 | 已初始化时自动启用持久化 |
@h-ai/cache | 缓存(DND delay 策略分布式锁) | 可选 | 已初始化时自动启用分布式锁 |
HaiReachError 做错误分支处理# config/_reach.yml
providers:
- name: email
type: smtp
host: ${HAI_REACH_SMTP_HOST:smtp.example.com}
port: ${HAI_REACH_SMTP_PORT:465}
secure: true
user: ${HAI_REACH_SMTP_USER:}
pass: ${HAI_REACH_SMTP_PASS:}
from: ${HAI_REACH_SMTP_FROM:noreply@example.com}
- name: sms
type: aliyun-sms
accessKeyId: ${HAI_REACH_SMS_ACCESS_KEY:}
accessKeySecret: ${HAI_REACH_SMS_SECRET_KEY:}
signName: ${HAI_REACH_SMS_SIGN_NAME:}
- name: webhook
type: api
url: ${HAI_REACH_WEBHOOK_URL:}
# 模板(可选,也可通过代码注册)
templates:
- name: verification_code
provider: email
subject: '验证码: {code}'
body: '您的验证码是 {code},有效期 {minutes} 分钟。'
- name: sms_code
provider: sms
body: '验证码: {code},{minutes} 分钟内有效。'
# 免打扰(可选)
dnd:
enabled: true
strategy: delay # discard(丢弃)或 delay(延时,DND 结束后集中发送)
start: '22:00'
end: '08:00'
API Provider 只接受 HTTP(S) URL。回调地址若来自不可信输入,业务层必须先做域名 allowlist,并由企业网络出口策略限制内网访问;不要在日志中记录完整 URL、收件人或第三方响应体。
import { cache } from '@h-ai/cache'
import { core } from '@h-ai/core'
import { reach } from '@h-ai/reach'
import { reldb } from '@h-ai/reldb'
// 先初始化可选依赖(按需)
await reldb.init(core.config.get('db')) // 可选,启用发送日志与模板持久化
await cache.init(core.config.get('cache')) // 可选,启用 DND delay 策略分布式锁
// 再初始化触达模块(自动检测已初始化的 reldb/cache 单例)
await reach.init(core.config.get('reach'))
// ... 使用触达服务
await reach.close()
reach.config 返回的是脱敏后的配置快照;api.url / endpoint、授权头、SMTP pass、短信密钥等敏感值会自动隐藏。
// 通过代码保存(配置文件中的模板在 init 时自动注册)
await reach.template.save({
name: 'verification_code',
provider: 'email',
subject: '验证码: {code}',
body: '您的验证码是 {code},有效期 {minutes} 分钟。',
})
await reach.template.saveBatch([
{ name: 'welcome', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎使用 {appName}!' },
{ name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
])
// 使用模板发送邮件(指定 provider)
const result = await reach.send({
provider: 'email',
to: 'user@example.com',
template: 'verification_code',
vars: { code: '123456', minutes: '5' },
})
// 直接发送邮件(无模板)
await reach.send({
provider: 'email',
to: 'user@example.com',
subject: '通知',
body: '<h1>Hello</h1>',
})
// 发送短信(通过 extra 传递 Provider 特有参数)
await reach.send({
provider: 'sms',
to: '13800138000',
extra: { templateCode: 'SMS_123456' },
vars: { code: '654321' },
})
// API 回调
await reach.send({
provider: 'webhook',
to: 'user@example.com',
body: '{"event":"signup"}',
})
| 方法 / 属性 | 签名 | 说明 |
|---|---|---|
reach.init | (config: ReachConfigInput) => Promise<HaiResult<void>> | 初始化(注册多个 Provider) |
reach.send | (message: ReachMessage) => Promise<HaiResult<SendResult>> | 发送消息(通过 provider 字段路由) |
reach.template | ReachTemplateRegistry | 模板注册表 |
reach.config | ReachConfig | null | 当前脱敏配置快照 |
reach.isInitialized | boolean | 是否已初始化 |
reach.close | () => Promise<void> | 关闭所有连接 |
interface ReachConfigInput {
providers: ProviderConfig[] // 多个 Provider 配置
templates?: TemplateConfig[] // 通过配置文件定义的模板
dnd?: {
enabled: boolean // 是否启用
strategy: 'discard' | 'delay' // discard 丢弃 / delay 延时发送
start: string // 开始时间 HH:mm
end: string // 结束时间 HH:mm
}
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
provider | string | ✅ | 目标 Provider 名称 |
to | string | ✅ | 接收方(邮箱或手机号) |
subject | string | — | 邮件主题(直接发送时) |
body | string | — | 消息正文(直接发送时) |
template | string | — | 模板名称(模板发送时) |
vars | Record<string, string> | — | 模板变量 |
extra | Record<string, unknown> | — | Provider 扩展参数(如短信模板编码) |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | ✅ | 模板名称 |
provider | string | ✅ | 绑定的 Provider 名称 |
subject | string | — | 邮件主题模板 |
body | string | ✅ | 正文模板 |
| 错误码 | code | 说明 |
|---|---|---|
HaiReachError.SEND_FAILED | hai:reach:001 | 发送失败 |
HaiReachError.TEMPLATE_NOT_FOUND | hai:reach:002 | 模板未找到 |
HaiReachError.TEMPLATE_RENDER_FAILED | hai:reach:003 | 模板渲染失败 |
HaiReachError.INVALID_RECIPIENT | hai:reach:004 | 无效接收方 |
HaiReachError.PROVIDER_NOT_FOUND | hai:reach:005 | Provider 未找到 |
HaiReachError.DND_BLOCKED | hai:reach:006 | 免打扰丢弃 |
HaiReachError.DND_DEFERRED | hai:reach:007 | 免打扰延时暂存 |
HaiReachError.NOT_INITIALIZED | hai:reach:010 | 模块未初始化 |
HaiReachError.UNSUPPORTED_TYPE | hai:reach:011 | 不支持的类型 |
HaiReachError.CONFIG_ERROR | hai:reach:012 | 配置错误 |
reach.template.save({
name: 'email_code',
provider: 'email',
subject: '验证码: {code}',
body: '您的验证码是 {code},有效期 {minutes} 分钟。',
})
reach.template.save({
name: 'sms_code',
provider: 'sms',
body: '验证码: {code},{minutes} 分钟内有效。',
})
// 根据用户选择的渠道发送
async function sendCode(channel: 'email' | 'sms', target: string, code: string) {
const template = channel === 'email' ? 'email_code' : 'sms_code'
return reach.send({
provider: channel,
to: target,
template,
vars: { code, minutes: '5' },
})
}
import { iam } from '@h-ai/iam'
import { reach } from '@h-ai/reach'
// 初始化 reach(注册 email 和 sms Provider)
await reach.init({
providers: [
{ name: 'email', type: 'smtp', host: 'smtp.example.com', from: 'noreply@example.com' },
{ name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '...' },
],
templates: [
{ name: 'password_reset', provider: 'email', subject: 'Password Reset', body: 'Token: {token}, expires: {expiresAt}' },
{ name: 'otp_email', provider: 'email', subject: 'Code: {code}', body: 'Your code is {code}' },
{ name: 'otp_sms', provider: 'sms', body: 'Your code is {code}' },
],
})
// 初始化 IAM,使用 reach 发送通知
await iam.init({
db,
cache,
onPasswordResetRequest: async (user, token, expiresAt) => {
await reach.send({ provider: 'email', to: user.email ?? '', template: 'password_reset', vars: { token, expiresAt: expiresAt.toISOString() } })
},
onOtpSendEmail: async (email, code) => {
await reach.send({ provider: 'email', to: email, template: 'otp_email', vars: { code } })
},
onOtpSendSms: async (phone, code) => {
await reach.send({ provider: 'sms', to: phone, template: 'otp_sms', vars: { code } })
},
})
import { HaiReachError } from '@h-ai/reach'
const result = await reach.send(message)
if (!result.success) {
switch (result.error.code) {
case HaiReachError.NOT_INITIALIZED.code:
break
case HaiReachError.PROVIDER_NOT_FOUND.code:
break
case HaiReachError.DND_BLOCKED.code:
// 免打扰时段(discard 策略),消息已丢弃
break
case HaiReachError.TEMPLATE_NOT_FOUND.code:
break
case HaiReachError.SEND_FAILED.code:
break
}
}
hai-core — 配置加载、日志、HaiResult 模式hai-reldb — 数据库操作(存储发送记录等)hai-cache — 缓存操作、分布式锁(DND flush 互斥)