npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@h-ai/reach

v0.1.0-alpha.10

Published

Hai Framework user reach module for email, SMS, and webhook channels.

Readme

@h-ai/reach

用户触达模块,支持同时注册多个 Provider(邮件、短信、API 回调等),通过模板绑定 Provider 实现按场景路由发送。内置免打扰(DND)机制,支持通过配置文件定义模板。

依赖

  • @h-ai/reldb — 数据库(发送日志与模板持久化),可选;已初始化时自动启用持久化
  • @h-ai/cache — 缓存(DND delay 策略分布式锁),可选;已初始化时自动启用分布式锁

支持的 Provider

| Provider | 渠道 | 说明 | | ------------ | ---- | ------------------------------------------- | | console | 通用 | 控制台输出(开发/测试用) | | smtp | 邮件 | SMTP 协议发送邮件(需安装 nodemailer) | | aliyun-sms | 短信 | 阿里云短信服务(直接调用 HTTP API,无 SDK) | | api | 通用 | HTTP API 回调(通用 webhook) |

快速开始

安装

pnpm add @h-ai/reach

邮件发送需安装 nodemailer

多 Provider 初始化

import { cache } from '@h-ai/cache'
import { reach } from '@h-ai/reach'
import { reldb } from '@h-ai/reldb'

// 1. 初始化可选依赖(按需)
await reldb.init({ type: 'sqlite', database: './data.db' }) // 可选,启用发送日志与模板持久化
await cache.init({ type: 'memory' }) // 可选,启用 DND delay 策略分布式锁

// 2. 初始化触达模块(自动检测已初始化的 reldb/cache 单例)
await reach.init({
  providers: [
    { name: 'email', type: 'smtp', host: 'smtp.example.com', from: '[email protected]' },
    { name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '某某科技' },
    { name: 'webhook', type: 'api', url: 'https://api.example.com/notify' },
  ],
  templates: [
    { name: 'welcome_email', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎!' },
    { name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
  ],
  dnd: { enabled: true, strategy: 'delay', start: '22:00', end: '08:00' },
})

// 通过 provider 字段选择发送渠道
await reach.send({
  provider: 'email',
  to: '[email protected]',
  template: 'welcome_email',
  vars: { userName: '张三' },
})

// 发送短信(通过 extra 传递 Provider 特有参数)
await reach.send({
  provider: 'sms',
  to: '13800138000',
  extra: { templateCode: 'SMS_123456' },
  vars: { code: '123456' },
})

// 关闭所有 Provider
await reach.close()

配置

Console(开发/测试)

const config = { name: 'dev', type: 'console' }

SMTP

| 字段 | 类型 | 必填 | 默认值 | 说明 | | -------- | --------- | ---- | ------ | --------------- | | name | string | ✅ | — | Provider 名称 | | type | string | ✅ | — | 固定 'smtp' | | host | string | ✅ | — | SMTP 服务器地址 | | port | number | — | 465 | SMTP 端口 | | secure | boolean | — | true | 是否使用 TLS | | user | string | — | — | 认证用户名 | | pass | string | — | — | 认证密码 | | from | string | ✅ | — | 发件人地址 |

阿里云短信

| 字段 | 类型 | 必填 | 默认值 | 说明 | | ----------------- | -------- | ---- | ----------------------- | ------------------- | | name | string | ✅ | — | Provider 名称 | | type | string | ✅ | — | 固定 'aliyun-sms' | | accessKeyId | string | ✅ | — | AccessKey ID | | accessKeySecret | string | ✅ | — | AccessKey Secret | | signName | string | ✅ | — | 短信签名 | | endpoint | string | — | dysmsapi.aliyuncs.com | API 端点 |

API 回调

| 字段 | 类型 | 必填 | 默认值 | 说明 | | --------- | ------------------------ | ---- | ------- | ------------- | | name | string | ✅ | — | Provider 名称 | | type | string | ✅ | — | 固定 'api' | | url | string | ✅ | — | 回调 URL | | method | 'POST' \| 'PUT' | — | POST | HTTP 方法 | | headers | Record<string, string> | — | — | 自定义请求头 | | timeout | number | — | 10000 | 超时毫秒数 |

DND(免打扰)

| 字段 | 类型 | 必填 | 默认值 | 说明 | | ---------- | ---------------------- | ---- | --------- | --------------------------------------- | | enabled | boolean | — | false | 是否启用 | | strategy | 'discard' \| 'delay' | — | discard | discard 丢弃 / delay 延时(DND 后发送) | | start | string | — | 00:00 | 开始时间(HH:mm 格式) | | end | string | — | 00:00 | 结束时间(HH:mm 格式) |

错误处理

所有操作返回 HaiResult<T>,错误码定义在 HaiReachError

| 错误码 | code | 说明 | | ---------------------------------- | --------------- | --------------- | | HaiReachError.SEND_FAILED | hai:reach:001 | 发送失败 | | HaiReachError.PROVIDER_NOT_FOUND | hai:reach:002 | Provider 未注册 | | HaiReachError.TEMPLATE_NOT_FOUND | hai:reach:003 | 模板不存在 | | HaiReachError.CONFIG_ERROR | hai:reach:007 | 配置错误 | | HaiReachError.NOT_INITIALIZED | hai:reach:010 | 未初始化 |

const result = await reach.send({ provider: 'email', to: '[email protected]', body: 'hello' })
if (!result.success) {
  // result.error.code / result.error.message
}

分布式锁

多节点部署时,DND(delay 策略)的 pending 消息 flush 操作通过 @h-ai/cache 分布式锁保护,确保同一时刻只有一个节点执行 flush。

  • 锁基于 cache.lock.acquire('reach:flush-pending'),TTL 60 秒
  • @h-ai/cache 未初始化,分布式锁自动禁用,不影响单节点运行
  • 使用稳定的进程级 owner 标识,防止误释放他人锁

测试

pnpm --filter @h-ai/reach test

License

Apache-2.0