:通知渠道插件——如何把 Dify 推送到钉钉/企业微信等渠道?)
Dify 插件开发实验06通知渠道插件——如何把 Dify 推送到钉钉/企业微信等渠道Dify 实验系列 · 插件开发 06/12 | 实验编号DIFY-106-06基于 Dify 1.16.1 实测2026-081. 业务场景先讲一个我们实际遇到的场景。客服工单 SaaS 的通知场景用户提交工单后客服要收到「新工单待处理」的提醒工单状态变了用户要收到「您的工单已更新」的消息。团队内部用企业微信对外通知走钉钉风格 webhook还可能对接自建的消息网关——同一个通知内容要按渠道适配格式发出去发失败了还要自动重试、能查回执。我们第一次做通知时第一反应也是「POST 一下 webhook 不就行了」。真正动手才发现——「发出去」和「确保送达」是两回事企微和钉钉的 payload 格式不一样混着用直接发不出去webhook 静默失败没人知道工单晾几个小时没人处理网络抖动失败不重试就丢通知4xx 的错重试一百遍也没用。这不是个例。任何「系统要主动触达用户/内部协作」的场景都是这个模式工单通知、告警推送、审批提醒、营销触达——渠道多、格式杂、发送还可能失败通知能力必须是一个「带渠道校验/模板/重试/回执的完整能力」而不是一段裸 http 调用。2. 场景痛点这个流程的痛点在通知链路上体现得最直接渠道格式各不同企业微信的 payload 和钉钉不一样混着用直接发不出去——每接一个渠道就要写一套适配。发送失败无感知webhook 静默失败客服没收到提醒工单晾了几个小时没人处理业务无感知。重试无策略临时网络抖动就失败不重试会丢通知4xx 类错误URL 错/鉴权错重试又毫无意义还拖时间。渠道地址写死在代码每个环境测试/生产/客户现场的 webhook 地址都不同写死意味着每换一处就改代码。本质上通知的难点不在「发出一条消息」而在「多发、可重试、可回执」——渠道要抽象、失败要降级、状态要可查。3. 方案为什么是通知工具插件选通知工具插件我们实际对比过渠道抽象同一消息按渠道适配 payload企业微信/钉钉/mock新增渠道只加一个适配函数不破坏现有重试策略分层仅对可重试错误超时/5xx重试 3 次×2s4xx 不重试直接失败——不浪费时间也不丢通知回执结构化{sent, message_id?, reason?}显式返回下游 IF-ELSE 按回执分流失败走降级分支业务有感知。这篇文章我们就用它把工单流程里的 http 通知节点升级为正式插件notify工具channel 枚举 wecom/dingtalk/mock title/body/priority跑通「渠道适配 → 重试 → 回执 → 降级」的完整通知链路。4. 整体架构【插件内部】notify渠道校验白名单式模板渲染priority 拼装前缀渠道 payload 适配POST webhooktimeout54xx 不重试 / 5xx·超时重试 3 次×2s回执【验证应用】否是开始channel/title/body发送通知notify回执判断IF-ELSE contains 「sent」: false发送成功end_sent发送失败降级end_degraded链路很清晰收通知参数 → 渠道校验 → 模板渲染 → payload 适配 → 带重试发送 → 回执分流。关键设计是「失败显式化」——回执里带 sent/reason下游永远知道这次通知到底发出去没有。5. 模块设计5.1 渠道适配与重试tools/notify.py同一消息按渠道输出不同 payload重试只对可重试错误CHANNELS(wecom,dingtalk,mock)RETRY_TIMES3RETRY_INTERVAL2TIMEOUT5defadapt_payload(channel,title,body,priority):contentf[{priority}]{title}\n{body}ifpriorityandpriority!normalelsef{title}\n{body}ifchannelwecom:return{msgtype:text,text:{content:content}}ifchanneldingtalk:return{msgtype:text,text:{content:content}}return{channel:channel,title:title,body:body,priority:priority}def_send_with_retry(self,url,payload):last_reasonforattemptinrange(RETRY_TIMES):try:resprequests.post(url,jsonpayload,timeoutTIMEOUT)ifresp.status_code400:return{sent:True,message_id:mid}# 成功回执if400resp.status_code500:# 4xx 不重试URL 错/鉴权错重试无意义return{sent:False,reason:fhttp{resp.status_code}(not retried)}last_reasonfhttp{resp.status_code}exceptrequests.exceptions.Timeout:last_reasontimeoutexceptrequests.exceptions.RequestExceptionase:last_reasonfnetwork error:{type(e).__name__}ifattemptRETRY_TIMES-1:time.sleep(RETRY_INTERVAL)return{sent:False,reason:f{last_reason}(retried{RETRY_TIMES}x)}5.2 渠道 URL 走凭证provider/notify_tool.yamlwecom_url/dingtalk_url/mock_url 三个 credential环境差异模板在代码业务差异——换环境只改凭证。注意 manifest tags1.16 daemon 合法枚举没有communication用utilities详见采坑点。6. 运行验证输入预期结果三渠道各发一条mock 端核对payload 与平台格式一致wecom/dingtalk text 结构✅ 一致priorityhigh / normalhigh 拼[high]前缀normal 不拼✅ 一致mock 配置 ?fail2前两次 500 第三次成功回执 senttrue✅ 实测耗时 4.0s2 次间隔mock 持续 500重试耗尽 sentfalse reason✅ 4.0s4xx 不重试 0.0s 直接失败非法 channel / 缺 title参数错误 param_invalid✅ 一致workflow 注入 fail99回执 containssent: false→ 降级分支✅ 双出口正确环境Dify 1.16.1Docker Composemock webhook 服务 tmp/mock_webhook.py127.0.0.1:8004?failN 模拟失败 / ?delay秒 模拟慢响应 / GET /received 查看凭证指向 host.docker.internal:8004。7. 实战坑坑现象修复plugin_tag 枚举manifest tags 写 communication → 打包报错 plugin_tag 校验失败用合法枚举 utilities1.16 合法集search/image/videos/weather/finance/design/travel/social/news/medical/productivity/education/business/entertainment/utilities/other/agent/rag/trigger4xx 也重试URL 错/凭证错重试无意义还拖时间4xx 不重试直接失败仅 5xx/超时/网络重试 3 次×2s降级静默发送失败不告知下游业务无感知回执 sentfalse 显式返回IF-ELSE containssent: false分流降级分支渠道 payload 差异企微与钉钉格式混用发不出去每渠道 adapt_payload 函数新增渠道加函数不破坏现有webhook URL 写死换环境就要改代码渠道 URL 在 credentials环境差异模板在代码业务差异通知超时拖慢主流程webhook 慢响应卡住整个流程requests timeout5超时归重试路径mock 未知渠道不报错测不出 4xx 不重试路径mock 对白名单外渠道返回 404模拟真实 URL 错8. 实验文档及源码获取实验文档DIFY-106-06通知渠道插件.md验证应用 DSLdify106_06_验证应用.yml插件安装包dify106_06_notify_tool.signed.difypkg源码目录dify-106/dsl | dify-106/plugins文章聚焦核心配置与采坑点完整分步操作与渠道 payload 对照表见实验文档原文。下一篇Dify 插件开发实验07私有模型网关接入——如何让 Dify 用上私有模型网关 你在这个实验的场景里踩过什么坑欢迎评论区分享你的实战经验。