指南

如何为应用添加通知:应用内、邮件和推送

要为 Web 应用添加通知,可以先从应用内通知开始:在数据库中建一张通知表,再用一个铃铛图标显示未读项目。对于用户离开时需要知道的事情,可以使用邮件服务提供商添加邮件通知;只有在及时提醒确实重要时,才添加 Web 推送,因为它需要 Service Worker、用户授权,并且在 iPhone 上还需要把应用保存到主屏幕。无论使用哪些渠道,都应在服务端的一个地方创建所有通知,并让用户自己选择接收哪些通知。

本指南对比了各种渠道,展示了一个简单的架构,并提供了可在 AI 应用构建工具中使用的提示词。

主要渠道对比

渠道何时触达用户设置难度运行成本最适合
应用内(铃铛、弹出提示)用户正在使用你的应用时低除数据库外没有其他成本活动动态、提及、状态变化
邮件用户查看邮箱的任何时候中等:需要服务商和域名设置按邮件数量由服务商收费收据、摘要,以及任何重要内容
Web 推送浏览器或已安装的 Web 应用,即使关闭时也可以中等到高通常免费发送时间敏感的提醒
短信几乎总能送达中等;需遵守同意规则按条收费预约提醒、紧急提醒
聊天工具(Slack、Teams)用户在工作时低到中等通常免费内部工具和团队提醒

大多数应用需要“应用内 + 邮件”的组合。只有当时机真的重要时,例如预约提醒或订单已准备好,才值得添加推送和短信。

通知系统如何组合在一起

保持简单,并且集中管理:

  1. **事件发生。**有人发表了评论、支付失败、预约在明天。
  2. 服务端调用一个函数,例如 notify(user, type, data)。
  3. 该函数检查用户的偏好设置,并在 notifications 表中写入一行记录(用于铃铛)。
  4. 然后发送到用户为该类型开启的其他渠道:通过邮件服务商发送邮件,通过 Push API 发送推送。
  5. 前端显示未读通知,并在用户打开时标记为已读。

统一的入口意味着你可以添加渠道、修改文案或遵守偏好设置,而无需在代码库中到处查找。

需要存储什么

通知表通常需要:用户 ID、类型(例如 comment_reply)、简短的标题和正文、指向相关页面的链接、创建时间和已读时间。另外还有一张偏好设置表,用于记录每位用户针对每种类型希望使用哪些渠道。

显示新通知

  • **在页面加载时获取,并每 30–60 秒轮询一次。**简单且可靠。
  • **使用 WebSockets 或服务器发送事件实现实时更新。**体验更即时,但会增加更多组成部分。除非即时更新确实重要,否则先从轮询开始。

邮件通知

邮件需要发送服务商和已验证的域名;具体细节见如何从你的应用发送邮件。针对通知本身:

  • 从服务端发送,绝不要在浏览器中使用服务商密钥。
  • **合并频繁发生的事件。**一封邮件写“3 条新评论”,胜过发送三封邮件。
  • 为低优先级的动态提供每日或每周摘要。
  • **在非必要邮件中加入退订或偏好设置链接。**营销邮件的规则因国家而异,请确认适用于你的规则。

Web 推送通知

Web 推送让你的网站能够发送类似应用通知的提醒。根据 MDN 的 Push API 文档,其工作方式如下:

  1. 你的网站注册一个 Service Worker,这是浏览器可以在后台运行的脚本。
  2. 用户同意后,Service Worker 通过 PushManager.subscribe() 进行订阅。浏览器会返回一个订阅信息,其中包含端点和加密密钥。
  3. 你的服务器保存该订阅信息;当事件发生时,向该端点发送消息,通常使用 VAPID 密钥进行签名(VAPID 是用于标识你服务器的密钥对)。
  4. 即使你的页面没有打开,浏览器也会启动你的 Service Worker,由它显示通知。

MDN 还指出,端点应当保密,因为知道该端点就足以向对应用户发送消息。

iPhone 和 iPad

WebKit 在 iOS 和 iPadOS 16.4 中为 Web 应用加入了 Web Push,其公告中提出两个条件:网站必须添加到主屏幕,并且权限必须响应用户操作来请求,例如点击“订阅”按钮。因此在 iPhone 上,推送只能触达已安装你 Web 应用的用户。关于安装 Web 应用意味着什么,请参见原生应用与渐进式 Web 应用。

恰当地请求权限

不要在首次访问时就请求。先说明好处(“在课程开始前一小时获得提醒”),然后在用户点击按钮时再显示浏览器的权限提示。如果有人拒绝了,浏览器可能不再允许你再次请求,因此过早弹出提示可能会永久失去这位用户。

定时通知需要触发器

“提前 24 小时提醒”或“每周一发送周报”需要有东西按计划运行。这属于后台任务问题,参见如何运行后台任务。一种常见做法:外部调度器每隔几分钟调用一个受保护的端点,由该端点发送所有到期的通知。

给 AI 构建工具的提示词

先从应用内通知开始:

添加应用内通知。创建一张 notifications 表(user、type、title、body、link、created_at、read_at)。添加一个服务端函数 notify(userId, type, data),所有功能都调用它。在页头显示一个带未读数量的铃铛图标,下拉列表显示最新的 20 条,并提供“全部标为已读”按钮。每 60 秒轮询一次新通知。

然后是偏好设置和邮件:

添加通知设置:针对每种类型(评论回复、提及、账单),让用户选择应用内、邮件、两者都要或都不要。notify() 必须遵守这些设置。使用 [PROVIDER] 发送邮件,并且只在服务端使用密钥 [ENV_NAME]。将评论邮件分组,确保同一用户每 15 分钟最多收到一封邮件。

如果需要推送,再添加:

添加可选的 Web 推送。注册一个 Service Worker,生成 VAPID 密钥,并将私钥保存为机密。在设置中添加一个“启用通知”按钮,仅在点击时才请求权限。为每位用户、每台设备保存一条订阅信息,当用户已选择接收时,由 notify() 发送推送,并删除推送服务报告为已过期的订阅。

常见错误

  • **通知散落在代码各处。**每个功能都以不同方式单独发送邮件,偏好设置也被忽略。应只使用一个 notify() 函数。
  • **在页面加载时请求推送权限。**大多数人会拒绝,而你可能再也没有第二次机会。
  • **没有偏好设置。**无法关闭嘈杂提醒的用户,要么把所有通知都关掉,要么直接离开。
  • **在浏览器中发送。**邮件和推送的密钥应只保存在服务端。
  • **不清理失效的推送订阅。**过期的订阅会不断堆积,浪费发送次数。
  • **通知文本中包含敏感信息。**锁屏会显示这些内容。“你有一条新消息”比消息本身更安全。
  • **没有发送记录。**当用户说“我没收到邮件”时,带有服务商消息 ID 的日志能帮你查明原因。

检查清单

  • 所有地方都使用同一个服务端 notify() 函数
  • 通知表包含已读状态,并有带未读数量的铃铛
  • 按类型设置渠道偏好,并由 notify() 遵守
  • 邮件从服务端发送,使用已验证的域名;对频繁事件进行摘要或合并
  • 只在用户操作之后请求推送权限,并附上说明
  • 已在桌面端和已添加到主屏幕的 iPhone 上测试推送
  • 已移除过期的推送订阅
  • 推送或邮件预览中不包含敏感内容
  • 定时提醒有可靠的触发器

Mythex 中的通知

Mythex 不为你构建的应用提供内置的邮件或推送服务,因此你需要使用自己的邮件服务商,并生成自己的推送密钥,将其保存为项目机密。应用内部分只需要项目的数据库,你在需要时请求即可添加。使用上面类似的提示词进行构建,在预览中测试铃铛和邮件,并在已发布的 URL 上测试推送,因为 Service Worker 和推送需要真实的 HTTPS 地址。文档中的从你的应用发送邮件配方提供了入门提示词。Mythex 没有记录定时任务,因此请按照如何运行后台任务中的说明,使用外部触发器来规划定时提醒。

常见问题

为 Web 应用添加哪种通知最简单?

应用内通知:在数据库中建一张通知表,再用一个铃铛图标显示未读通知。它不需要任何第三方服务。下一步是邮件,需要使用邮件服务提供商。

Web 应用能向 iPhone 发送推送通知吗?

可以,但有限制。WebKit 在 iOS 和 iPadOS 16.4 中为主屏幕上的 Web 应用加入了 Web Push。因此用户必须先把你的 Web 应用添加到主屏幕,并且权限请求必须在用户点击之后发起,例如点击“订阅”按钮时。

网站关闭时推送通知还能工作吗?

可以。Push API 会把消息投递给网站的 Service Worker,消息到达时浏览器会启动它,因此页面不必保持打开。但用户必须先授予权限。

如何避免通知打扰用户?

只通知用户真正关心的事情,把密集的通知合并为一条消息,让用户在设置中按类型选择接收渠道,并且只有在用户看到推送的价值之后,才请求推送权限。

Mythex · Pricing · 用 AI 构建任何东西 · 网站和应用模板 · Mythex 对比 · AI 应用构建术语表 · 免费的 AI 应用构建工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 繁體中文(香港) 繁體中文(台灣)