Skip to content

通知与站内信 ​

本文面向带客服团队的站长与运营主管,讲清站内信、邮件、短信三条通知通道各自怎么开关、模板怎么配、哪些业务动作会真的发信,以及发不出去时从哪个后台入口查。变量语法、模板编号与触发点都取自当前代码;某项能力「后端有定义但代码里没有调用」的地方会直接写明。

1. 三条通道的分工与总开关 ​

通道存储与投递总开关位置默认值
站内信写 user_notifications 表,用户在控制台查看无全局开关;受用户自己的通知偏好约束开启
邮件走邮件驱动(集成插件)同步或入队发送,落 message_logs「站点内容 → 站点设置」里的「启用邮件通知」关闭
短信走短信驱动(集成插件),落 message_logs「站点内容 → 站点设置」里的「启用短信通知」关闭

两个总开关的存储名分别是 notification.email_enabled 与 notification.sms_enabled,取值为 1、true、on 时才算开启。它们只拦「系统主动发信」,不拦两条旁路:

  • 「集成插件」页里的测试发信走驱动直发,不受总开关约束——测试成功不代表对外能发。
  • 关闭总开关后,系统仍会先写一条 pending 的 message_logs 记录,再以「邮件通知未启用」或「短信通知未启用」把这条记录置为 failed。所以日志里查得到失败记录、错误信息就是这句话,这是判断「是开关没开而不是通道坏了」的最快依据。

通道的「发不发得出去」还取决于驱动能不能解析出来:邮件与短信都先从当前启用绑定里挑一个可用驱动,挑不到就报「邮件驱动 [xxx] 未注册」或「短信驱动 [xxx] 未注册」。也就是说开关开了、模板也配了,但插件没装或没绑定,一样发不出去。配置步骤见邮件与短信。

2. 站内信 ​

站内信是唯一不依赖外部通道的通知,插件没配好也能送达。用户在控制台的通知中心看到的是两类内容的合并流:一类是公告(内容文章 + 已读记录),一类是个人通知(user_notifications)。未读数也按这两类相加。

每条个人通知包含类型、标题、正文、跳转链接和一段结构化数据。与工单相关的类型有 4 个:

类型常量触发时机跳转
ticket_staff_reply员工回复工单,或上游管理员回复经回调写入/client/tickets/{id}
ticket_pending_reminder员工回复后长时间无客户跟进,关闭前催办/client/tickets/{id}
ticket_auto_closed工单被超时自动关闭/client/tickets/{id}
ticket_reopened工单被重开(管理端或客户自己触发)/client/tickets/{id}

运营要点:

  • 写入是防御性的。 表不存在或写库抛错时只记一条 warning 日志,不会阻断建单、回复等主流程。所以「站内信少了」可能是写入被吞,去 storage/logs/laravel.log 搜 [站内信] 写入失败。
  • 营销类受用户偏好控制。 写入时带营销标记的通知,只在用户开启了 marketing_alert 时才落库;工单、账单这类业务必要提醒不受影响。
  • 标题会被截断到 191 字符;正文按写入内容原样保存,不截断。
  • 用户在控制台可以逐条已读或全部已读;已读状态存 read_at,不存在「已读但未读数不减」的情况——未读数就是 read_at 为空的行数。

3. 邮件模板与变量 ​

模板入口在「站点内容 → 通知与接口 → 邮件模板」,权限码 settings.view;点进单个模板是「邮件模板详情」页,同一个页面也服务短信模板。模板不是纯设置项,而是 notification_templates 表里的行,按 channel(email / sms)+ code(编号)唯一。

系统内置 31 个邮件模板(编号 100001 至 100031),全部由 EmailNotificationTemplateDefaults 提供默认主题与 HTML 正文,可用命令 php artisan app:seed-email-templates 幂等恢复默认。字段与影响范围:

字段说明
subject邮件主题模板;渲染后会去掉换行
content正文模板,可填完整 HTML 文档或正文片段;片段会被套上统一外框
is_enabled关掉后该模板的邮件直接跳过发送,不写日志、不报错
variables该模板可用变量清单,页面上以「可用变量」展示
provider_template_id仅短信:运营商审核通过的模板 ID

改主题或正文后 is_custom 会被标记为自定义;没有「一键回滚单个模板」的按钮,恢复默认要重跑上一条命令(会覆盖全部邮件模板的自定义内容,慎用)。

变量占位符有两种写法,邮件与短信都支持:

  • {{变量名}} 直接替换。在邮件正文里,HTML 模板中的替换值会被转义,纯文本模板则自动转成段落。
  • {{#变量名}}…{{/变量名}} 条件块:变量有值时保留块内内容,空值(null、空字符串、false)时整块删掉。邮件 {{login_tip}}、短信正文里的可选段落都靠它。

此外短信正文额外支持单花括号 {变量名},用于兼容运营商自有模板语法;邮件正文不支持单花括号写法。两个变量 site_name 与 site_logo 由系统自动注入,不需要调用方传入。写成 {{ }} 之外的其它形式(例如拼错的变量名)不会被替换,而是原样留在正文里,因此改完模板务必用测试发送看真实结果。

预览与测试:详情页右侧是实时预览,右上角「测试发送」会按模板里出现的全部变量自动填充示例值(联系方式类变量给的是示例值),真实走一次发送链路并回显成功/失败条数。测试发送需要 settings.manage 权限,且一次只允许一个收件人。

4. 工单通知的实际触发点 ​

下表是当前代码里真实会发的工单通知。收件人范围由工单处理人决定,规则见工单与客服第 4 节。

业务动作邮件模板收件人同时写站内信
客户新建工单100026 管理员新工单提示客服(见下方规则)否
客户回复工单100027 管理员工单回复通知客服(见下方规则)否
员工回复工单100024 工单新回复通知客户是
工单超时自动关闭100025 工单自动关闭通知客户是
工单重开无邮件—是
关闭前催办无邮件—是
上游管理员回复经回调写入无邮件—是

三个容易踩的点:

  • 客户侧只有「员工回复」和「自动关闭」发邮件,重开与催办只有站内信。想让客户一定看到,就要接受「他必须登录控制台」这个前提。
  • 给客服的两封邮件受处理人影响极大:工单未指派时发给全部启用且持 ticket.reply 的管理员,已指派时只发给处理人一个人。指派出去了,就等于替其他人静音了。
  • 100023 工单开通通知、以及短信目录里的工单类编号(100010、100012、100028)在当前代码里没有任何调用方。 它们定义了编号与文案模板,但改它们不会产生任何发送行为(未在代码中确认存在其它调用路径)。

模板变量与实际传参存在缺口,这是已知现象:100025 的默认正文含 {{ticket_closed_at}} 与 {{close_reason}},但自动关闭时只传了 display_name、ticket_id、ticket_subject、status、tickets_url、login_tip,这两个变量渲染为空。要客户看到关闭时间,得自己改模板,改用实际传入的变量(未在代码中确认这两个变量是否计划补齐)。

5. 短信的实际能力 ​

短信这条通道要先明确一件事:当前代码里真正会发出去的只有验证码短信(编号 100001),它只有一个入口——客户端获取手机验证码接口 POST /api/v2/client/auth/phone-code(未在代码中确认存在其它发送入口),用 purpose 区分用途:注册、登录、重置密码、换绑/绑定手机号、校验已绑定手机号,以及未指定时的通用场景。该接口限流为每分钟 3 次,发送前另按「渠道 + 手机号 + IP」做一道发码频控,超限直接返回错误而不发短信;「校验已绑定手机号」这一类还要求目标号码与当前账号已绑定号码一致。邮箱验证码走另一条链路(POST /api/v2/client/auth/email-code,同样每分钟 3 次,邮件模板 100001)。短信侧非阿里云驱动使用系统内置正文,阿里云驱动走驱动自带的模板与服务商模板 ID。

其余短信模板的情况是:

  • notification_templates 里存在 channel=sms 的编号目录(100001 至 100033),后台「短信模板」页可编辑正文、启用状态与「供应商模板 ID」,但除验证码外的正文没有内置默认值,需要按服务商要求自行填写。
  • 目录里的业务编号(登录提醒、续费提醒、账单提醒、工单类等)在当前代码中没有调用方(未在代码中确认存在其它调用路径)。配了也不会发,不要据此向客户承诺短信提醒。
  • 阿里云驱动的验证码正文由插件按 purpose 提供,系统层只保留等价的回退文案;purpose 不影响系统其它逻辑,也不改变日志里的模板编号。

投放前的最小验证:在「集成插件 → 短信」页确认已启用并绑定驱动,再用「短信模板」页对 100001 做一次测试发送,然后去「日志中心 → 日志管理 → 短信日志」看 status 是否为 success。

6. 用户的接信偏好 ​

用户在控制台「通知偏好」里能改的开关存在用户表上,接口是 GET/PUT /api/v2/client/auth/notification-preferences。它们对系统发信的影响并不相同,混为一谈会得出错误结论:

用户偏好实际影响
marketing_alert控制带营销标记的站内信是否落库;默认关闭,关闭时营销通知直接丢弃
login_email_alert是否发送登录提醒邮件;邮箱为空或该值为 0 时登录提醒被静默跳过
login_notify登录通知的当前生效值,未设置时回退取 login_email_alert
login_location_alert异地登录提醒
password_change_alert密码变更提醒
phone_change_alert手机号变更提醒
email_change_alert邮箱变更提醒

也就是说:用户关不掉工单、账单这类业务通知——站内信照写、邮件照发。能被用户关掉的是营销推送与安全提醒,而安全提醒关掉后账号被盗用也没有邮件可查,续费与工单通知不受其影响。

7. 发送日志与排查入口 ​

三条通道的记录都落在 message_logs 表,后台按通道分两个页面展示,权限码都是 log.list:

页面列表里能直接看到的列常用过滤器
「日志中心 → 日志管理 → 邮件日志」收件人、模板编号、主题、正文摘要、状态、驱动、Trace ID、错误信息、记录时间邮箱、插件 ID、驱动、Trace ID、关键词(模板编号/主题/正文)、状态
「日志中心 → 日志管理 → 短信日志」接收号码、模板编号、内容摘要、状态、驱动、Trace ID、服务商请求号、错误信息、记录时间手机号、插件 ID、驱动、Trace ID、关键词(模板编号/请求号/内容)、状态

排查按固定顺序走,能省掉大部分来回:

  1. 先确认动作有没有触发:工单是否被指派决定了收件人范围;客户手机号/邮箱是否为空决定客户侧邮件是否被静默跳过(服务端在收件人为空时直接返回,不记日志)。
  2. 再看总开关:日志里出现「邮件通知未启用」「短信通知未启用」就是开关问题,不是通道问题。
  3. 再看模板开关:模板被停用时邮件是完全跳过的——不写日志、不报错。日志里查不到记录,又确认动作和开关都正常,就去模板页核对 is_enabled。
  4. 再看驱动:报「驱动未注册」说明插件没装、没启用或没绑定;报驱动自己的错误码说明是服务商侧问题,错误原文就在 error_msg 字段里。
  5. 最后看 Trace ID:日志里带 trace_id,同步发给服务商排查时用它对齐同一次请求。

几个后台入口能直接串起上下游:邮件与短信日志每行都有「详情」,正文、请求号与错误信息在那里看全;详情里带 ID 类字段时可按 ID 类型(工单 ID、用户 ID、订单 ID 等)跳到对应页面。工单维度的发送情况可回到工单交流页对照;工单上游投递另有专门页面「日志中心 → 日志管理 → 上游工单投递」(过滤器含工单号、回复 ID、原因码、供应商),上传与清理记录在「上游附件上传」,自动任务(含工单自动关闭)在「自动任务日志」。

关于日志内容本身:邮件正文与短信正文在日志里是完整保存、不脱敏的,收件地址也按原值展示,这是为了让运营能真实复核发出去了什么。相应地,这条数据里会出现客户邮箱、手机号与通知正文,按贵站的权限管理决定谁能看「日志中心」——log.list 是一个单独的权限码,不要随手发给所有人。

8. 失败重试与限流 ​

重试能力只覆盖三个入队任务,它们的参数完全一致:最多尝试 3 次,退避间隔 30 秒、120 秒、300 秒,单次超时 120 秒,重试耗尽后由 failed() 记一条 warning 日志并带上收件地址与上下文。

任务覆盖的通知
SendTicketNotificationEmailJob工单相关的全部邮件
SendClientLoginEmailAlertJob用户登录提醒邮件
SendClientLoginFailureEmailAlertJob用户登录失败提醒邮件

其余邮件与全部短信都是同步发送、不重试:NotificationService::sendEmail 与 SmsService::sendTemplateSms 在请求内直接调用驱动,失败立刻把异常抛回调用方(同时把 message_logs 的状态更新为 failed 并写入 error_msg)。工单邮件也只在队列可用时才排队:队列驱动为 sync 时改为「响应后发送」,database 驱动下 jobs 表不存在、redis 驱动下没有可用客户端时同样退回不重试的路径。

发送侧没有额外的通知专用限流;真正拦你的是接口层的限流,例如客户建单、回复、关闭各为每分钟 10 次,图片上传每分钟 12 次。批量触达要自己控制节奏,撞到限流时才会在日志里看到服务商返回的 429 之类的错误。

9. 上线前的通知体检 ​

按顺序做一遍,能在放量前发现绝大多数通知问题:

  1. 「站点设置」里两个总开关打开,站点名称与 Logo 已换成自家品牌(模板里的 {{site_name}}、{{site_logo}} 取的就是这两个值)。
  2. 「集成插件」里邮件与短信各自启用并绑定了一个驱动,用到「测试」页确认连接正常。
  3. 用「测试发送」各发一次邮件与短信验证码(两边的编号都叫 100001,按通道区分),确认收到。
  4. 去「邮件日志」「短信日志」确认两条记录都是 success,并记下它们的 Trace ID 以熟悉排查入口。
  5. 用一个测试账号走一遍:提交工单、客服回复、超时催办(可临时把自动关闭小时数调小来验证)、自动关闭。核对四条站内信与两封客户邮件是否都到。
  6. 确认改过正文的模板仍能渲染:详情页预览没有残留的 {{ }} 占位符,测试发送的正文与预期一致。
  7. 密码变更、登录提醒等安全邮件的开关由用户自己掌握,发布公告时说明可以在控制台通知偏好里调整,避免客服被当成开关责任方。

关联文档 ​

基于 AGPL-3.0-or-later 发布