
自托管 Chatwoot 踩坑实录:从"部署完成"到"真正能用"的 11 个教训
想给产品官网加个在线客服,Crisp 注册不上,转向自托管 Chatwoot。Docker 部署十几分钟搞定,用起来才发现坑一个接一个:邮件发不出去、链接变乱码、手机没推送……本文整理了 11 个官方文档没写的实战教训,帮你少走弯路。
写在前面
事情是这样的。我给自己的几个产品做了官网,一直想加个在线客服 Widget——右下角小气泡那种,用户点开就能直接聊。
前阵子参加 Let's Vision 大会,晚上就想把这事儿落地。本来打算直接用 Crisp,结果注册死活过不去(还发推吐槽了一下),试了几次也懒得查原因了。干脆让 AI 帮我调研开源替代品,翻了一圈选了 Chatwoot——开源、自托管、社区活跃,看着不错。
当天晚上就用 Claude + Docker Compose 部署好了,十几分钟的事。打开后台建好 Inbox,Widget 代码贴到官网上,第一印象挺好的。
等大会结束回来真正用起来,问题就一个接一个了:邮件发不出去、发出去了链接是乱码、手机 App 收不到推送、用户回复的邮件石沉大海……每个问题背后都是一个官方文档里没写的坑。
这篇文章就是把这些坑整理出来,给想自托管 Chatwoot 的朋友避避雷。
第一章:邮件发送——一场连环踩坑
Chatwoot 的核心流程之一是:用户在 Widget 里发消息 → 客服在后台回复 → 如果用户已经离开网站,Chatwoot 通过邮件通知用户"你有新消息"。
所以邮件发送是刚需。但这一块,我前前后后踩了五个坑。
坑 1:改了 .env,重启后没生效
这是第一个坑,也是最隐蔽的。
我在 .env 里配好了 SMTP 信息,然后很自然地执行了:
结果邮件还是发不出去。我反复检查 SMTP 配置,确认没有拼写错误,百思不得其解。
后来用 Rails runner 进容器查环境变量,发现 SMTP_ADDRESS 居然是空的!.env 里明明写了啊?
真相是:docker compose restart 只重启进程,不会重新读取 env_file。
这不是 Chatwoot 的问题,是 Docker Compose 的机制。但如果你不知道这一点,能在这儿卡半天。
正确做法:修改 .env 后,永远用:
这条命令会销毁旧容器、创建新容器,新容器启动时才会读取最新的 .env。
建议把这条命令刻进肌肉记忆。后面每次改配置,我都被它救了。
坑 2:邮件"成功"了,但其实根本没发
修复了环境变量的问题后……不对,其实在修复之前,我被一个现象误导了很久:Sidekiq 后台显示 MailDeliveryJob 执行成功,耗时 3.83ms。
3.83 毫秒?真正连接 SMTP 服务器、握手、发送,怎么也得几百毫秒到几秒。3 毫秒就"成功"了,明显不对劲。
进容器一查,ActionMailer::Base.delivery_method 竟然是 :sendmail 而不是 :smtp。
原因在 Chatwoot 的源码里:
就是说,如果 SMTP_ADDRESS 为空(因为坑 1,环境变量没加载进来),Chatwoot 会静默回退到 sendmail。容器里又没装 sendmail,所以邮件就这么无声无息地消失了,而 Job 状态还显示"成功"。
这个坑的恶心之处在于:没有任何报错,日志里一切正常,你以为邮件发出去了,其实根本没有。
坑 3 & 4:逗号——一个字符,两次翻车
我的公司名叫 5KM Tech, LLC,很自然地写进了配置:
结果邮件又发不出去了。
邮件地址解析中,逗号是分隔符。5KM Tech, LLC <[email protected]> 会被解析成两个地址:5KM Tech 和 LLC <[email protected]>,两个都不合法。
好,我把 .env 里的逗号去掉了。结果还是报 Net::SMTPSyntaxError。
查了半天发现,Chatwoot 的 Inbox 设置里有个 business_name 字段,它会覆盖 .env 中的发件人名称。我在后台填的还是 5KM Tech, LLC——带逗号的版本。
所以这个逗号让我翻了两次车:一次在 .env 里,一次在后台 Inbox 设置里。
教训:邮件相关的所有"名称"字段,都别用逗号。如果公司名确实有逗号,要么去掉,要么用引号包起来("5KM Tech, LLC")。
顺带一提,我同一台服务器上部署的 listmonk(邮件营销工具),也因为同样的逗号问题翻车了。Go 的 mail.ParseAddress() 同样不接受这种格式。所以这不是 Chatwoot 特有的坑,而是邮件协议层面的通用问题。坑 5:WebWidget 必须配 MAILER_INBOUND_EMAIL_DOMAIN
前面的坑都填了,SMTP 连接正常了,结果又报了个新错:
Chatwoot 发邮件时需要生成一个 reply+uuid@domain 格式的 Message-ID,它会按优先级查找域名:
- Account 数据库中的
domain字段 - 环境变量
MAILER_INBOUND_EMAIL_DOMAIN - Inbox channel 的 email 地址
问题是,WebWidget 类型的 Inbox 没有 email 属性(它不是邮件 Inbox),所以第 3 层不可用。如果前两层也没配,就会报这个错。
修复:在 .env 中添加:
这个变量在官方文档中存在,但没有明确说明"WebWidget 用户必须配置",很容易忽略。
第二章:邮件模板——官方不让改,我们自己改
邮件能发了,用户也收到了。但打开一看,邮件里只有消息内容,没有任何"回到网站继续聊"的入口。
用户收到邮件后:诶,有人回复我了,然后呢?去哪儿看?
我去 GitHub 翻了一圈,发现社区早就提过这个需求(#3379、#3725、#8262),官方的态度是:我们设计的流程是让用户直接回复邮件来继续对话。
但现实是,很多场景下用户不会回复邮件(尤其是国内用户),他们更习惯回到网页上操作。
坑 6:只能通过 Volume Mount 覆盖模板
Chatwoot 不提供模板编辑器。邮件模板是 .erb 文件,硬编码在 Rails 应用里。
唯一的办法是从容器里把模板提取出来,修改后通过 Docker volume mount 覆盖回去:
注意 rails 和 sidekiq 两个服务都要挂载,因为邮件发送是 Sidekiq 后台任务处理的。
升级 Chatwoot 后要重新检查模板兼容性:
如果官方模板有变化,需要手动合并自定义部分。
坑 7:website_url 不含 https://
我在模板里加了一个"Continue Conversation"按钮,链接用的是模板自带的 @channel.website_url 变量——这个变量会自动取当前 Inbox 配置的网站地址,不同 Inbox 对应不同网站,很优雅。
结果用户点了按钮,macOS 弹出一个"未设定用来打开 URL x-webdoc://xxxxx/zipic.app?chat=open 的应用程序"。
一看就明白了:Inbox 配置里存储的 website_url 是 zipic.app,没有 https:// 前缀。邮件客户端遇到没有协议的 URL,就按自己的逻辑解析,变成了一个乱码协议。
修复:在模板中做协议前缀检测:
几个关键点:
@channel.website_url是动态的,不同 Inbox 自动对应不同网站- 只在 WebWidget 类型 Inbox 时显示(Email 类型 Inbox 没有
website_url,respond_to?会返回 false) ?chat=open参数是自定义的,配合前端 JS 使用
坑 8:前端要配合写 JS 自动打开 Widget
光在邮件里加个链接跳到官网还不够——用户到了官网,Widget 默认是收起来的小气泡,用户还得自己点开。
需要在网站嵌入 Widget 的地方加一段 JS:
这样用户从邮件点击按钮跳转到 https://yoursite.com?chat=open 时,Widget 会自动弹开,对话历史也会自动恢复(Chatwoot Widget 基于浏览器 cookie 识别用户)。
第三章:用户回复邮件去哪儿了?
邮件模板搞定后,我注意到邮件的"回复地址"是 [email protected]。这是 Chatwoot 的 Conversation Continuity 功能——理论上用户直接回复邮件,内容会路由回 Chatwoot 对应的对话。
但我的自部署 chatwoot 只是一个 Nginx 反代的 Web 服务,没有配置邮件接收(没有 MX 记录,没有入站邮件服务器)。所以用户回复的邮件会被退信。
坑 9:入站邮件是个独立的基础设施
要让邮件回复功能工作,你需要:
- 配置 MX 记录指向你的服务器
- 跑一个 Postfix + Action Mailbox Relay
- 或者用 Mailgun/SendGrid 的入站路由服务
这是个不小的工程。但好消息是,它不是必须的。
如果你的主要场景是"通知用户有新消息 → 用户回到网站继续聊",那邮件模板里的"Continue Conversation"按钮已经闭环了。入站邮件只是一个锦上添花的便利功能,不影响核心流程。
我目前就没配,以后有需要再说。
第四章:推送通知——为什么手机收不到?
Chatwoot 有官方 iOS App,装上登录后,期望是:用户发消息 → 手机推送通知 → 我去处理。
但我装好 App 后,一条推送都没收到过。
坑 10:自托管必须显式启用推送中继
Chatwoot 官方 App 的推送通知走的是 Chatwoot 的中继服务器(push relay server)。自托管实例默认不启用这个中继。
在 .env 中添加:
就这一行。但官方文档里这个配置项藏得很深,安装指南里根本没提。
注意:如果你自己构建了 Chatwoot 移动 App(配置了 Firebase),就不要开这个选项,两者互斥。用官方 App 就开 relay,用自定义 App 就配 Firebase,不能混用。
坑 11:浏览器推送需要 VAPID 密钥
除了手机推送,Chatwoot 还支持浏览器 Web 推送(即使 Chatwoot 页面没打开也能收到桌面通知)。但这个也需要额外配置。
在容器里生成 VAPID 密钥:
然后写入 .env:
配好后重建容器,然后每个坐席还需要手动操作:
- 登录 Chatwoot 网页版
- 进入 Profile Settings → Notifications
- 开启 Push Notifications,浏览器弹出授权提示时点"允许"
- 开启 Email Notifications(可选,双保险)
- 音频通知建议勾选"My assigned" + "Unassigned"
额外说明:联系人的 "Not Verified" 标识
你可能会注意到每个通过 Widget 来的联系人都有一个叹号,悬浮显示"Not Verified"。
这是 Chatwoot 的身份验证功能(HMAC Identity Verification)。简单说,通过 Widget 匿名发消息的用户,Chatwoot 无法确认他们自称的身份是真的。
对大多数场景来说,这个标识可以忽略。它不影响任何功能,对话正常进行。
如果你想消除它,需要在网站后端用 Inbox 的 HMAC Token 对用户标识符签名,然后通过 Widget SDK 的 setUser() 传入。但这要求用户已登录你的网站,对匿名反馈场景意义不大。
总结:一张清单
如果你也准备自托管 Chatwoot,以下是"部署完成"之后还需要做的事:
必做项
- 配置 SMTP(注意发件人名称不要有逗号)
- 配置
MAILER_INBOUND_EMAIL_DOMAIN(WebWidget inbox 必须) - 配置
ENABLE_PUSH_RELAY_SERVER=true(用官方移动 App 时) - 生成并配置 VAPID 密钥(浏览器推送)
- 坐席在个人设置中开启推送和邮件通知
- 关闭公开注册
ENABLE_ACCOUNT_SIGNUP=false - 修改
.env后始终用docker compose up -d --force-recreate
推荐项
- 自定义邮件模板,添加"Continue Conversation"按钮
- 网站 Widget 处添加
?chat=open参数自动打开的 JS - 配置自动分配规则,确保新对话即时通知到坐席
- 设置数据库自动备份
可选项
- 配置入站邮件(Postfix + Action Mailbox / Mailgun)
- 配置 HMAC 身份验证
- 自定义其他邮件模板
最后
AI 帮你十分钟部署好 Chatwoot 不难,难的是之后这些"怎么邮件没发出去"、"怎么手机没通知"、"怎么链接打不开"的琐碎问题。这些东西官方文档写得零散,StackOverflow 上讨论不多,GitHub Issues 里倒是有人问过,但答案往往埋在几十条回复里。
希望这篇文章能帮你少走几天弯路。
有问题欢迎交流。



