返回博客
官小西

ntfy vs Gotify vs Nostr:自建推送方案深度对比 —— AI Agent 的 cron 结果怎么推到手机上

自建 AI Agent 的人都会在第三天夜里撞上同一个墙:凌晨三点的 cron 跑完了,产出了 20 条竞品动态和一次失败的依赖更新,而你正睡在床上,手机安静得像块砖。第二天早上打开终端,才发现昨晚有个任务挂了,已经错过了窗口期。

问题不在于"结果存哪"——数据库里躺着呢。问题在于结果怎么主动找到你。邮件太慢且被淹没,微信机器人要绑企业微信/飞书还要应付风控,短信要钱。真正靠谱的答案是自建推送通道:让 Agent 用一个 HTTP 请求就能把消息怼到你手机的锁屏上。

这个赛道有三个主流选手:ntfyGotifyNostr Relay。它们都能自建、都开源、都能从 curl 一行命令发消息。但它们的架构假设完全不同,选错了一个,你的 iOS 手机就收不到任何东西,或者你的消息永远等不到 App 被唤醒。截至 2026 年 8 月,我逐个走了一遍源码和部署流程,下面是我的结论。

三者的本质差异:架构先行

在展开细节前,先看一张架构总览图——三者虽然都叫"推送服务",但数据流和依赖关系完全是三套东西:

ntfy、Gotify、Nostr Relay 三种自建推送方案架构对比

一句话概括:ntfy 是"极简 HTTP pub/sub",Gotify 是"带用户体系的 WebSocket 服务器",Nostr 是"去中心化事件广播协议"。这个差异决定了后面所有的取舍。

ntfy.sh:把"推送"简化到一条 curl

ntfy(binwiederhier/ntfy)是我见过的最克制的一个项目。它的核心设计就一句话:HTTP POST 到一个 topic,所有订阅这个 topic 的客户端就收到消息

curl -d "build failed" ntfy.sh/mytopic

没有 SDK,没有注册流程,没有握手。发布方和订阅方唯一的约定就是这个 topic 字符串——它既是地址,也是凭证。这带来两个直接好处:

第一,发布端零成本接入。 任何能发 HTTP 请求的东西——shell 脚本、Python、Go、CI 系统、甚至 watch curl——都能当发布方。对一个 AI Agent 来说,这意味着你不需要往它的工具链里塞一个 ntfy 专用 SDK,一行 curl 或一个 httpx.post() 就完事。这是它和 Gotify(需要先建应用、拿 App Token)最本质的差距。

第二,订阅端全平台覆盖。 ntfy 官方提供 iOS、Android、Web 三个客户端(截至 2026 年 8 月均活跃维护),这是它压过 Gotify 的关键点。更关键的是底层机制:官方 Android App 支持 FCM(Firebase Cloud Messaging),iOS App 支持 APNs(Apple Push Notification),也就是说即使 App 被系统杀掉、后台冻结,消息依然能通过系统级推送通道到达锁屏——这是自建推送方案里最难做、也最容易被忽略的一环。

这个设计决策值得单独说透:很多自建推送方案(包括 Gotify 和纯 WebSocket 方案)在移动端靠"长连接保活"来收消息,但 iOS 的后台策略会让长连接被系统掐断,导致消息延迟甚至丢失。ntfy 的做法是在公共实例上跑 FCM/APNs 转发,自建实例作为上游——你在 docker run 启动时配一个上游公共服务器,消息先到你自己的服务器,再借公共实例的 FCM/APNs 通道做系统级推送。这个"混合架构"是它即时送达的根源。

其余能力一页列完:SQLite/PostgreSQL 双后端、附件支持(本地文件系统或 S3,默认 3 小时过期)、消息缓存(cache-duration 可配,默认 12 小时)、优先级(1-5 级)、Markdown 渲染、点击动作、邮件转发。访问控制上支持 auth-file(账号密码)+ deny-all 基线,默认关闭注册、默认拒绝订阅,想开订阅要先在 auth.yml 里显式放行——安全默认值是这三个方案里最保守的。

限制也明说:免费的公共实例 ntfy.sh 限制每个 topic 每天 512 条消息,且你的消息对所有猜得到 topic 名的人可见(topic 即密码,随机长串等于密码)。要私有要么自建,要么自建后配 FCM/APNs 上游。

Gotify:一个漂亮的 Android 专属推送服务器

Gotify(gotify/server)是三者里"最像完整产品"的一个。它是 Go 写的、MIT 许可、Docker 一条命令起,自带一个 React 写的 Web UI 管理界面,可以建用户、建应用、发 App Token、看消息历史。

架构上它和 ntfy 有个根本分歧:Gotify 靠 WebSocket 常连接推送,不依赖 FCM/APNs 这类系统通道。这带来一个很漂亮的特性——自建后零外部依赖,消息完全走自己的服务器,延迟极低(局域网内实测 <50ms),隐私上完全不经过 Google/Apple 的推送服务器。消息持久化到 SQLite,历史可查,图片附件通过 extras 字段直传。

但它的致命缺陷写在脸上:没有 iOS 客户端。截至 2026 年 8 月,Gotify 官方只提供 Android App 和 Web 界面,iOS 用户只能靠 Web App 或第三方缝合方案凑合,而这在 iOS 的后台限制下基本等于"收不到实时通知"。Android 端它其实做得不错——App 通过 WebSocket 常连接实时收消息,也支持 FCM 作为兜底——但一个推送方案砍掉整个 iOS 用户群,对绝大多数人来说已经一票否决了。

用户体系是双刃剑:用户 → 应用 → Token 的三层模型比 ntfy 的 topic 更"正式",适合多用户、多应用、带管理界面的场景,但对"Agent 发一条消息"这种轻量用途来说,它是额外的仪式感——你得先建应用、生成 Token,而不是随手敲一个 topic 名。

Nostr Relay:去中心化的正确姿势,但推送是反直觉的弱点

Nostr 不是为推送设计的,它是一套去中心化的社交协议(NIP-01 定义 Event + Schnorr 签名,relay 存储并转发事件)。但正因为它天然支持"一个公钥向任意 relay 广播消息、任意客户端订阅",被不少人拿来当自建推送通道。

发消息的姿势是用 pynostr(Python 库)或 JS SDK 构造一个签名 Event 塞进自己的 relay:

from pynostr.key import PrivateKey
from pynostr.relay_manager import RelayManager
from pynostr.event import Event

key = PrivateKey()
relay = RelayManager()
relay.add_relay("wss://relay.example.com")
event = Event("build failed", kind=1, pubkey=key.public_key.hex())
key.sign_event(event)
relay.publish_event(event)

它的强项是真材实料的:去中心化(relay 可以多冗余部署,一个挂了消息还在另一个)、抗审查、NIP-04/NIP-44 加密 DM、消息在 relay 上按 policy 永久存储、客户端生态开放(Damus/iOS、Amethyst/Android、Iris/Web 都是现成的)。

但"推送"恰恰是它最弱的一环。 这是我最想提醒的一点:Nostr 客户端收消息依赖客户端保持前台或至少后台常连接,而 iOS 对后台 WebSocket 的限制会让消息在 App 被系统冻结后根本到不了锁屏。也就是说,你把它当"人主动刷的社交信息流"完全没问题,但把它当"Agent 半夜跑完任务通知你"的通道,消息大概率在你第二天打开 App 时才姗姗来迟。这个缺陷不是配置能解决的,是协议定位决定的——Nostr 从来就没承诺过系统级推送。

七维对比

把三个方案放进一张表,选哪个就不言自明了:

维度 ntfy Gotify Nostr Relay
协议 HTTP POST/PUB + WebSocket/SSE 订阅,topic 即地址 REST + WebSocket 常连接,需注册 NIP-01 Event + Schnorr 签名,开放协议
客户端 ✅ iOS + Android + Web,官方全平台 ❌ 仅 Android + Web,无 iOS ✅ iOS + Android + Web,但无原生推送
安全 ACL + Token + IP 限流,deny-all 基线 用户 + App Token + TLS 公钥签名 + 加密 DM,无服务端信任
自建难度 Docker 一条命令,极简 Docker 一条命令,极简 Relay + DB + 反代,中等
延迟 ~1.1s(公共)/ <100ms(自建),FCM/APNs 即时 <50ms(自建 WebSocket 常连接) ~50-200ms,但依赖客户端前台
附件 ✅ S3/本地,默认 3h 过期 ✅ 图片/文件(extras 字段) ⚠️ 仅 URL 引用(NIP-94/92)
消息历史 ✅ 可配置缓存(默认 12h) ✅ SQLite 永久持久化 ✅ Relay 存储,多 relay 冗余

ntfy、Gotify、Nostr 七维特性矩阵对比

评分

维度 ntfy Gotify Nostr
协议简洁度 9/10 7/10 6/10
客户端覆盖 9/10 4/10 8/10
安全模型 8/10 8/10 9/10
自建难度 9/10 9/10 6/10
推送延迟 8/10 9/10 4/10
附件支持 8/10 7/10 5/10
消息历史 7/10 9/10 9/10
综合 8.3 7.6 6.7

评分依据一句带过:ntfy 的协议简洁度和全平台覆盖是决定性的(对推送这个场景,这两个维度权重最高);Gotify 死在客户端覆盖上,但延迟和历史是三者最强;Nostr 的安全和历史很强,但"推送延迟 4/10"直接拉垮了它的总分——一个推送方案,延迟不行,别的维度再好也白搭。

结论:按场景选,而不是按信仰选

我的推荐很直接,不绕弯:

  • AI Agent 推送,选 ntfy。 没有之一。Agent 发消息只需要一个 curlhttpx.post(),不需要 SDK、不需要建应用、不需要 Token 仪式感;iOS/Android/Web 全覆盖,FCM/APNs 保证锁屏即时送达,Docker 一条命令自建,配合 deny-all + 随机长 topic 就是一套够用的私有推送。这是三个方案里唯一一个"为推送而生"的。如果你在搭自己的 Agent 基础设施,这个话题和我之前拆过的 Bitchat Mesh 协议(节点间通信)以及 Iroh 1.0 的 dial-keys-not-ips(对等寻址)正好构成"Agent 通信"的三个互补侧面:节点间怎么连、身份怎么寻址、结果怎么推给你。

  • 纯 Android 环境,选 Gotify。 如果你和你的用户全在 Android 上,且在意"消息完全不经过 Google/Apple 服务器"这个隐私点,Gotify 的 WebSocket 常连接 + 低延迟 + 永久历史 + Web UI 是最舒服的。但只要你未来有 1% 的可能用 iOS,就别碰它。

  • 去中心化信仰,选 Nostr。 如果你要的是抗审查、开放协议、消息永久存储、和已有 Nostr 身份打通,那它是唯一选择。但请把"推送"这个用途从你的期望里划掉——它适合"信息流",不适合"通知"。真要硬用,把它当作 Agent 消息的归档层(relay 永久存历史),推送通知那条腿另接 ntfy,两者并不冲突。

最后的实话:这三个方案都不是银弹,但 ntfy 是唯一一个把"从 cron 到锁屏"这条链路每一环都做对、又不逼你妥协的。自建 AI Agent 的痛点清单上,推送这一项,ntfy 已经帮你划掉了。

参考资料

  1. ntfy — ntfy.sh 官方文档(发布/订阅/配置/发布端点)(持续更新)
  2. ntfy — GitHub 仓库 binwiederhier/ntfy(Go,Apache-2.0/GPL-2.0 双许可)
  3. ntfy — iOS App(App Store)
  4. ntfy — Android App(Google Play)
  5. Gotify — GitHub 仓库 gotify/server(Go,MIT)
  6. Gotify — 官方文档(REST API / WebSocket / 部署)
  7. Gotify — Android App
  8. Nostr — NIP-01 协议规范(Event 结构与 relay 行为)
  9. Nostr — NIP-04 加密 DM / NIP-44 版本化加密
  10. pynostr — Python Nostr 库(发消息用)
  11. Nostr relay — nostream(TypeScript relay 实现)
  12. Nostr 客户端 — Damus(iOS) / Amethyst(Android)