migrate-to-sent
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMigrate to Sent
迁移至Sent
Every migration from a major CPaaS provider hits the same five translation problems. Work them in this order, because the first one silently doubles cost and is invisible in tests.
从主流CPaaS提供商迁移时都会遇到相同的五大转换问题,请按以下顺序处理,因为第一个问题会悄无声息地使成本翻倍,且在测试中无法被发现。
1. Ordered fallback becomes automatic routing
1. 有序回退转为自动路由
Incumbent platforms express cross-channel delivery through different caller-side arrays, failover objects, messaging-service features, or application-level priority configuration. Do not assume those shapes have a direct Sent request-field equivalent.
Sent's array is a broadcast list. Porting an ordered array produces one message and one charge per recipient-channel pair, which passes tests and multiplies production spend. The correct translation is automatic routing — omit or send — which lets the platform select a route and reroute across up to three channel-and-provider pairs on the same . Details belong to ; the migration rule is simply: never port an ordered channel list.
channelchannel["sent"]message_idsent-routing-strategist现有平台通过不同的调用端数组、故障转移对象、消息服务功能或应用级优先级配置来实现跨渠道投递。不要假设这些结构在Sent中有对应的请求字段。
Sent的数组是一个广播列表。移植有序数组会为每个接收者-渠道对生成一条消息并收取一次费用,这种情况在测试中不会报错,但会使生产环境的成本翻倍。正确的转换方式是使用自动路由——省略参数或传入——这样平台会选择一条路由,并在同一个下最多跨三个渠道-提供商对重新路由。详细信息可参考;迁移规则很简单:切勿移植有序渠道列表。
channelchannel["sent"]message_idsent-routing-strategist2. Status vocabularies do not line up
2. 状态术语无法直接匹配
Incumbent statuses map onto Sent's, but Sent adds two states that have no equivalent and that break naive retry logic.
| Sent status | Closest incumbent analogue | Migration note |
|---|---|---|
| Twilio | Accepted, not sent |
| no analogue | Route chosen; fires again on reroute |
| Twilio | Provider handoff only |
| | The first proof of handset receipt |
| Twilio | WhatsApp and RCS only |
| | May still reroute; not necessarily final |
| Twilio error 21610 (opt-out) | Policy gate. Never retry |
| account-level errors | Account precondition. Fix the account, then resend |
| no analogue | Quiet-hours parking; resumes automatically |
Two consequences for ported code. Handlers that treat every non-delivered terminal state as retryable will retry consent blocks, which is a compliance failure rather than a bug. And handlers keyed on numeric provider error codes — Twilio's is the classic — must be rewritten against Sent's string families.
21610error.code现有平台的状态可以映射到Sent的状态,但Sent新增了两个无对应项的状态,这会破坏简单的重试逻辑。
| Sent状态 | 最接近的现有平台对应状态 | 迁移注意事项 |
|---|---|---|
| Twilio | 已接受,但尚未发送 |
| 无对应项 | 已选择路由;重新路由时会再次触发 |
| Twilio | 仅完成提供商交接 |
| 所有平台的 | 首次确认手机已接收 |
| Twilio | 仅适用于WhatsApp和RCS |
| 所有平台的 | 仍可能重新路由;不一定是最终状态 |
| Twilio错误码21610(退订) | 策略关卡。切勿重试 |
| 账户级错误 | 账户前置条件。修复账户后重新发送 |
| 无对应项 | 静默时段暂存;会自动恢复发送 |
这对移植代码有两个影响。将所有未投递的终端状态视为可重试的处理程序会重试合规拦截请求,这属于合规失败而非程序bug。而依赖数字提供商错误码(典型如Twilio的)的处理程序必须重写为基于Sent的字符串类型族。
21610error.code3. Webhook verification is a rewrite, not a port
3. Webhook验证需重写,而非移植
No two providers sign the same way, and no Sent SDK ships a verifier.
| Provider | Scheme |
|---|---|
| Twilio | |
| Sinch | HMAC-SHA256 over |
| Infobip | Basic, HMAC-SHA256 over the raw body, or OAuth on a notification profile; the header name is account-configured |
| Vonage | JWT in |
| MessageBird/Bird | |
| Sent | |
Sent's key is the signing secret with stripped and the remainder base64-decoded, compared in constant time, with timestamps outside 300 seconds rejected. Because Sent provides no per-event id, dedupe keys must be derived from payload semantics. Build the receiver with rather than adapting the incumbent's verifier.
whsec_sent-webhook-engineer没有两个提供商的签名方式相同,且Sent SDK不提供验证器。
| 提供商 | 签名方案 |
|---|---|
| Twilio | |
| Sinch | 基于 |
| Infobip | Basic认证、基于原始请求体的HMAC-SHA256签名,或通知配置文件上的OAuth;头部名称由账户配置 |
| Vonage | |
| MessageBird/Bird | |
| Sent | |
Sent的密钥是去除前缀后剩余部分经base64解码得到的签名密钥,需通过常量时间比较验证,且会拒绝超出300秒的时间戳。由于Sent不提供每个事件的ID,去重密钥必须从负载语义中推导。请使用构建接收端,而非修改现有平台的验证器。
whsec_sent-webhook-engineer4. Opt-out stores must be reconciled, not migrated by copy
4. 退订存储需对账,而非直接复制迁移
Every provider keeps its own suppression list — Twilio Advanced Opt-Out, Infobip Blocklist, Sinch OPT_IN/OPT_OUT events. Sent enforces consent at the platform level before events reach the application, stores it as on the contact, and applies it channel-agnostically: a on SMS suppresses WhatsApp and RCS too.
opt_outSTOPReconciliation rules: export the incumbent's suppression list before cutover, treat any opt-out on any incumbent channel as a global Sent opt-out, and never clear to "clean up" migrated data. Sent's ten default keywords are , , , , , , , , , , matched only when the entire trimmed body equals the keyword — so incumbent-specific keywords need custom keyword entries. Rewrite any incumbent keyword matcher as an exact local consent mirror and audit mechanism; the matcher must not write consent to Sent again. Consent semantics belong to .
opt_outSTOPCANCELUNSUBSCRIBEQUITENDSTARTUNSTOPSUBSCRIBEHELPINFOsent-two-way-messaging每个提供商都有自己的抑制列表——如Twilio Advanced Opt-Out、Infobip Blocklist、Sinch OPT_IN/OPT_OUT事件。Sent在平台层面强制执行合规同意,在事件到达应用前就进行处理,将其存储为联系人的字段,并且跨渠道统一应用:短信渠道的指令会同时屏蔽WhatsApp和RCS渠道。
opt_outSTOP对账规则:切换前导出现有平台的抑制列表,将现有平台任意渠道的退订视为Sent全局退订,切勿为“清理”迁移数据而清除字段。Sent的十个默认关键词为、、、、、、、、、,仅当修剪后的请求体完全匹配关键词时才会触发——因此现有平台的特定关键词需要添加自定义关键词条目。将现有平台的关键词匹配器重写为精确的本地合规镜像和审计机制;该匹配器不得再次向Sent写入合规同意信息。合规同意语义相关内容可参考。
opt_outSTOPCANCELUNSUBSCRIBEQUITENDSTARTUNSTOPSUBSCRIBEHELPINFOsent-two-way-messaging5. Templates and tenancy are re-registered, not transferred
5. 模板和租户需重新注册,而非转移
WhatsApp templates live with the WABA, so the migration question is whether the WABA moves. Positional placeholders (, ) become named parameters in Sent, which means every call site that passed an ordered array must pass a named map. Approval is asynchronous and arrives as a webhook event, so build the template inventory before cutover rather than during it.
{{1}}{{2}}templatesTenancy maps as follows, with the boundary decision owned by and the API work by :
sender-profile-architectsent-profile-provisioning| Incumbent construct | Sent equivalent |
|---|---|
| Twilio subaccount | Sender Profile |
| Twilio Messaging Service | routing plus profile configuration, not a caller-side pool |
| Infobip Application or Entity | Sender Profile |
| Sinch Conversation API app | Sender Profile |
| Provider API credential per tenant | Profile-scoped API key, or organization key with |
WhatsApp模板归属于WABA(WhatsApp Business Account),因此迁移的核心问题是是否迁移WABA。位置占位符(、)在Sent中变为命名参数,这意味着所有传入有序数组的调用点都必须传入命名映射。模板审批是异步的,会以 Webhook事件形式通知,因此请在切换前完成模板清单的构建,而非在切换过程中进行。
{{1}}{{2}}templates租户映射如下,边界决策由负责,API操作由负责:
sender-profile-architectsent-profile-provisioning| 现有平台结构 | Sent对应项 |
|---|---|
| Twilio子账户 | Sender Profile(发送者配置文件) |
| Twilio消息服务 | 路由加配置文件配置,而非调用端池 |
| Infobip应用或实体 | Sender Profile |
| Sinch对话API应用 | Sender Profile |
| 每个租户的提供商API凭证 | 配置文件范围的API密钥,或带有 |
Migration sequence
迁移流程
- Inventory every send call site, webhook handler, status branch, template, suppression list, and credential. Use to find them mechanically.
scripts/inventory_scan.py - Map each item using references/provider-mapping.md, flagging ordered-fallback arrays and numeric error codes as required rewrites.
- Stand up Sent in parallel: credentials, one webhook per environment, verified receiver, templates re-registered and approved.
- Prove equivalence in sandbox with , then with a small live cohort confirmed to
"sandbox": true.DELIVERED - Dual-run with a traffic split, comparing delivery rates, latency, and cost per message on the same message classes.
- Cut over by message class — lowest-risk transactional first, marketing last — keeping the incumbent receiver live.
- Decommission only after a full billing cycle of clean data, then revoke incumbent credentials.
Sequencing detail, verification gates, and rollback triggers are in references/cutover-playbook.md.
- 盘点所有发送调用点、Webhook处理程序、状态分支、模板、抑制列表和凭证。使用进行自动盘点。
scripts/inventory_scan.py - 映射每个条目,参考[references/provider-mapping.md],标记有序回退数组和数字错误码作为必须重写的内容。
- 并行部署Sent:配置凭证、为每个环境设置一个Webhook、验证接收端、重新注册并审批模板。
- 在沙箱环境验证等效性,设置,然后在小批量真实用户群体中确认消息已
"sandbox": true。DELIVERED - 双运行并分流流量,对比相同消息类型的投递率、延迟和单条消息成本。
- 按消息类型切换:先切换风险最低的交易类消息,最后切换营销类消息,同时保持现有平台的接收端处于活跃状态。
- 停用现有平台:仅在完成一个完整计费周期且数据无异常后,再撤销现有平台的凭证。
流程细节、验证关卡和回滚触发条件可参考[references/cutover-playbook.md]。
Mistakes that survive testing
测试无法发现的错误
- Porting an ordered channel array. Doubles cost, never errors.
- Treating as retryable. Compliance exposure.
FILTERED - Reusing the incumbent's signature verifier. Every delivery returns 401.
- Assuming means delivered. Sent acknowledges acceptance only.
202 - Keeping positional template placeholders. Parameters silently mismatch.
- Retrying on . Ten consecutive auth failures lock the credential with escalating lockout.
401 - Omitting during dual-run. A timeout retry sends twice.
Idempotency-Key - Sending with a profile-scoped key. Returns
x-profile-id.403 - Copying an incumbent's pattern. Sent authenticates with
Authorization: Bearer.x-api-key
- 移植有序渠道数组:成本翻倍,且不会报错。
- 将视为可重试状态:引发合规风险。
FILTERED - 复用现有平台的签名验证器:所有投递请求都会返回401。
- 认为表示已投递:Sent仅确认请求已被接受。
202 - 保留位置式模板占位符:参数会静默不匹配。
- 在时重试:连续十次认证失败会锁定凭证,且锁定时长逐渐增加。
401 - 双运行期间省略:超时重试会导致重复发送。
Idempotency-Key - 使用配置文件范围的密钥时传入:返回
x-profile-id。403 - 复用现有平台的模式:Sent使用
Authorization: Bearer进行认证。x-api-key
Boundaries
职责边界
This skill owns provider mapping and line-by-line migration planning. Hand the resulting Sent client and resilience work to , channel semantics to , receiver construction to , WhatsApp onboarding to , and US campaign registration to .
sent-integration-startersent-routing-strategistsent-webhook-engineerwaba-embedded-signupsms-10dlc-registration本技能负责提供商映射和逐行迁移规划。生成的Sent客户端和弹性相关工作交由处理,渠道语义交由处理,接收端构建交由处理,WhatsApp入驻交由处理,美国短信Campaign注册交由处理。
sent-integration-startersent-routing-strategistsent-webhook-engineerwaba-embedded-signupsms-10dlc-registration