我推荐 ntfy 起步,要多渠道就用 Apprise 收口,SMS 放最后;「何时发」优先挂 agent 原生触发点(hooks/notify),挂不上的,用一条命令的约定兜底。我的需求很具体:跟任何 agent 说「完事发消息给我」,然后离开屏幕。

这份选型依据来自 2026-10-18 的动手研究 session:GitHub API 查询、官方文档阅读及社区讨论,相关记录经过独立复核;不代表已经实测各渠道的送达能力。来源与证据级别见文末注。

把「发到哪」和「何时发」分开

接入拆成两层。传输层决定消息发到哪,触发层决定哪个事件值得发。这样换接收渠道时,不用重写「完成」的定义;换 agent 时,也尽量保留同一个发送约定。〔注1〕

官方文档写明,ntfy 有长时间运行命令的通知示例,包括 alias alert='notify_via_ntfy' 和 sleep 10; alert。这至少给出了一个具体接法:命令结束后调用发送入口。〔注2〕但发送动作之前,还得决定消息里的「完事」究竟指什么。〔注1〕

传输层:先比较这三个工具

GitHub API 记录显示,按 2026-10-18 口径,ntfy 有 34,886 stars,Apprise 有 17,602,Gotify server 有 16,089;前两者的 pushed 日期为当天,后者为 2026-10-15。〔注3〕我用这些记录判断候选是否值得继续看,不把 stars 当成可靠性评分。

候选可确认的依据适用判断
ntfy官方文档提供命令结束通知示例,也写明自建入口 ntfy serve。〔注2〕〔注4〕只想先收到消息,从这里开始
Apprise官方 README 列出 tgram://bottoken/ChatID 和 twilio://AccountSid:AuthToken@FromPhoneNo/ToPhoneNo。〔注5〕接收渠道变多时,集中维护发送配置
Gotify有文章认为它适合全 Android 的私有部署,提到 app/user 分离。〔注6〕已确定这种使用环境时,再与 ntfy 自建比较

记录显示,2026-10-18 的 Apprise plugins 目录有 158 个 .py 文件(含 __init__.py,插件 157 个),包含 telegram.py、twilio.py 等;这个文件数不能直接解释成独立服务数量。〔注7〕已有渠道适配可以复用,不用让每个 agent 的配置都各写一遍渠道细节。〔注1〕

社区讨论里有 Gotify 与 Apprise、ntfy 与 Gotify 的比较。不过现有材料只到摘要级,社区还没选出共同赢家。〔注8〕

按需求选,先别增加接入层

下面是推荐选项;条件变化时,可以沿着同一张表重新选。〔注1〕

当前需求选择
单渠道,先通知自己ntfy
多渠道,希望集中配置Apprise
全自建ntfy 自建或 Gotify,按接收端环境比较
明确需要 SMS商业网关经 Apprise 接入;免费替代单独核对边界
需要决定何时发送优先原生 hooks/notify,缺口用一条命令约定

需求里出现 agent,不等于先要求增加 MCP 接口。对「完事通知我」这个范围,更合适的做法是先把发送入口与完成含义写清,再判断额外接入是否值得。〔注1〕

原生触发点存在,但配置不能直接搬

官方文档写明的入口如下。这里列的是事件与接口,不能据此推定它们具有相同的成功语义。〔注9〕

agent官方触发点
Claude CodeStop、StopFailure、Notification hooks
Codexnotify 在 agent-turn-complete 时运行外部程序;TUI notifications 可筛选完成或审批通知
Gemini CLIAfterAgent 对应 agent loop 结束,另有 Notification;hooks 通过 stdin/stdout JSON 交互

这些差异意味着接入时仍要按 agent 分别处理事件名和载荷,不能期待一份配置直接通用。〔注9〕做法是把差异留在触发点附近,向下调用同一个发送入口。〔注1〕

挂不上原生触发点时,可以在 prompt 里放这样的约定。这里借用 ntfy 示例中的 alert 名称,前提是已经配置好对应命令;这段文字本身不会安装任何东西。〔注2〕

本轮工作结束后调用一次 alert。该通知只表示本轮结束,不表示任务成功。最终回复另行说明是否满足验收条件;如果未满足,说明剩余工作。发送失败时如实报告,不要声称已经通知。

这是一份建议采用的约定,不是经过验证的跨 agent 标准。更倾向于让原生触发点承担发送时机,把 prompt 约定留作兜底;也不承诺 agent 每次都会遵守。〔注1〕

「轮次结束≠任务成功」需要写进约定。推荐先用语义保守的结束通知;只有完成条件得到检查,才允许消息写「任务成功」。否则手机上的一句「完成」,可能承载了发送方根本没有确认的含义。〔注10〕

自建、SMS 和审批各有边界

这里的通知限定为单向提醒,不给它附加审批语义。需要用户批准后再继续的工作,应该另行定义交互协议;peon-ping / Lucarne 这类双向工具形态可以作为参考方向,但不在本文通知范围内。〔注1〕官方文档写明,A2A v0.2.5 已有可选的任务状态 webhook push,包括完成状态;仍不能把它直接当作通知到人的通用方案。〔注11〕

对于有合规要求的场景,优先评估自建,但自建本身不等于满足合规。〔注1〕ntfy 的 topic 名称也不该承担访问凭证的职责:官方文档写明,默认访问是 read-write,匿名用户可以读写所有 topic。做法是把名称当可能被猜到的标识,设置 token,并采用官方文档写明的 auth-default-access: "deny-all",不靠隐藏 topic 名称保护内容。〔注4〕

SMS 放在最后评估。按 2026-10-18 回读的官方页面宣称,Textbelt 有每天 1 条免费额度;TextBee 使用自己的 Android/SIM 套餐,托管免费层为每月 300 条。送达能力与可靠性没有经过这次研究的实测。〔注12〕不能把这些额度解释成无成本的可靠通道,也不能因「免费」就跳过接收环境与套餐边界。〔注1〕

这次生态盘点也只能算抽样。选型时仍应该留意终端内置通知等其他接入形态,不能从少量搜索结果推导整个生态的大小,更不能据此宣称原生触发点之外没有值得用的方案。〔注1〕

换 agent 时,保留什么

判断是:渠道可以替换,约定和触发点才是要稳定的东西。迁移时先核对新事件是否仍对应原先约定的结束含义,再接回发送入口,而不是仅以手机响过一次验收。〔注1〕

留给团队的问题是:当通知写着「完成」,接收者是否可以据此采取下一步行动,还是必须回到终端重新判断?先和使用者确定这个答案,再决定通知应该说到哪一步。〔注10〕

文末注

来源 session:2026-10-18 research session,非 Retro 归档卡
真实上下文:作者需求:跟任何 agent 说『完事发消息给我』
证据级别:实测/自述/推断混合,逐条见注
session 发生时间:2026-10-18
Created:2026-10-18
Updated:发布日待定
结论性质:选型判断=推断;API 数字与官方文档引文=实测;免费额度页面宣称与社区/厂商比较=自述

  1. 〔推断〕作者的分层模型、选型推荐与接入约定,不是送达保证或官方标准。推断依据:https://docs.ntfy.sh/examples/ 、https://github.com/caronc/apprise 、https://code.claude.com/docs/en/hooks 。
  2. 〔实测:官方文档阅读〕长时间运行命令通知示例、alert alias 与 sleep 10; alert:https://docs.ntfy.sh/examples/ 。文中的 prompt 为作者拟定。
  3. 〔实测:GitHub API 查询记录,2026-10-18〕仓库 stars 与 pushed 日期。来源仓库:https://github.com/binwiederhier/ntfy 、https://github.com/caronc/apprise 、https://github.com/gotify/server 。
  4. 〔实测+推断〕官方配置文档写明默认访问为 read-write、匿名用户可读写所有 topic,私有部署建议 auth-default-access: "deny-all":https://docs.ntfy.sh/config/ ;把 topic 名当可猜标识并配 token,是作者的安全建议。
  5. 〔实测:官方 README 阅读〕Apprise 的 Telegram 与 Twilio URL schemes:https://github.com/caronc/apprise 。
  6. 〔自述:厂商文章〕关于 Gotify 与 ntfy 适用环境的作者观点,未作为独立实测结论:https://woopy.dev/blog/ntfy-vs-gotify 。
  7. 〔实测:目录检查,2026-10-18〕Apprise plugins 文件计数与文件名,计数不等于独立服务数量:https://github.com/caronc/apprise 。
  8. 〔自述:社区摘要〕仅证明存在这些比较讨论,不据此概括共同结论:https://www.reddit.com/r/selfhosted/comments/1akbgle/ 、https://www.reddit.com/r/selfhosted/comments/shw73e/ 。
  9. 〔实测+推断〕官方文档中的触发点;配置不能直接通用为作者据此作出的判断:https://code.claude.com/docs/en/hooks 、https://developers.openai.com/codex/config-advanced/ 、https://geminicli.com/docs/en/hooks/ 。
  10. 〔推断〕依据轮次结束与任务成功的语义差异,提出通知内容与验收约定:https://developers.openai.com/codex/config-advanced/ 、https://geminicli.com/docs/en/hooks/ 。
  11. 〔实测+推断〕A2A v0.2.5 可选任务状态 webhook push 为文档事实;不将其直接视为通知到人的通用方案是作者判断:https://a2a-protocol.org/v0.2.5/topics/key-concepts/ 。
  12. 〔自述:官方页面宣称,2026-10-18 回读〕Textbelt 与 TextBee 免费额度及后者的自有 Android/SIM 要求;未测试可靠性或送达能力:https://textbelt.com 、https://textbee.dev 。