后端与接口
在现有代码库中首次接入 Sent v3…
当需要为应用首次添加 Sent 消息集成、选择 SDK、处理幂等发送、429/409 响应、验证 webhook 或上线前加固时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
在现有代码库中首次接入 Sent v3 特定
解决思路
- 分四个阶段接入 Sent:认证、幂等发送、接收已验证事件、加固;不要把阶段混在一起,大多数坏集成只过了第一阶段而跳过了第三阶段。
- 直接调用 Sent v3 REST 时使用 `x-api-key` 请求头认证;应用代理可以接受来自调用方的 `Authorization: Bearer`,Sent MCP 服务器使用客户端管理的 OAuth,但发给 `api.sent.dm` 的 REST 请求头不变。
- 组织级密钥可加 `x-profile-id` 代表子 profile 操作;profile 范围的密钥若发送该请求头会收到 `403`。
- 按语言选择 SDK:TypeScript 用 `@sentdm/sentdm` 的 `new SentDm()`;Python 用 `sentdm`(导入 `sent_dm`)的 `Sent()` 或 `AsyncSent()`;Go 用 `github.com/sentdm/sent-dm-go` 的 `sentdm.NewClient()`;Java 用 `dm.sent:sent-java` 的 `SentOkHttpClient.fromEnv()`;C# 用 `Sentdm` 的 `new SentClient()`;PHP 用 `sentdm/sent-dm-php` 的 `new SentDm\Client($apiKey)`;Ruby 用 `sentdm` 的 `Sentdm::Client.new`。
- 除 PHP 外所有 SDK 自动读取 `SENT_DM_API_KEY`;单端点接收示例读取 `SENT_DM_WEBHOOK_SECRET`;多租户生产接收器需要按 webhook id 索引的密钥注册表,而不是一个进程级密钥。
- 旧文档中的 `SENT_API_KEY` 和 `SENT_WEBHOOK_SECRET` 视为别名,统一改用 `SENT_DM_` 前缀名称。
- 按凭证模型选择客户端生命周期:单账号服务用一个服务器管理的密钥,复用长生命周期客户端及其连接池;多租户代理按请求解析调用方或 profile 凭证,为该请求构造客户端并丢弃,避免租户凭证通过共享状态泄漏。
- 在启动时校验配置,密钥缺失时快速失败,而不是在首次客户发送时才暴露认证错误。
- 发送请求中 `to` 是唯一必填字段;提供 `template` 或 `text`,省略 `channel` 让自动路由选择。
- 绝不要写一个包含多个值的 `channel` 数组并期望回退——那会广播并成倍计费;渠道决策属于 `sent-routing-strategist`。
- 在每个 POST、PUT、PATCH 上发送 `Idempotency-Key`,从自己的领域对象确定性派生(例如订单 id 加通知类型),使超时后的重试不会重复发送。
- 幂等键为 1–255 个字符,字符集 `[A-Za-z0-9_-]`,按客户按键缓存 24 小时;重放返回缓存响应体并带 `Idempotent-Replayed: true` 和 `X-Original-Request-Id`。
- 原请求仍在进行时到达的重复请求最多等待五秒,然后失败 `409 CONFLICT_001`;`503 SERVICE_001` 表示幂等存储不可用且请求被故意未执行。
- `202` 表示已接受而非已投递;立即持久化返回的 `message_id` 值,并关联自己的租户、profile 和逻辑发送标识。
- 注意 webhook 事件携带 Sent 消息 id 和账号数据,但绝不携带应用的租户标识。
- 没有接收器的集成没有投递真相;注册端点后验证每次投递:对 `{x-webhook-id}.{x-webhook-timestamp}.{raw_body}` 做 HMAC-SHA256,密钥为去掉 `whsec_` 前缀后 base64 解码的 secret,用常量时间比较,拒绝时间戳超出 300 秒的请求;任何语言的 SDK 都不附带验证器。
- 先返回 `200` 再处理工作,出站事件按 `{message_id}:{message_status}` 去重,入站事件按 `message_id` 去重;连续十次投递失败会禁用端点。
- 按响应类别制定重试策略:`2xx` 不重试;`400`、`422 VALIDATION_*` 不重试,修复请求;`401`、`403 AUTH_*` 不重试,立即停止,连续十次认证失败会锁定凭证并逐步升级锁定;`404 RESOURCE_*` 不重试,引用的对象不存在;`409 CONFLICT_001` 暂停后重试一次;`429` 重试,遵守 `Retry-After` 并用抖动退避;`5xx`、`503 SERVICE_001` 重试,用带抖动和上限的指数退避;无响应超时仅在证据充分时安全重试,复用同一 `Idempotency-Key`,没有幂等键时无法按 key 或收件人可靠查询 API,因此不要自动重发。
- 标准限流为滑动窗口每分钟 200 请求;`POST /v3/webhooks/{id}/rotate-secret` 和 `POST /v3/webhooks/{id}/test` 限制为每分钟 10 次;限流响应头只在 `429` 响应中出现,因此必须设计节奏而非测量,批量工作每请求最多 1000 个收件人,节奏约每秒一个请求。
- 错误格式为 `{success, data, error: {code, message, details, doc_url}, meta: {request_id, timestamp, version}}`;按 `error.code` 前缀族(`AUTH_`、`VALIDATION_`、`RESOURCE_`、`BUSINESS_`、`CONFLICT_`、`SERVICE_`、`INTERNAL_`)分支,而不是按消息文本或单个错误码。
- 注意两个反直觉错误码:`BUSINESS_003` 和 `BUSINESS_004` 文档上标为请求级错误,但在 `POST /v3/messages` 上请求会以 `202` 被接受,受影响消息最终变为 `BLOCKED` 和 `FILTERED`;因此余额不足不会使发送调用失败。
- 在每个响应(成功或失败)上记录 `meta.request_id`,它是支持排查的关联句柄;记录逻辑发送到返回 `message_id` 值的映射,并保留只追加的事件历史,使重路由的序列可审计。
- 绝不记录 API key、webhook 签名 secret、`payment_details` 或超出保留策略的原始收件人消息内容。
- 上线检查清单:凭证从环境加载、不提交、每个环境有独立密钥;客户端生命周期匹配凭证范围(单服务器管理密钥共享,租户提供凭证按请求);每个变更调用都有确定性派生的 `Idempotency-Key`;重试策略按错误族区分可重试与终止;批量路径按每分钟 200 请求限速并每批最多 1000 收件人;webhook 接收器验证签名和时间戳、快速返回 `200` 并去重;接收器在真实失败时返回非 2xx 以便 Sent 重试;发送前持久化 `message_id` 到租户的映射;记录 `request_id`、不记录 secret 和卡数据;沙箱冒烟测试通过后真实发送达到 `DELIVERED`;告警覆盖 webhook `consecutive_failures`、`429` 量和过滤/阻止率。
- 运行本地预检,无需凭证和网络:`python3 scripts/preflight.py --self-test`。
- 用 `"sandbox": true` 验证真实路径(认证并校验但不执行),最后用一次真实发送并通过接收器确认达到 `DELIVERED`。
适用边界 · 注意事项
- 需要接收器深度实现时用 `sent-webhook-engineer`,渠道选择用 `sent-routing-strategist`,确认的一次性发送用 `sent-messaging`,入站和同意用 `sent-two-way-messaging`,多租户开通用 `sent-profile-provisioning`,替换其他 CPaaS 提供商时用 `migrate-to-sent`。
- 没有幂等键时不要自动重发无响应超时的请求,因为无法按 key 或收件人可靠查询 API。
- 不要写包含多个值的 `channel` 数组期望回退,那会广播并成倍计费。