关于 willitsend
willitsend 是面向会发送短信的 AI Agent 的确定性预检工具,可在发送前检查 SMS/iMessage 草稿。它计算分段并标示文档规定的合规风险,但不会发送或存储消息。
主要功能
- 01检查首条消息的合规项
- 02计算编码与短信分段
- 03标示 iMessage 功能回退风险
- 04返回判定、依据与修正建议
- 05提供 MCP 服务、CLI 和库
适用场景
- 01发送首条短信前检查退订说明
- 02发现 Unicode 字符造成的分段增加
- 03检查 iMessage 转为 SMS 时丢失的功能
MCP 相关
MCP 服务向客户端提供 preflight_message 工具,用于检查短信草稿,并返回判定、发现项和发送追踪信息。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
1收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| body | string | 是 | 要预检的确切消息文本。仅发送媒体时请传入空字符串。此文本仅用于本地分析——工具不会在任何地方存储或转发该文本。 |
| to_number | string | 否 | 单个目标:E.164 电话号码(例如 +15551234567)、电子邮件地址(仅限 iMessage)、5–6 位短代码,或 grp_ 群组 ID。使用 `recipients` 指定多收件人群组时请省略。 |
| brand_name | string | 否 | 首条消息中应能识别出的发送方品牌或公司名称。若未提供,品牌识别检查将降级为仅提供建议。 |
| media_urls | array | 否 | 媒体附件 URL。包含 2–20 个条目时会触发 iMessage 轮播;在其他渠道上,请检查返回的发现项中是否有轮播大小警告。 |
| recipients | array | 否 | 用于创建新的 iMessage 群组的 2 个或更多目标(仅限 iMessage,绝不会通过 SMS 投递)。不要与 to_number 同时使用。 |
| send_style | string | 否 | 仅限 iMessage 的视觉发送效果(例如 "confetti"、"slam")。在 iMessage 以外的渠道会被静默丢弃;除非已确认渠道为 iMessage,否则会标记此项。 |
| campaign_type | enum | 否 | 已知时可传入 10DLC campaign tier,以便在跟踪信息中附加每日发送上限的静态示例。sole_proprietor | low_volume | high_volume_low_trust | high_volume_standard | high_volume_high_trust | high_volume_highest_trust |
| response_format | enum | 否 | 输出长度。"detailed"(默认):完整消息、修复方法和引用 URL。"concise":每个发现项占一行(严重性、规则、修复方法),令牌数约减少 10 倍——使用相同引擎和判定,并包含所有必要的操作信息;适用于高频循环。detailed | concise |
| reply_to_message_id | string | 否 | 仅限 iMessage 的线程回复目标消息 ID。 |
| destination_line_type | enum | 否 | 通过外部查询获知的目标电话号码线路类型(如有)。不得根据号码本身猜测——VoIP 线路的 iMessage/SMS 投递情况通常不可靠。mobile | voip | landline | unknown |
| destination_capabilities | object | 否 | 如果已查询目标的已知投递能力,可在此传入。显式传入此值始终优先于此服务自身的可选查询,并会跳过该查询。 |
| destination_capabilities.sms | boolean | 是 | |
| destination_capabilities.imessage | boolean | 是 | |
| is_first_message_to_contact | boolean | 否 | 这是否是首次向此联系人发送外发消息。确定时设置为 true 或 false——首条消息有更严格的合规要求(退订措辞、品牌标识、选择加入措辞)。只有确实未知时才留空;此时工具会将受影响的检查结果标记为有条件,而非直接断言(判定结果为 needs_context)。 |
详细介绍
willitsend
你的 AI Agent 会发送短信。运营商会悄悄丢弃不合规的短信,而 API 从不告知你。willitsend 是模型与运营商之间缺失的检查环节:它是一项确定性的发送前预检,可在你付费发送前发现静默过滤、分段暴增以及被丢弃的 iMessage 功能。
在浏览器中试用:在客户端运行,不会有任何内容离开当前页面。
npx -y -p github:abryfs/willitsend willitsend-cli "Hey! Your appointment is tomorrow at 2pm." --first-message
Verdict: BLOCK
Segments: 1 (gsm7, 41 septets)
Channel: unknown
[BLOCK] first-message.opt-out: First message doesn't include opt-out instructions (e.g. "Reply STOP
to unsubscribe"). Carriers may silently filter first messages that lack one, with no API error.
Fix: Append "Reply STOP to unsubscribe." to the message body.
Source: https://docs.agentphone.ai/documentation/reference/messaging-rate-limits#first-message-requirements
[WARN] first-message.opt-in: No opt-in acknowledgment language found …
[INFO] first-message.brand: No brand_name was provided …
问题
消息 API 在消息到达下游运营商时就会报告 "sent",而不是在消息到达手机时。AgentPhone 的速率限制文档明确说明了后果:首次发送的消息如果缺少品牌标识、选择加入确认或退订说明,“可能会被运营商静默过滤。API 不会返回错误。”
如今,AI Agent 会自主发送短信,而模型与运营商之间没有任何环节进行检查。Agent 如果漏掉退订说明,不会收到错误,也无法送达。Agent 如果加上一个表情符号,就会把一条 160 个字符的消息变成三个计费分段,却毫无察觉。
willitsend 是缺失的检查环节:针对草稿消息的确定性、无状态 lint 工具。输入文本和上下文,输出判定和依据。它不会发送或存储任何内容。
sequenceDiagram
participant A as AI agent
participant W as willitsend
participant API as Messaging API
participant C as Carrier
participant P as Phone
rect rgba(255,69,58,0.08)
note over A,P: without preflight
A->>API: send_message (no opt-out language)
API-->>A: "sent" ✓
C--xP: silently filtered. no error, no delivery
end
rect rgba(52,199,89,0.08)
note over A,P: with preflight
A->>W: preflight_message(draft, context)
W-->>A: BLOCK · missing opt-out · fix + citation
A->>W: preflight_message(fixed draft)
W-->>A: PASS
A->>API: send_message
API-->>A: "sent" ✓
C->>P: delivered
end
为什么不直接把规则作为提示词告诉 Agent?
这是个合理的问题:AgentPhone 的文档可供 Agent 读取,因此你可以在每次生成调用时都把文档提供给模型,并要求模型遵守。两个问题会随之出现,其中一个还会带来高昂成本:
- 生成是概率性的,验证是确定性的。 当规则出现在上下文中时,模型 通常 会遵守。本检查器每次都会返回相同的判定,出错时会给出明确的检查结果,而不是让消息被静默过滤。撰写时的指导与发送前的验证相辅相成——此仓库两者都提供(Agent 技能是撰写时的部分)。
- 模型无法计算分段。 七位组计数、GSM-7 扩展字符、一个智能引号导致的编码切换、考虑位置的分段打包——基于词元的模型在结构上就不擅长这类计算。再多提示词也无法解决;300 行的确定性函数却可以。
- 实测词元成本(基于实际页面的字符数除以 4 的估算方法,2026 年 7 月):
| 方案 | 每条消息的词元数 | 每天 10,000 条消息,输入价格为每百万词元 3 美元 |
|---|---|---|
| 每次生成调用都提供 AgentPhone 的两个文档页面 | ~7,700 | ~$231/天(开启 90% 提示词缓存后约 ~$23/天) |
preflight_message 调用结果(默认) | ~250(另需每个会话一次,约 ~1,450 词元用于工具定义) | ~$7.50/天 |
preflight_message 搭配 response_format: "concise" | 通过时约 ~4,拦截时约 ~44(实测) | ~$0.35/天,按 90% 通过率计算 |
默认模式下,每条消息的词元数大约减少 30 倍,精简模式下则减少数百倍;检查器自身的计算成本可忽略不计(本地约 4µs,无需 API)。如果你把规则压缩到系统提示词中——我们的技能文件只需约 950 个词元——就能保留撰写时的好处,但仍然没有验证器,也无法计算分段。批量发送者对此感受最深:这个工具的存在,正是为了服务大量发送短信的 AI Agent。
快速入门
作为 MCP 工具(Claude Code):
claude mcp add willitsend -- npx -y -p github:abryfs/willitsend willitsend-mcp
或在任何 MCP 客户端配置中:
{
"mcpServers": {
"willitsend": {
"command": "npx",
"args": ["-y", "-p", "github:abryfs/willitsend", "willitsend-mcp"]
}
}
}
供 AI Agent 使用 — 将此代码块粘贴到你的 Agent 中(或让它查看此 README):
Install the willitsend MCP server (pre-send SMS/iMessage lint, no API key needed):
- Claude Code: run `claude mcp add willitsend -- npx -y -p github:abryfs/willitsend willitsend-mcp`
- Any other MCP client: add {"command": "npx", "args": ["-y", "-p", "github:abryfs/willitsend", "willitsend-mcp"]} to its MCP server config.
Then, before every send_message (or equivalent) call, call preflight_message with the
draft body, is_first_message_to_contact (true/false when known), and brand_name.
Treat verdict "block" as do-not-send: apply the returned fix strings and re-run until
pass. Treat "needs_context" as a signal to determine whether this is a first message
(check conversation history), never as permission to send. For high-volume loops pass
response_format: "concise" (~4 tokens on pass).
无需 API 密钥。如果你想查询电话号码目的地的实时 iMessage/SMS 功能,请设置 AGENTPHONE_API_KEY;不设置时,工具会离线运行。上述配置直接从此仓库安装(安装时会运行 prepare 脚本进行构建)——无需注册。willitsend 也会发布到 npm 和 MCP Registry,服务名称为 io.github.abryfs/willitsend;发布版本后,支持注册表的客户端可以按名称添加它,并通过 npx -y willitsend 直接启动服务。有关发布流程,请参阅 docs/publishing.md。
作为库使用(npm install github:abryfs/willitsend):
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; // each with severity, fix, and a citation URL
report.trace.segments; // { encoding, units, segments, perSegment, ... }
作为 CLI 使用: npx -y -p github:abryfs/willitsend willitsend-cli --help(或从 npm 安装后运行 npx -y -p willitsend willitsend-cli)。退出代码:0 表示通过/警告,1 表示阻止,2 表示需要上下文,3 表示用法错误。可直接用于 CI 或 Shell 管道。
检查内容
| 规则 | 严重级别 | 来源 |
|---|---|---|
first-message.opt-out:发给联系人的第一条消息必须附带退订说明 | 拦截 | AgentPhone 文档 |
first-message.brand:第一条消息必须标明品牌(仅根据你提供的 brand_name 检查,绝不猜测) | 拦截 | AgentPhone 文档 |
first-message.opt-in:第一条消息应说明联系人如何选择订阅 | 警告 | AgentPhone 文档 |
first-message.media-only:合规文本不能只放在图片里 | 警告 | AgentPhone 文档 |
segments.unicode-blowup:一个非 GSM 字符就会导致整条消息重新编码为 UCS-2 | 警告 | AgentPhone 文档 |
imessage.feature-fallback:回退到 SMS 时,send_style、串线回复和轮播消息会悄然丢失 | 警告 | AgentPhone 文档 |
imessage.invalid-send-style、imessage.carousel-count:无效的效果名称;轮播数量不在文档所述的 2-20 范围内 | 拦截 | AgentPhone 文档 |
imessage.new-contact-cap:iMessage 对每条线路向新联系人发送消息的数量限制为每天 50 条 | 信息 | AgentPhone 文档 |
destination.invalid、destination.voip:目标地址格式错误;已知的 VoIP 线路(绝不根据号码猜测线路类型) | 拦截 / 警告 | 发送 API / AgentPhone 文档 |
content.shaft:运营商会过滤涉及性、酒精、枪支、烟草的词语(仇恨言论没有关键词规则:词表无法检测仇恨言论,我们不会假装可以) | 警告 | Twilio 指南 |
content.url-shortener:共用的公共短链接服务(bit.ly、tinyurl、……)与 CTIA 关于专用短链接的指导原则相冲突 | 警告 | CTIA MP&BP(PDF) |
content.spam-patterns:连续大写字母、$$$、!!! | 信息 | 启发式规则 |
规则分为两个标注的层级。来源于 AgentPhone 自有文档的规则可以拦截发送。来自行业资料的规则最多发出警告,因为关键词启发式规则无权否决你的发送操作。
判定模型
如果某项检查依赖你未提供的上下文,就不会假装能确定结果。如果你没有说明这是否是发给某位联系人的第一条消息,退订规则会报告为有条件适用,判定结果为 needs_context。你不会得到虚假的通过或拦截结果。
flowchart LR
F[findings] --> B{any block finding<br/>with known context?}
B -- yes --> BLOCK
B -- no --> C{any block finding<br/>waiting on context?}
C -- yes --> NC[NEEDS_CONTEXT]
C -- no --> W{any warning?}
W -- yes --> WARN
W -- no --> PASS
每份报告还会包含一条发送跟踪信息:目标地址分类、假定渠道(iMessage/SMS/MMS)、完整的分段计算(编码、septet 和码元数量、考虑位置的逐段打包),以及在提供 10DLC 活动等级时,这条消息会占用已公布的每日分段上限多少额度。
基准测试
以下所有数字都可以从仓库中复现;没有任何一项需要账户。
- **分段计算一致性:**在一个固定的 126 个向量语料库中,编码和分段数量均与 Twilio 的参考分段计算器一致,涵盖 GSM-7/UCS-2 边界、扩展字符、表情符号、ZWJ 序列和确定性模糊测试。语料库和测试套件位于
test/parity.test.ts;运行npm test。 - **延迟:**每条消息的中位延迟约为 4µs,单线程每秒约处理 100,000 条消息(Node 22、Apple Silicon 笔记本电脑;采用单次遍历码点表,逐字符分配次数为零)。在你的设备上运行
npm run bench。对每条出站消息进行预检,比记录日志更省成本。 - **成本示例,基于公开的上限:**独资经营者的 10DLC 营销活动每天可获得 1,000 个 T-Mobile 分段(按 AgentPhone 公布的估算,美国境内总计约 3,000 个)。一个多余的表情符号若将一条 2 个 GSM-7 分段的消息变为 4 个 UCS-2 分段,就会让该配额可发送的消息数量减半。文本相同,收件人相同,覆盖范围减半。
这里不会声称投递率有所提升。这种说法需要我们并不掌握的发送数据。
此工具无法做到的事
运营商使用的过滤系统规则并未公开。政策层面是公开的(行为准则、内容类别、发送量上限);运行时的过滤决策则刻意保持不透明,而且只能观察到部分结果(某些拦截会返回明确的过滤错误,某些消息则会被接受,但始终无法送达)。此工具仅涵盖确定且有文档说明的部分,不涉及其他内容:
pass不代表保证送达。它只表示没有任何有文档说明的规则会拦截消息。- 自愿订阅检查会确认是否包含相关措辞,而不是确认是否确有同意。同意记录保存在你的记录中,而非消息文本里。
- v1 仅检测英文(检测结果包含
locale字段)。 - 配额计算是根据公开上限进行的静态示例,不反映你账户的实时状态。
- 此工具不会根据电话号码推断线路类型(VoIP/移动线路)。如果你有查询服务,请传入
destination_line_type;libphonenumber-js 可提供免费的近似判断,但对美国号码有已知限制。
相关先例
- TwilioDevEd/message-segment-calculator 是分段计算的参考实现。此库会测试与其是否一致,而不是与之竞争。
- sms_policy_checker 使用大模型层为 Ruby/Rails 提供类似的合规检查;willitsend 则是确定性、零网络请求的对应方案。
- 网页检查工具(10dlccheck.com、Calilio)以面向用户的表单形式覆盖了部分相同规则。willitsend 让这些检查可嵌入使用:一个库,Agent 每秒可调用十万次,并提供引用依据。
- 据我们所知,这是首个用于发送前 SMS 内容合规检查的 MCP 工具。欢迎指正。
隐私
不收集遥测数据。除非你使用 API 密钥启用了可选的能力查询,否则不会发起网络请求。此工具绝不会记录、存储或在输出中回显消息正文。交互式试用工具在你的浏览器中运行,不会向任何地方发送数据。
未来
- 引擎最适合部署在服务端:在发送端点本身加入
dry_run参数。该库采用即插即用的设计,是一个无状态的纯函数,核心部分不依赖任何其他组件。 - 闭环:将预检判定与投递状态 Webhook 关联起来,根据实际结果而非假设来衡量规则的准确性。过滤过程只能部分观测到(某些拦截会明确报错,另一些则没有反馈),但这些信号足以诚实地评估一套确定性规则。要学习不透明层本身,需要汇总许多发送方的发送数据;这属于平台的职责,而不是无状态客户端工具的职责。
- 通过 MCP Apps 扩展呈现投递能力报告卡。
- 添加英语以外的语言包。
开发
git clone https://github.com/abryfs/willitsend && cd willitsend
npm install # builds via prepare
npm test # 191 tests: unit, Twilio parity, held-out acceptance
npm run bench
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。