SMS
OpenClaw 通过 Twilio 电话号码或 Messaging Service 接收和发送 SMS/MMS。网关会注册一个 webhook 路由(默认 /webhooks/sms),默认验证 Twilio 请求签名,通过 Twilio Messages API 发送回复,并记录出站投递回调。
状态:官方插件,需单独安装。支持 SMS 文本和 MMS 附件,仅限直接消息。
SMS 的默认 DM 策略为配对。
检查 webhook 暴露情况和发送方访问控制。
跨渠道诊断和修复手册。
开始之前¶
你需要:
- 已使用
openclaw plugins install @openclaw/sms安装官方 SMS 插件。 - 一个拥有支持 SMS 的电话号码的 Twilio 账户,或一个 Twilio Messaging Service。MMS 需要支持 MMS 的发送方;原生 MMS 投递还取决于目的国家和运营商。
- Twilio Account SID 和 Auth Token。
- 一个可访问你的 OpenClaw 网关的公共 HTTPS URL。
- 发送方策略选择:
pairing(默认)用于私人使用,allowlist用于预先批准的电话号码,或open仅用于有意公开的 SMS 访问。
如果一个 Twilio 号码同时具备两种能力,它可以同时用于 SMS 和语音通话。SMS webhook 和语音 webhook 在 Twilio 中分别配置,并使用不同的网关路径;本页仅介绍 SMS webhook。
美国 A2P / 10DLC 投递¶
应用程序从美国本地 10DLC 号码向美国收件人发送的 SMS 和 MMS 需要美国 A2P 10DLC 注册。免费电话号码和短代码使用单独的验证流程。这与 OpenClaw 渠道设置是分开的:webhook 签名验证、配对和出站凭据可能都正确,但运营商仍可能阻止或过滤投递。
在依赖美国 10DLC 发送方之前,请在 Twilio 中确认:
- 账户已付费;Twilio 试用账户无法注册 A2P 10DLC。
- Twilio Trust Hub 中已批准主要或次要合规资料。
- 品牌(Brand)和活动(Campaign)已注册并获批。
- Twilio 电话号码的 A2P 状态为
REGISTERED,并且位于与已批准活动关联的 Messaging Service 的发送方池(Sender Pool)中,或者你在此处配置的messagingServiceSid就是该已批准服务。 - 活动描述了真实的 OpenClaw 消息使用场景,并包含匹配的示例消息。
- 每个网站、关键词、线下、纸质或二维码订阅路径都已完整描述。如果该流程不公开可见,请提供可公开访问的截图或其他证据。
- 消息同意是自愿的,并且独立于必需的服务条款、账户创建或购买,并包含 Twilio 要求的隐私政策、条款、频率、费率和退订披露。
- 你保留同意证明,标识发送方,遵守标准的一步退订关键词,并且不购买、租用、出售或转让同意。退订后,除非收件人再次订阅,否则只发送一条确认消息。
以 Twilio 作为当前要求的权威来源:A2P 10DLC 概述、注册快速入门,以及所需业务和活动信息。本节是设置指南,不是法律建议。
如果 Twilio 在注册审核期间拒绝品牌或活动,请在将该发送方用于 OpenClaw 之前先在 Twilio 中修复。30909 表示消息流程或行动号召不完整或无法验证。30923 表示消息同意被要求作为服务、账户创建或购买的条件,或与条款捆绑。30893 表示示例消息与声明的使用场景不匹配。
快速设置¶
1. 安装插件
2. 创建或选择 Twilio 发送方
在 Twilio 中,打开电话号码 > 管理 > 活动号码,并选择一个支持 SMS 的号码。若要发送附件,请选择一个也支持 MMS 的号码。保存:
- Account SID,例如
ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - Auth Token
- 发送方电话号码,例如
+15551234567
如果你使用 Messaging Service 而不是固定发送方号码,请保存 Messaging Service SID,例如 MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx。
3. 配置 SMS 渠道
将此保存为 sms.patch.json5 并修改占位符:
{
channels: {
sms: {
enabled: true,
accountSid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authToken: "twilio-auth-token",
fromNumber: "+15551234567",
publicWebhookUrl: "https://gateway.example.com/webhooks/sms",
dmPolicy: "pairing",
},
},
}
应用它:
openclaw config patch --file ./sms.patch.json5 --dry-run
openclaw config patch --file ./sms.patch.json5
4. 将 Twilio 指向网关 webhook
在 Twilio 电话号码设置中,打开**消息**,并将**收到消息时**设置为:
使用 HTTP `POST`。默认本地路径为 `/webhooks/sms`;如果你需要不同路由,请修改 `channels.sms.webhookPath`。
5. 暴露确切的 SMS webhook 路径
你的公共 URL 必须将 SMS 路径路由到网关进程(默认端口 `18789`)。同一路径用于入站 Twilio webhook,以及 OpenClaw 发送 MMS 时的短期、带令牌的附件。如果你使用 Tailscale Funnel 进行本地测试,请显式暴露 `/webhooks/sms`:
tailscale funnel --bg --set-path /webhooks/sms http://127.0.0.1:<gateway-port>/webhooks/sms
tailscale funnel status
语音通话和 SMS 使用不同的 webhook 路径。如果同一个 Twilio 号码同时处理两者,请在 Twilio 和你的隧道中保留两条路由配置。
6. 启动 Gateway 并批准第一个发送者
向 Twilio 号码发送一条短信。第一条消息会创建一个配对请求。批准它:
配对码在 1 小时后过期。
配置示例¶
所有键都位于 channels.sms 下(每个账户位于 channels.sms.accounts.<id> 下):
| 键 | 默认值 | 用途 |
|---|---|---|
enabled |
true |
启用或禁用该渠道/账户。 |
accountSid |
— | Twilio Account SID (AC...)。 |
authToken |
— | Twilio Auth Token;明文字符串或 SecretRef。 |
fromNumber |
— | E.164 发送者号码。 |
messagingServiceSid |
— | 当无法解析 fromNumber 时使用的 Messaging Service SID (MG...)。 |
defaultTo |
— | 发送流程未指定明确目标时的默认目标。 |
webhookPath |
/webhooks/sms |
入站 Twilio webhook 的 Gateway HTTP 路径。 |
publicWebhookUrl |
— | 公共 Twilio webhook URL;签名验证和出站 MMS 托管所需。 |
dangerouslyDisableSignatureValidation |
false |
跳过 X-Twilio-Signature 检查;仅用于本地隧道测试。 |
dmPolicy |
"pairing" |
pairing、allowlist、open 或 disabled。 |
allowFrom |
[] |
允许的发送者号码(E.164),或在 dmPolicy: "open" 时使用 "*"。 |
textChunkLimit |
1500 |
每条出站 SMS 分片的最大字符数。 |
accounts, defaultAccount |
— | 多账户映射和默认账户 id。 |
配置文件¶
当你希望渠道定义随 Gateway 配置一起保存时,使用配置文件方式:
{
channels: {
sms: {
enabled: true,
accountSid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authToken: "twilio-auth-token",
fromNumber: "+15551234567",
publicWebhookUrl: "https://gateway.example.com/webhooks/sms",
dmPolicy: "pairing",
},
},
}
环境变量¶
环境变量仅适用于默认账户;配置值优先于环境变量值。
| 变量 | 映射到 |
|---|---|
TWILIO_ACCOUNT_SID |
accountSid |
TWILIO_AUTH_TOKEN |
authToken |
TWILIO_PHONE_NUMBER (alias TWILIO_SMS_FROM) |
fromNumber |
TWILIO_MESSAGING_SERVICE_SID |
messagingServiceSid |
SMS_PUBLIC_WEBHOOK_URL |
publicWebhookUrl |
SMS_WEBHOOK_PATH |
webhookPath |
SMS_ALLOWED_USERS |
allowFrom(逗号分隔) |
SMS_TEXT_CHUNK_LIMIT |
textChunkLimit |
SMS_DANGEROUSLY_DISABLE_SIGNATURE_VALIDATION |
dangerouslyDisableSignatureValidation("true") |
export TWILIO_ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TWILIO_AUTH_TOKEN="<twilio-auth-token>"
export TWILIO_PHONE_NUMBER="+15551234567"
export SMS_PUBLIC_WEBHOOK_URL="https://gateway.example.com/webhooks/sms"
然后在配置中启用该渠道:
SecretRef 认证令牌¶
authToken 可以是 SecretRef(source: "env" | "file" | "exec" | "store")。当 Gateway 应从 OpenClaw secrets 运行时解析 Twilio Auth Token,而不是存储明文配置时,使用此方式:
{
channels: {
sms: {
enabled: true,
accountSid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authToken: { source: "env", provider: "default", id: "TWILIO_AUTH_TOKEN" },
fromNumber: "+15551234567",
publicWebhookUrl: "https://gateway.example.com/webhooks/sms",
dmPolicy: "pairing",
},
},
}
所引用的环境变量或 secret provider 必须对 Gateway 运行时可见。更改主机环境变量后,重启受管理的 Gateway 进程。
Messaging Service 发送者¶
当 Twilio 应通过 Messaging Service 选择发送者时,使用 messagingServiceSid 代替 fromNumber:
{
channels: {
sms: {
enabled: true,
accountSid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authToken: "twilio-auth-token",
messagingServiceSid: "MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
publicWebhookUrl: "https://gateway.example.com/webhooks/sms",
dmPolicy: "pairing",
},
},
}
如果在配置和环境变量解析后同时存在 fromNumber 和 messagingServiceSid,则使用 fromNumber。
默认出站目标¶
当自动化或代理发起的发送流程未指定显式目标时,如果需要有默认目的地,请设置 defaultTo:
{
channels: {
sms: {
enabled: true,
accountSid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authToken: "twilio-auth-token",
fromNumber: "+15551234567",
defaultTo: "+15557654321",
publicWebhookUrl: "https://gateway.example.com/webhooks/sms",
},
},
}
访问控制¶
channels.sms.dmPolicy 控制直接短信访问:
pairing(默认):未知发件人会收到配对码;使用openclaw pairing approve sms <CODE>批准。allowlist:仅处理allowFrom中的发件人。空的allowFrom会拒绝所有发件人(网关会记录启动警告)。open:配置校验要求allowFrom包含"*"。如果没有通配符,只有列出的号码可以聊天。disabled:所有入站私信都会被丢弃。
allowFrom 条目应为 E.164 电话号码,例如 +15551234567。接受 sms: 和 twilio-sms: 前缀,并会进行规范化。对于私人助理,建议使用 dmPolicy: "allowlist" 并显式指定电话号码:
{
channels: {
sms: {
enabled: true,
accountSid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authToken: "twilio-auth-token",
fromNumber: "+15551234567",
publicWebhookUrl: "https://gateway.example.com/webhooks/sms",
dmPolicy: "allowlist",
allowFrom: ["+15557654321"],
},
},
}
发送短信¶
选择 SMS 通道后,目标可以接受裸 E.164 号码或 sms: 前缀:
当通道选择是隐式时,twilio-sms: 前缀会选择此通道,而不会占用 sms: 服务前缀;iMessage 使用该前缀为其自身目标选择运营商短信投递:
CLI 要求显式指定 --target。defaultTo 用于自动化和代理发起的投递路径,在这些路径中目标可以从通道配置中解析。
来自入站短信会话的代理回复会自动通过已配置的 Twilio 发件人返回给发件人。
短信输出为纯文本。OpenClaw 会移除 Markdown,展平围栏代码块,将链接重写为 label (url),并在通过 Twilio 发送前将长回复拆分为最多 textChunkLimit 个字符的块(默认 1500)。
发送 MMS¶
使用常规的结构化媒体字段或 CLI --media 选项:
openclaw message send \
--channel sms \
--target sms:+15551234567 \
--message "photo" \
--media ./photo.jpg
OpenClaw 通过共享的出站媒体策略加载附件,将其临时存储在插件范围的 SQLite 状态中,并在已配置的 publicWebhookUrl 路径上向 Twilio 提供一个带令牌的 HTTPS URL。支持仅媒体发送。
channels.sms.mediaMaxMb 以 MiB 为单位限制每个入站和出站附件。所选账户的 mediaMaxMb 会覆盖通道根配置,然后 agents.defaults.mediaMaxMb 提供回退值。此按附件设置不会替代下面 Twilio 的总消息或媒体类型上限。出站图像在发送前可能会被优化。
生成的媒体 URL 是一种 bearer 能力,10 分钟后过期。将其完整查询字符串视为机密:配置反向代理和访问日志以省略查询字符串,或隐藏每个查询值。OpenClaw 网关路由诊断仅记录路径名,但无法控制上游代理日志。
出站 OpenClaw 投递会附加一个媒体项。OpenClaw 将 JPEG、JPG、PNG 和 GIF 附件限制为 5,000,000 字节;其他受支持的媒体类型限制为 500,000 字节。application/vcard 附件必须为仅媒体;Twilio 不接受带说明文字的此类附件。目标运营商可能执行更小的限制或拒绝不支持的格式。Twilio 必须能够在没有 HTTP 身份验证的情况下获取生成的 URL,因此 publicWebhookUrl 不能包含嵌入的 userinfo;基于查询的反向代理令牌会被保留。
对于入站 MMS,OpenClaw 最多处理 10 个附件,并最多下载总计 5 MiB。任何额外或不可用的附件都会生成可见的不可用媒体通知,而不是丢弃已签名的消息或静默投递一个空回合。下载仅在发件人授权后发生,并带有 Twilio 身份验证和 api.twilio.com 主机限制。
投递状态¶
每次成功出站发送后,如果响应中包含初始 Twilio API 状态,OpenClaw 会存储该状态。当 publicWebhookUrl 有效时,每条出站消息还会向 Twilio 提供一个派生的 StatusCallback URL,该 URL 保留其基础 URL 和连接覆盖项,同时添加所需的投递回调重试设置。无效或过大的派生 URL 会被省略。
后续的投递回调会更新同一插件范围的 SQLite 记录。语义重试会去重,较早的状态转换不能使终态回退,冲突的终态观察会被报告为 conflicted,而不是选择一个错误的获胜者。记录包含消息 SID、状态/错误元数据和时间戳,但不包含消息正文或电话号码地址。每条记录在其最新观察后最多保留 30 天,同时受插件级 5,000 条消息上限和最旧记录淘汰机制约束。
验证设置¶
网关启动后:
- 确认网关日志显示 SMS webhook 路由。
- 运行 Twilio 侧探测(检查已配置的 Twilio webhook URL/方法、最近的入站错误以及最近存储的出站投递状态):
- 从你的手机向 Twilio 号码发送一条短信。
- 运行
openclaw pairing list sms。 - 使用
openclaw pairing approve sms <CODE>批准配对码。 - 再发送一条短信,并确认代理会回复。
如需仅测试出站消息,请使用:
从 macOS iMessage/SMS 进行端到端测试¶
在可以通过“信息”应用发送运营商 SMS 的 Mac 上,你可以使用 imsg 来驱动发送端,而无需触碰手机:
imsg send --to "+15551234567" --service sms --text "OpenClaw SMS E2E $(date -u +%Y%m%dT%H%M%SZ)" --json
openclaw pairing list sms
openclaw pairing approve sms <CODE>
imsg send --to "+15551234567" --service sms --text "reply exactly SMS pong" --json
第一条消息应创建配对请求。第二条消息应通过 Twilio 接收代理回复。
Webhook 安全¶
默认情况下,OpenClaw 使用 publicWebhookUrl 和 authToken 验证 X-Twilio-Signature。请确保 publicWebhookUrl 的端点部分与在 Twilio 中配置的 URL 逐字节对齐,包括协议、主机、路径和查询字符串。按照 Twilio 的要求,OpenClaw 会在签名计算中排除 Twilio 连接覆盖 片段(#...)。
Webhook 路由还独立于签名验证强制执行以下规则:
- 仅允许
POST。 - 每个短信账户、Webhook 路由和解析后的客户端地址,每分钟失败请求的预算为 300 次。所有请求都会计入此预算,但仅当正文解析或 Twilio 签名验证失败时,才会应用 HTTP 429。
- 已签名的投递回调会在入站发送者配额之前进行分类,并在返回 HTTP 200 之前提交到有界、插件作用域的 SQLite 状态。它们不消耗入站调度配额:这些配额保护原始入站消息准入和下游代理调度。相反,投递持久化在每个短信账户路由上有一个独立的每分钟 3,000 次回调的安全熔断,超过该限制时会返回 HTTP 503,且不包含持久接受标记。这是故障关闭的过载保护,而非无损背压。当签名验证被禁用时,投递回调在持久化之前首先使用更严格的每分钟 30 次解析客户端地址上限。
- 在正文解析和签名验证通过后,每个短信账户、Webhook 路由和已验证发送者每分钟的可调度回调限制为 30 个已接受回调(超过则返回 HTTP 429)。发送者键是规范化且受签名覆盖的
From值,因此等效的 SMS/RCS 地址形式共享同一个预算,一个刷屏发送者只会耗尽自己的预算,而来自 Twilio 共享出口地址后面其他发送者的回调仍然可调度。无效或缺失的发送者值共享一个单独的空发送者预算。 - 每个短信账户和 Webhook 路由的已验证回调总量上限为每分钟 300 个已接受回调。这限制了来自许多不同已签名发送者的持久入站压力,而不会重新造成共享出口的交叉限流。如果签名验证被禁用,则无法认证
From;此时将应用更严格的每分钟 30 个解析客户端地址调度上限,而不是已验证发送者和总量策略。 - 客户端地址通过共享 Gateway 可信代理规则解析。如果
gateway.trustedProxies包含转发 Twilio 回调的反向代理,OpenClaw 将基于转发的客户端地址来应用地址相关限制;否则,将回退到直接套接字地址。 - 入站负载必须携带一个非空的
AccountSid,且该值必须与配置的accountSid完全匹配。直拨号码回调必须指向配置的fromNumber;Messaging Service 回调必须携带配置的MessagingServiceSid。原始回调首先被提交到持久入站队列并被确认;随后,身份不匹配会在排空期间被标记为永久无效负载失败,并且永远不会被调度或允许下载媒体。 - 带有缺失或不匹配
AccountSid的投递回调会被确认、记录,并有意不存储。 - 重放的
MessageSid值由持久入站队列去重。已完成消息的墓碑记录保留 24 小时(每个账户最多 20,000 条);永久失败墓碑记录保留 30 天(最多 1,000 条)。 - 投递观测使用来源、消息 SID、规范化状态、错误代码和运营商完成日期的语义化非 PII 指纹。一条出站消息的多个状态保持相互独立。记录在其最近一次观测后 30 天过期,而 5,000 条消息的上限可能会更早淘汰较旧的记录。
- 超过 32 KB 的请求正文将被拒绝。
OpenClaw 会为生成的投递 StatusCallback URL 添加 5xx 重试策略和重试次数,以便 Twilio 可以重试失败的 SQLite 提交或过载的投递状态路由。Twilio 默认不会重试 HTTP 429。#rp=4xx 和 #rp=all 连接覆盖可选用 4xx 重试,但 Twilio 将完整重试事务限制在 15 秒内。429 和投递状态 503 都不能保证后续恢复;当最终状态完整性很重要时,请使用对账机制。错过的中间状态转换无法重建。
对于对完整性敏感的工作流,请持久化 Message SID,并通过轮询 Twilio 的 Message 资源来对账过期的非终止记录。Twilio 的 投递日志记录指南 建议:当消息在 12 小时内未达到 delivered 或 undelivered 状态时进行轮询,因为状态回调可能未到达。SMS 回退 URL 不能替代此机制:它只处理获取或执行 入站 SMS TwiML webhook 时的失败。
仅用于本地隧道测试时,你可以设置:
请勿在公共 Gateway 上使用已禁用的签名验证。
多账户配置¶
当你运营多个 Twilio 号码时,请使用 accounts:
{
channels: {
sms: {
accounts: {
support: {
enabled: true,
accountSid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authToken: "twilio-auth-token",
fromNumber: "+15551234567",
publicWebhookUrl: "https://gateway.example.com/webhooks/sms/support",
webhookPath: "/webhooks/sms/support",
dmPolicy: "allowlist",
allowFrom: ["+15557654321"],
},
},
},
},
}
每个账户必须使用不同的 webhookPath;Gateway 会拒绝注册路径已被其他账户占用的 webhook 路由。TWILIO_*/SMS_* 环境变量回退仅适用于默认账户;设置 defaultAccount 可更改默认账户。
故障排查¶
Twilio 返回 403 或 OpenClaw 拒绝 webhook¶
请检查 publicWebhookUrl 是否与在 Twilio 中配置的 URL 完全匹配,包括协议(scheme)、主机(host)、路径(path)和查询字符串(query string)。Twilio 会对公共 URL 字符串进行签名,因此代理重写和备用主机名可能会破坏签名验证。
如果 Twilio 收到了持久确认,但没有出现配对请求,请检查 Gateway 日志中是否存在永久性无效载荷失败。确认回调中的 AccountSid 和 To 与所配置的账户及 fromNumber 匹配,或者其 MessagingServiceSid 与所配置的 Messaging Service 匹配。
未出现配对请求¶
检查 Twilio 号码的 Messaging webhook URL 和方法。它必须指向 SMS webhook URL 并使用 POST。同时确认 Gateway 可通过公共互联网或你的隧道访问。
如果 Twilio 消息日志显示错误 11200,则表示 Twilio 已接受入站 SMS,但无法访问你的 webhook。请检查:
- Twilio Messaging > 收到消息时 指向
publicWebhookUrl。 - 方法为
POST。 - 隧道或反向代理需要暴露确切的
webhookPath;对于 Tailscale Funnel,请运行tailscale funnel status并确认列出了/webhooks/sms。 publicWebhookUrl需要使用与 Twilio 发送时相同的协议、主机、路径和查询字符串,以便签名验证可以重现被签名的 URL。
openclaw channels status --channel sms --probe 会同时显示不匹配的 Twilio webhook 设置以及最近的 11200 错误。
出站发送失败¶
确认 accountSid、authToken 已解析,并且 fromNumber 或 messagingServiceSid 至少有一项已解析。Twilio 试用账户只能向账户注册国家/地区内的已验证收件人发送消息,并且必须使用 Twilio 预定义的内容;不支持自定义 SMS 正文。试用账户也无法注册 A2P 10DLC,因此在注册美国 10DLC 发件人之前,请先升级账户。
Twilio 接受发送但投递稍后失败¶
首先查看 OpenClaw 存储的投递观测结果:
如果最近的出站状态为 failed 或 undelivered,请使用其 messageSid 在 Twilio 中查看最终的 Message 状态和错误代码。30034 表示发件人未注册,或者不在与已批准 Campaign 关联的 Messaging Service 的 Sender Pool 中。30035 表示 Twilio 仍在注册、注销或重新分配该号码;请等待其状态变为 REGISTERED 后再发送。
消息到达但 agent 未回复¶
检查 dmPolicy 和 allowFrom。使用默认的 pairing 策略时,发件人必须先获得批准,之后才会处理常规的 agent 轮次。
相关¶
- Channels 概述
- Secrets 管理 — 从 SecretRef 解析 Twilio auth token
- 配置 — gateway — 上文使用的
gateway.trustedProxies及其他 Gateway 设置
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw