willitsend について
willitsend は、SMS/iMessage を送信する AI エージェント向けの決定論的な事前チェックツールです。メッセージを送信・保存せずに、分割数を計算し、文書化されたコンプライアンス上のリスクを示します。
主な機能
- 01初回メッセージの要件チェック
- 02文字コードとSMS分割数の計算
- 03iMessageからのフォールバック警告
- 04判定、根拠、修正案の提示
- 05MCP サーバー、CLI、ライブラリを提供
活用シーン
- 01初回SMSの配信停止案内を確認
- 02Unicodeによる分割数増加を検出
- 03SMSへのフォールバックで失われる機能を確認
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 を指定すると、トレースに 1 日あたりの送信上限の静的な例が追加されます。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":検出事項ごとに 1 行(深刻度、ルール、修正)で出力し、トークン数を約 10 分の 1 に削減します。同じエンジンと判定を使用し、実行に必要な情報はすべて含まれます。大量処理のループに使用してください。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 エージェントがテキストメッセージを送信します。通信事業者は準拠していないメッセージをひそかに破棄しますが、API はそれを通知しません。willitsend は、モデルと通信事業者の間に欠けていたチェック機能です。送信前の SMS/iMessage を確定的に事前検証し、ひそかなフィルタリング、セグメント数の急増、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 エージェントが自律的にテキストメッセージを送信するようになりましたが、モデルと通信事業者の間には何もありません。オプトアウトの一文を省いたエージェントにはエラーも配信もありません。絵文字を1つ加えたエージェントは、160文字のメッセージが課金対象の3セグメントに増えても気づきません。
willitsend は、欠けていたチェック機能です。下書きメッセージのための確定的かつステートレスなリンターです。テキストとコンテキストを入力すると、判定結果と根拠が返されます。メッセージの送信も保存もしません。
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
ルールをエージェントへのプロンプトに入れるだけではだめですか?
もっともな疑問です。AgentPhone のドキュメントはエージェントが読み取れるため、すべての生成呼び出しでモデルに読み込ませ、ルールに従うよう求めることもできます。しかし、2つの問題があり、さらにコストもかかります。
- 生成は確率的ですが、検証は確定的です。 ルールをコンテキストに含めたモデルは、たいていは従います。このチェッカーは毎回同じ判定を返し、失敗した場合も、ひそかにフィルタリングされたメッセージではなく、明示的な指摘を返します。作成時のガイダンスと送信前の検証は相互に補完するものです。このリポジトリには両方が含まれています(エージェントスキルが作成時のガイダンスにあたります)。
- モデルはセグメント数を計算できません。 セプテットのカウント、GSM-7 拡張文字、スマートクォート1つによるエンコーディングの切り替え、配置を考慮した詰め込みなど、トークンベースのモデルは、まさにこうした計算が構造的に不得意です。プロンプトをどれだけ工夫しても解決できません。300行の確定的な関数で実現できます。
- トークン数の計算値(実測)(実際のページを対象にした文字数÷4の概算、2026年7月):
| 方法 | メッセージあたりのトークン数 | 入力100万トークンあたり3ドルで1日10,000件の場合 |
|---|---|---|
| AgentPhone のドキュメントページを2つとも、各生成呼び出しに読み込ませる | ~7,700 | ~231ドル/日(プロンプトキャッシュを90%利用すると約23ドル/日) |
preflight_message 呼び出しの結果(デフォルト) | ~250(+ツール定義にセッションごとに約1,450) | ~7.50ドル/日 |
response_format: "concise" を指定した preflight_message | 合格時は約4、ブロック時は約44(実測) | 合格率90%の場合、約0.35ドル/日 |
デフォルトモードでは、メッセージあたりのトークン数を約30分の1に削減できます。簡潔モードでは数百分の1になります。また、チェッカー自体の計算コストはゼロに近い値です(ローカルで約4µs、API 不要)。代わりにルールをシステムプロンプトにまとめた場合、このスキルファイルのように約950トークンで済み、作成時のメリットは得られますが、検証機能もセグメント数の計算もありません。大量送信を行う場合に特に効果があります。このツールは、大量のテキストメッセージを送信する AI エージェントのために存在します。
クイックスタート
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 エージェント向け — このブロックをエージェントに貼り付けるか、この 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 やシェルパイプラインに組み込めます。
チェック内容
| ルール | 重大度 | ソース |
|---|---|---|
first-message.opt-out:連絡先への最初のメッセージには、配信停止の方法を記載する必要があります | ブロック | AgentPhone docs |
first-message.brand:最初のメッセージにはブランドを明記する必要があります(指定された brand_name のみを対象に確認し、ブランド名を推測することはありません) | ブロック | AgentPhone docs |
first-message.opt-in:最初のメッセージでは、連絡先がオプトインした方法に触れることが推奨されます | 警告 | AgentPhone docs |
first-message.media-only:コンプライアンスに関するテキストを画像に含めることはできません | 警告 | AgentPhone docs |
segments.unicode-blowup:GSMに対応していない文字が1文字でもあると、メッセージ全体がUCS-2で再エンコードされます | 警告 | AgentPhone docs |
imessage.feature-fallback:SMSにフォールバックすると、send_style、スレッド形式の返信、カルーセルは通知なく失われます | 警告 | AgentPhone docs |
imessage.invalid-send-style、imessage.carousel-count:無効なエフェクト名、またはドキュメントに記載された2~20件の範囲外のカルーセル | ブロック | AgentPhone docs |
imessage.new-contact-cap:iMessageでは、新しい連絡先への送信は回線ごとに1日50件までです | 情報 | AgentPhone docs |
destination.invalid、destination.voip:宛先の形式が不正な場合、既知のVoIP回線の場合(回線種別を電話番号から推測することはありません) | ブロック/警告 | send API / AgentPhone docs |
content.shaft:通信事業者がフィルタリングする性・アルコール・銃器・タバコ関連の用語(ヘイトスピーチにはキーワードルールがありません。単語リストでは検出できず、検出できるふりもしません) | 警告 | Twilio guidelines |
content.url-shortener:一般公開されている短縮URLサービス(bit.ly、tinyurl、…)は、CTIAの専用短縮URLに関するガイダンスに抵触します | 警告 | CTIA MP&BP (PDF) |
content.spam-patterns:ALL CAPSの連続、$$$、!!! | 情報 | ヒューリスティック |
ルールは、ラベル付きの2つの階層に分かれています。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)、セグメント数の詳細な計算(エンコーディング、セプテット数とコード単位数、配置を考慮したセグメントごとのパッキング)、さらに10DLCキャンペーンのティアが指定されている場合は、公表されている1日あたりのセグメント上限に対するこのメッセージのコストが含まれます。
ベンチマーク
ここに記載されているすべての数値は、リポジトリから再現できます。アカウントは不要です。
- セグメント計算の同等性: 固定された126件のベクトルコーパス全体で、エンコーディングとセグメント数についてTwilioの参照用セグメント計算ツールと一致します。対象には、GSM-7/UCS-2の境界、拡張文字、絵文字、ZWJシーケンス、決定論的なファジースイープが含まれます。コーパスとテストスイートは
test/parity.test.tsにあります。npm testを実行してください。 - レイテンシ: 1メッセージあたりの中央値は約4µs、1スレッドで毎秒約100,000メッセージです(Node 22、Apple Silicon搭載ノートパソコン、単一パスのコードポイントテーブル、文字ごとの割り当てなし)。お使いの環境で
npm run benchを実行してください。送信するすべてのメッセージを事前チェックするほうが、ログに記録するより低コストです。 - 公表されている上限値に基づくコスト計算例: 個人事業主の10DLCキャンペーンでは、1日にT-Mobileのセグメントを1,000件(AgentPhoneの公表推定値によると、米国全体で約3,000件)利用できます。絵文字が1つ紛れ込み、GSM-7で2セグメントだったメッセージがUCS-2の4セグメントになると、その上限で送れるメッセージ数は半分になります。同じテキスト、同じ宛先で、リーチは半分です。
配信率が向上するという主張はここにはありません。その主張には、こちらにない送信データが必要です。
できないこと
キャリアは、ルールを公開していないシステムでフィルタリングを行います。ポリシーの層は公開されています(行動規範、コンテンツカテゴリ、送信量の上限)が、実行時のフィルタリング判断は意図的に不透明であり、結果の一部しか観測できません(明示的なフィルターエラーを返すブロックもあれば、メッセージが受理されても配信されないままになる場合もあります)。このツールが対象とするのは、決定論的で文書化された層のみです。
passは配信を保証するものではありません。メッセージを妨げる、文書化された要因がないことを意味します。- オプトインのチェックでは、同意を示す文言があるかどうかを確認するだけで、実際に同意が存在するかは確認しません。同意はメッセージ本文ではなく、記録に保存されています。
- v1の検出は英語のみです(検出結果には
localeフィールドが含まれます)。 - クォータの計算は、公表されている上限値に基づく静的な例示であり、お客様のアカウントの現在の状態を示すものではありません。
- このツールは、電話番号から回線種別(VoIP/モバイル)を推測しません。照会サービスがある場合は、
destination_line_typeを渡してください。libphonenumber-jsを使えば無料でおおよその判定ができますが、米国の番号には既知の制限があります。
先行事例
- TwilioDevEd/message-segment-calculatorは、セグメント計算の参照実装です。このライブラリは、それと競合するのではなく、同等性をテストしています。
- sms_policy_checkerは、LLM層を備えたRuby/Rails向けの同様のコンプライアンスチェックを提供します。willitsendは、決定論的でネットワーク接続を必要としない代替ツールです。
- ウェブ上のチェッカー(10dlccheck.com、Calilio)は、重複するルールを人間向けのフォームでチェックします。willitsendなら、これらのチェックを組み込み可能にできます。引用情報を伴い、エージェントが毎秒10万回呼び出せるライブラリです。
- 確認できる限り、これは送信前の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 サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。