内容
# willitsend
[](https://github.com/abryfs/willitsend/actions/workflows/ci.yml) [](LICENSE)
您的 AI 代理发送短信。运营商会静默丢弃不符合规定的消息,而 API 永远不会告诉您。**willitsend 是模型和运营商之间的缺失检查**:一个用于外发 SMS/iMessage 的确定性预检,可以在花费发送费用之前捕获静默过滤、分段爆炸和丢弃的 iMessage 功能。
**[在浏览器中试用](https://abryfs.github.io/willitsend/)**:在客户端运行,页面上没有任何内容离开。
```
npx -y -p github:abryfs/willitsend willitsend-cli "嘿!你的预约明天在下午 2 点。" --first-message
```
```
裁决:拦截
分段:1(gsm7,41 个七位组)
通道:未知
[拦截] first-message.opt-out:第一条消息不包含退出说明(例如“回复 STOP 取消订阅”)。运营商可能会静默过滤缺乏退出说明的第一条消息,而不会返回 API 错误。
修复:在消息正文中添加“回复 STOP 取消订阅”。
来源:https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#first-message-requirements
[警告] first-message.opt-in:未找到选择加入确认语言 …
[信息] first-message.brand:未提供品牌名称 …
```
## 问题
消息 API 在消息到达下游运营商时报告“已发送”,而不是到达手机时。AgentPhone 的[速率限制文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#first-message-requirements) 明确了后果:跳过品牌识别、选择加入确认或退出说明的第一条消息“可能会被运营商静默过滤。API 不会返回错误。”
现在,AI 代理自主发送短信,模型和运营商之间没有任何内容。一个丢弃退出行的代理不会收到错误或送达通知。一个添加一个表情符号的代理会将 160 个字符的消息变成三个计费分段,并且永远不会注意到。
`willitsend` 是缺失的检查:一个用于草稿消息的确定性、无状态的 lint。文本和上下文输入,裁决和证据输出。它不会发送任何内容,也不会存储任何内容。
```mermaid
sequenceDiagram
participant A as AI 代理
participant W as willitsend
participant API as 消息 API
participant C as 运营商
participant P as 手机
rect rgba(255,69,58,0.08)
note over A,P: 无预检
A->>API: send_message(无退出语言)
API-->>A: "sent" ✓
C--xP: 静默过滤。无错误,无送达
end
rect rgba(52,199,89,0.08)
note over A,P: 有预检
A->>W: preflight_message(草稿,上下文)
W-->>A: 拦截 · 缺少退出 · 修复 + 引用
A->>W: preflight_message(修复后的草稿)
W-->>A: 通过
A->>API: send_message
API-->>A: "sent" ✓
C->>P: 已送达
end
```
## 为什么不直接提示代理规则?
一个公平的问题:AgentPhone 的文档是代理可读的,因此您可以将它们馈入每个生成调用,并要求模型遵守。两个问题会中断,一个会很昂贵:
1. **生成是概率性的;验证是确定性的。** 具有上下文规则的模型通常会遵守。此检查每次都会返回相同的裁决,其故障模式是可见的发现,而不是静默过滤的消息。编写时的指导和发送前验证是互补的 - 此存储库提供了两者([代理技能](skill/SKILL.md) 是编写时的另一半)。
2. **模型无法进行分段数学运算。** 七位组计数、GSM-7 扩展字符、从一个智能引号编码转换、放置感知打包 - 基于令牌的模型在结构上不擅长这种算术。没有提示可以修复它;一个 300 行的确定性函数可以做到。
3. **令牌数学,衡量** (char-count/4 启发式算法,实际页面,2026 年 7 月):
| 方法 | 每条消息的令牌 | 每天 10,000 条消息,按 $3/M 输入 |
| --- | --- | --- |
| 将 AgentPhone 文档页面的内容馈入每个生成调用 | ~7,700 | ~$231/天(~$23/天,90% 提示缓存) |
| `preflight_message` 调用结果(默认) | **~250** (+ ~1,450 一次会话的工具定义) | **~$7.50/天** |
| `preflight_message` 使用 `response_format: "concise"` | **~4 通过,~44 拦截** (已衡量) | **~$0.35/天** ,通过率为 90% |
大约 **30 倍每条消息的令牌** 在默认模式下 - 在简洁模式下为几百倍 - 并且检查者自己的计算成本为零(~4µs 本地,无 API)。如果您将规则压缩到系统中 - 我们的技能文件在 ~950 个令牌下执行 - 您将保留编写时的好处,但仍然没有验证器和分段数学运算。大批量发送者会感受到这一点:该工具专门为大量发送短信的 AI 代理而存在。
## 快速入门
**作为 MCP 工具** (Claude Code):
```
claude mcp add willitsend -- npx -y -p github:abryfs/willitsend willitsend-mcp
```
或在任何 MCP 客户端配置中:
```json
{
"mcpServers": {
"willitsend": {
"command": "npx",
"args": ["-y", "-p", "github:abryfs/willitsend", "willitsend-mcp"]
}
}
}
```
**对于 AI 代理** - 将此块粘贴到您的代理中(或指向此 README):
```text
安装 willitsend MCP 服务器(发送前 SMS/iMessage lint,无需 API 密钥):
- Claude Code:运行 `claude mcp add willitsend -- npx -y -p github:abryfs/willitsend willitsend-mcp`
- 任何其他 MCP 客户端:在其 MCP 服务器配置中添加 {"command": "npx", "args": ["-y", "-p", "github:abryfs/willitsend", "willitsend-mcp"]}。
然后,在每次 send_message(或等效)调用之前,用草稿正文、is_first_message_to_contact(true/false,当已知时)和 brand_name 调用 preflight_message。
将裁决“block”视为 do-not-send:应用返回的修复字符串并重新运行直到通过。
将“needs_context”视为确定这是否是第一条消息的信号(检查对话历史),而非发送许可。
对于高容量循环,通过 response_format: "concise"(~4 个令牌通过)。
```
无需 API 密钥。如果您想进行实时 iMessage/SMS 能力查找以获取电话号码目的地,请设置 `AGENTPHONE_API_KEY`;如果没有,该工具将离线运行。上述配置直接从此存储库安装(`prepare` 脚本在安装时构建)- 无需注册。willitsend 还发布到 npm 和 [MCP 注册表](https://registry.modelcontextprotocol.io),在服务器名称 `io.github.abryfs/willitsend` 下;一旦发布,支持注册表的客户端可以通过名称添加它,并且 `npx -y willitsend` 直接启动服务器。参见 [`docs/publishing.md`](docs/publishing.md) 获取发布工作流程。
**作为库** (`npm install github:abryfs/willitsend`):
```ts
import { preflight } from "willitsend";
const report = preflight({
body: "Acme: thanks for signing up. Your order shipped. Reply STOP to unsubscribe.",
is_first_message_to_contact: true,
brand_name: "Acme",
});
report.verdict; // "pass" | "warn" | "block" | "needs_context"
report.findings; // 每个都带有严重性、修复和引用 URL
report.trace.segments; // { encoding, units, segments, perSegment, ... }
```
**作为 CLI:** `npx -y -p github:abryfs/willitsend willitsend-cli --help` (或 `npx -y -p willitsend willitsend-cli` 一旦从 npm 安装)。退出代码:`0` 通过/警告,`1` 拦截,`2` 需要上下文,`3` 用法错误。它直接放入 CI 或 shell 管道中。
## 检查内容
| 规则 | 严重程度 | 来源 |
| --- | --- | --- |
| `first-message.opt-out`:第一条消息到联系人必须带有退出说明 | 拦截 | [AgentPhone 文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#first-message-requirements) |
| `first-message.brand`:第一条消息必须标识品牌(仅检查您提供的 `brand_name`,从不猜测) | 拦截 | [AgentPhone 文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#first-message-requirements) |
| `first-message.opt-in`:第一条消息应确认联系人如何选择加入 | 警告 | [AgentPhone 文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#first-message-requirements) |
| `first-message.media-only`:合规文本不能放在图像中 | 警告 | [AgentPhone 文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#first-message-requirements) |
| `segments.unicode-blowup`:一个非 GSM 字符重新编码整个消息为 UCS-2 | 警告 | [AgentPhone 文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#daily-message-limits) |
| `imessage.feature-fallback`:`send_style`、线程回复和轮播在 SMS 降级时无痕迹 | 警告 | [AgentPhone 文档](https://docs.agentphone.ai/documentation/guides/messages#imessage) |
| `imessage.invalid-send-style`、`imessage.carousel-count`:无效的效果名称,轮播在文档的 2-20 范围之外 | 拦截 | [AgentPhone 文档](https://docs.agentphone.ai/api-reference/messages/send-message-v-1-messages-post#carousel--multi-image-imessage) |
| `imessage.new-contact-cap`:iMessage 对新联系人每天限制 50 条消息/线路 | 信息 | [AgentPhone 文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#imessage) |
| `destination.invalid`、`destination.voip`:格式错误的地址;已知的 VoIP 线路(线路类型从不根据号码猜测) | 拦截/警告 | [发送 API](https://docs.agentphone.ai/api-reference/messages/send-message-v-1-messages-post) / [AgentPhone 文档](https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#delivery-and-reliability) |
| `content.shaft`:运营商过滤的性/酒精/火器/烟草术语(仇恨言论没有关键字规则:单词列表无法检测到它,我们不假装) | 警告 | [Twilio 指南](https://www.twilio.com/en-us/guidelines/us/sms) |
| `content.url-shortener`:共享的公共缩短器(bit.ly、tinyurl、…)与 CTIA 专用缩短器指南冲突 | 警告 | [CTIA MP&BP (PDF)](https://api.ctia.org/wp-content/uploads/2023/05/230523-CTIA-Messaging-Principles-and-Best-Practices-FINAL.pdf) |
| `content.spam-patterns`:全大写运行、`$$$`、`!!!` | 信息 | 启发式 |
规则分为两个标记层级。来自 AgentPhone 自己文档的规则可以拦截。行业来源的规则最多警告,因为关键字启发式不能否决您的发送。
### 裁决模型
依赖于您未提供的上下文的发现不会假装确定。如果您从未说明这是否是联系人的第一条消息,则退出规则会条件性地报告,裁决为 `needs_context`。您不会获得虚假的通过或拦截。
```mermaid
flowchart LR
F[发现] --> B{任何拦截发现<br/>带有已知上下文?}
B -- 是 --> 拦截
B -- 否 --> C{任何拦截发现<br/>等待上下文?}
C -- 是 --> NC[需要上下文]
C -- 否 --> W{任何警告?}
W -- 是 --> 警告
W -- 否 --> 通过
```
每个报告还附带一个**发送跟踪**:目的地分类、假设通道(iMessage/SMS/MMS)、完整的分段数学(编码、七位组和代码单元计数、每个分段的放置感知打包),以及给定 10DLC 活动层级的发布每日分段上限,消息在此成本下。
## 基准
在此存储库中重现每个数字;无需帐户。
- **分段数学一致性:** 与 [Twilio 的参考分段计算器](https://github.com/TwilioDevEd/message-segment-calculator) 在编码和分段计数方面达成一致,涵盖 126 个向量语料库:GSM-7/UCS-2 边界、扩展字符、表情符号、ZWJ 序列和确定性模糊扫描。语料库和套件位于 [`test/parity.test.ts`](test/parity.test.ts);运行 `npm test`。
- **延迟:** 中位数 ~4µs 每条消息,~100,000 条消息/秒在一个线程上(Node 22,Apple Silicon 笔记本电脑;单通道代码点表,每个字符零分配)。在您的机器上运行 `npm run bench`。预检每条出站消息的成本低于记录日志的成本。
- **工作成本示例,根据发布上限:** 独资 10DLC 活动每天可获得 1,000 个 T-Mobile 分段(≈3,000 个美国总分段,根据 AgentPhone 的估计)。一个表情符号使 2 分段的 GSM-7 消息翻转为 4 个 UCS-2 分段,从而使该配额购买的可用消息数量减半。相同的文本、相同的收件人、可用消息数量减半。
您不会在此找到任何交付率改善的声明。 该声明需要我们没有的发送数据。
## 它不能做什么
运营商使用他们不公开的规则过滤系统。 政策层是公开的(行为准则、内容类别、容量上限); 运行时过滤决策是故意不透明的, 并且只有结果的一部分是可观察的(一些拦截会返回明确的过滤错误, 一些消息被接受并静默地从未送达)。 本工具涵盖了确定性的、文档化的层, 而不是其他任何东西:
- **通过** 不是交付保证。 这意味着文档中没有任何内容会阻止消息。
- 选择加入检查验证语言存在, 而不是存在同意。 同意存在于您的记录中, 而不是在消息文本中。
- 检测仅限英语(v1 中发现带有 `locale` 字段)。
- 配额数学是根据发布的上限进行的静态说明, 而不是您帐户的实时状态。
- 工具永远不会根据电话号码推断线路类型(VoIP/移动电话)。 如果您有电话号码, 请从查找服务传递 `destination_line_type`; [libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js) 提供免费的近似值, 但有已知的 US 号码限制。
## 先前的工作
- [TwilioDevEd/message-segment-calculator](https://github.com/TwilioDevEd/message-segment-calculator) 是分段数学的参考。 该库针对它进行了测试, 而不是与其竞争;
- [sms_policy_checker](https://github.com/ConnorBaldes/sms_policy_checker) 涵盖了 Ruby/Rails 的类似合规性检查; willitsend 是确定性的、零网络的对应物。
- Web 检查器(10dlccheck.com,Calilio)涵盖了重叠规则作为人为界面。 willitsend 使这些检查可嵌入:一个库,代理可以在一秒内调用一百万次,并带有引用。
- 就我们所知, 这是第一个用于发送前 SMS 内容合规性的 MCP 工具。 欢迎纠正。
## 隐私
无遥测。 无网络调用, 除非您通过 API 密钥启用可选的能力查找。 工具永远不会记录、 存储或回显其输出中的消息正文。 游乐场在您的浏览器中运行, 并且不会将任何内容发送到任何地方。
## 未来
- 引擎的自然家园是服务器端:自身发送端点的 `dry_run` 参数。 库编写为纯函数, 无状态, 核心依赖项为零。
- 闭环: 将预检裁决与交付状态 Webhook 相关联, 以便规则精度根据实际结果而非假设进行衡量。 过滤部分可观察(某些拦截会返回明确的过滤错误, 其他消息被接受并静默传递), 这恰好足以诚实地评估确定性规则集。 学习不透明层本身需要跨多个发送者的聚合发送数据; 那属于平台, 而不是无状态客户端工具。
- 通过 MCP 应用扩展的呈现的交付能力报告卡。
- 英语以外的语言包。
## 开发
```
git clone https://github.com/abryfs/willitsend && cd willitsend
npm install # 通过准备构建
npm test # 191 个测试:单元测试、Twilio 一致性、保留验收
npm run bench
```
## 许可证
MIT