跳到主要内容

发送提醒

每个频道都有一个或多个发送链接。要发送提醒,向发送链接 POST 一段 JSON 即可;拼不了 JSON 的设备可以发送纯文本、表单或查询字符串。完整链接可以在应用中复制,格式如下:

https://<api-host>/v1/send/<token>

链接中的 token 是唯一凭证。任何拿到链接的人都能向这个频道发送提醒,所以不要把它放进公开仓库或日志。发送链接只能发送,不能读取提醒,也不能修改频道。

终端窗口
curl -X POST "$SEND_URL" \
-H 'Content-Type: application/json' \
-d '{"alert": {"title": "构建失败", "body": "main 分支有 3 个测试失败"}}'

请求成功时返回 201 Created。提醒会在响应之前保存好,随后在后台投递到设备。

在脚本和定时任务中,可以用命令行工具发送同样的请求,它还提供重试、加密和命令失败提醒。

字段 类型 说明
alert.title string 必填。 提醒标题。
alert.body string 可选,显示在标题下方的详细内容。
level string urgent、high、normal 或 silent,默认 normal。见提醒级别与重复提醒。
require_ack boolean 在有人确认之前,提醒一直保持待处理。见确认与回执。
receipt boolean 即使不要求确认,也返回一个可以查询的回执。
callback boolean 提醒被确认时调用频道的确认回调。
emergency object 紧急提醒的重复提醒时间:retry_seconds 和 expire_seconds。
url string 从提醒中打开的链接。
event.type string 发生了什么,例如 build.failed。
event.subject string 发生在什么对象上,例如 repo:payments:main。
event.fingerprint string 事件的稳定标识,用来识别重复事件。默认由 subject 和 type 组成。
dedupe object 重复事件的处理方式,见重复事件。
thread_key string 把相关提醒归为一组,显示在提醒详情中。
payload object 想随提醒一起保存的任意 JSON。会保存并由接口返回,但不会显示。
expires_at string RFC 3339 时间,过了这个时间提醒可以被删除。默认为发送后 21 天,最晚为发送后 90 天。
idempotency_key string 用同一个 key 重试请求,会返回原来的提醒而不会再创建一条。也可以放在 Idempotency-Key 请求头中。

要求端到端加密的频道不接受 alert,而是接受加密信封,见加密提醒。

路由器、摄像头、NAS、PLC、单片机等拼不了 JSON 的设备,可以用更简单的方式向同一个链接发送。这类请求使用下面这些扁平字段;emergency、callback、dedupe、payload 等其他字段仍需使用 JSON。

字段 请求头 对应 JSON 字段
title Title alert.title
body — alert.body
level Level level
url Url url
thread_key Thread-Key thread_key
idempotency_key Idempotency-Key idempotency_key
event_type Event-Type event.type
event_subject Event-Subject event.subject
event_fingerprint Event-Fingerprint event.fingerprint

纯文本。 用 POST 或 PUT 发送文本,Content-Type 设为 text/plain 或者不设。文本就是提醒内容,其他字段放在请求头或查询字符串里。

终端窗口
curl -X POST "$SEND_URL" \
-H 'Content-Type: text/plain' \
-H 'Title: Door opened' -H 'Level: high' \
--data-binary '车库,03:12'

没有标题时,正文第一行会作为标题,所以设备只发一行文字也可以。

表单。 像 HTML 表单一样发送 application/x-www-form-urlencoded 字段:

终端窗口
curl -X POST "$SEND_URL" --data 'title=UPS+on+battery&body=Load+42%25&level=urgent'

查询字符串。 上面这些请求都可以把字段放在查询字符串里,例如 POST $SEND_URL?title=Door+opened&level=high。

规则:

  • 每个字段只能出现一次。同一字段出现在两个地方(比如查询字符串和请求头),或者查询字符串、表单里有不认识的字段,都会返回 invalid_target,field 指明是哪个字段。
  • 以 { 开头的请求体一律按 JSON 读取,不管 Content-Type 是什么。不含 = 的表单请求体按纯文本读取,所以 curl -d '文本' 也能用。
  • 文本使用 UTF-8。GBK 文本请声明 Content-Type: text/plain; charset=gbk,也支持 gb2312 和 gb18030。
  • JSON 请求会忽略上面这些请求头,Idempotency-Key 除外。
  • 长度限制和频率限制与 JSON 相同。要求端到端加密的频道会以 encryption_required 拒绝这类请求。

有些设备只能打开一个网址。要让这类设备使用某个发送链接,先在应用中为它允许 GET:打开该发送链接的操作菜单,开启允许 GET 请求。然后把字段放在查询字符串里:

https://<api-host>/v1/send/<token>?title=Motion&body=Front+door&level=high

GET 默认关闭,因为任何打开这个网址的程序都会发出提醒,包括为贴进来的链接生成预览的聊天软件。关闭时,GET 请求返回 405 method_not_allowed。HEAD 请求永远不会发送。设备常常会盲目重试,请加上 idempotency_key 或 event_type,避免重试产生第二条提醒。

  • 发送链接只能通过 HTTPS 访问,需要 TLS 1.2 或 1.3 以及 SNI。普通 http:// 请求会直接收到 403 https_required,不会被重定向,因为大多数设备跟不了重定向。此时 token 已经以明文经过网络,如果网络不可信,请重新生成链接。
  • 完全不支持 TLS 的设备,可以通过支持 TLS 的本地网关转发,例如 Home Assistant 或 Node-RED。
  • 返回 201 就表示提醒已保存,响应体可以忽略。收到 429 时按 Retry-After 等待后重试,5xx 稍等后重试。其他 4xx 错误不要重试,重试也会失败。
{
"message": {
"id": "msg_4f1c…",
"receipt_id": "rcp_9a02…",
"level": "urgent",
"require_ack": true,
"status": "open",
"dedupe_count": 1
},
"decision": { "reason": "new_message" }
}

响应中的 message 是保存下来的提醒,这里只列出了部分字段。提醒需要确认或请求了 receipt 时会有 receipt_id。decision.reason 说明这次请求的结果:

值 含义
new_message 创建了一条新提醒。
duplicate_updated 重复事件更新了一条未处理的提醒,见下文。
duplicate_recorded 记录了一次重复,但没有修改提醒。
duplicate_dropped 忽略了这次重复。
idempotent_replay 这个 idempotency_key 已经用过,返回原来的提醒。

decision.effective_policy 给出这次实际采用的重复事件处理方式,字段名与请求中的 dedupe 对象相同。

每分钟失败一次的任务不应该塞满你的收件箱。请求带有 event,并且有一条指纹相同、仍未处理、在时间窗口内出现过的提醒时,这次请求会更新那条提醒,而不是新建一条:标题、正文、链接和级别被替换,出现次数加一,应用里会显示为“出现 3 次”这样的字样。除非提醒需要确认,重复事件不会再次通知。

没有任何 event 字段的请求永远不会被当作重复事件。

字段 默认值 说明
dedupe.mode coalesce none 表示总是新建提醒。
dedupe.window_seconds 600 距上一次重复多长时间内,新的请求仍会合并到这条未处理的提醒。
dedupe.accept_policy update_visible update_visible 更新提醒;update_and_notify 更新并再次通知;record_only 只计数不修改;drop 直接忽略。
dedupe.renotify_after_seconds 0 允许再次通知时,距上次通知至少要间隔的时间。

提醒被确认或解决之后,同一指纹的下一次请求会开始一条新提醒。

长度按字符计算,一个汉字、一个英文字母、一个表情符号都算一个字符。首尾空白会被忽略。

字段 限制
请求体 64 KB
alert.title 100 个字符,只能一行
alert.body 1000 个字符,可以包含换行和制表符
alert.title + alert.body 合计 1000 个字符
url 512 字节;必须是 http 或 https 绝对地址,非 ASCII 字符需百分号编码
thread_key、idempotency_key 128 个字符
event.type 64 个字符
event.subject、event.fingerprint 200 个字符
payload JSON 不超过 8 KB,嵌套不超过 8 层
expires_at 最晚为发送后 90 天;更晚的时间会以 invalid_target 拒绝

超过限制的请求会以 alert_too_long 拒绝,不会被悄悄截断。推送通知中过长的正文会被截短,但收件箱里始终是完整内容。所有限制汇总在限制。

加密提醒不适用上面这些限制,只有一项内容限制:加密内容最多 4096 字节,不限字符数。见大小限制。

  • 每个发送链接默认每分钟接受 60 次请求。超过后请求返回 429 limit_exceeded 和 Retry-After 响应头,且不算作已发送。
  • 如果一个发送链接远远超出限制还在持续发送——一分钟内至少 100 次被拒绝,或者限制的十倍(取较大者)——它会被暂停,之后返回 401 invalid_send_url。否则服务器无法阻止一个失控的脚本,而泄露的链接也绝不会被悄悄恢复。修好发送端之后,在应用中重新生成链接。
  • 每个网络地址在所有接口上每分钟最多 600 次请求,超过后返回 429 rate_limited。

格式校验错误会先检查,不会占用发送链接的次数。

错误响应是带有稳定 code 的 JSON:

{ "error": { "code": "alert_too_long", "message": "…", "field": "alert.title", "limit": 100, "actual": 128 } }
状态码 错误码 含义
400 invalid_json 请求体不是合法的 JSON 或 UTF-8。
400 invalid_request 简单请求的文本、表单、查询字符串或请求头不是合法的 UTF-8 或 URL 编码。
401 invalid_send_url 发送链接错误、已撤销、已暂停,或所属频道已删除。
403 https_required 请求使用了 http://。请改用 https://;如果网络不可信,请重新生成链接。
405 method_not_allowed 对未允许 GET 请求的发送链接使用了 GET,或使用了 HEAD。
413 request_too_large 请求体超过 64 KB。
415 unsupported_media_type 发送接口不支持该 Content-Type 或 charset。
422 invalid_target 缺少 alert.title、字段值无效或含有不允许的字符、简单请求字段不认识或重复出现,或请求在其他方面无效;field 指明字段。
422 alert_too_long 字段超过限制。错误中包含 field、limit 和 actual。
422 invalid_alert_level level 不是四种级别之一。
422 invalid_emergency emergency 的时间超出范围。
422 invalid_callback 对无法确认的提醒请求了 callback。
422 callback_not_configured 请求了 callback,但频道没有启用回调。
422 encryption_required 频道只接受端到端加密的提醒。
422 invalid_encrypted_message 加密信封不完整或格式错误。
429 limit_exceeded 达到了发送链接的频率限制。等待 Retry-After 指定的秒数后重试。
429 rate_limited 来自你的地址的请求过多。等待 Retry-After 指定的秒数后重试。

整个接口的所有错误码见错误。