先说结论
很多人第一次看到 Odoo 的 mail.group,会把它理解成一个“能群发邮件的邮箱列表”。
这不算错,但太浅。
从 addons/mail_group/models/mail_group.py 和 mail_group_message.py 看,官方其实实现的是一整套邮件协同模型,里面同时包含:
- 邮件别名入口
- 成员订阅与退订
- 访问模式控制
- 审核队列
- 发件人白名单 / 黑名单
- 待审核提醒给 moderator
- 一键退订链接和 token
一句话说:
mail.group不是“群发器”,而是“以邮件为载体的半开放协同空间”。
为什么 mail.group 不是简单继承 mail.thread
源码注释里写得很清楚:
- 它没有直接继承
mail.thread - 但又接入了 mail gateway 流程
原因很实际。
如果直接把它做成普通 chatter 记录,很多“邮件列表”语义不好表达,比如:
- reply-to 维护
- 列表页脚
- List-Unsubscribe 头
- 审核前挂起
- 同发件人的批量 allow / ban
所以 Odoo 走的是另一条路:
- 用
mail.message承载具体邮件消息 - 再用
mail.group.message包装出“邮件组上下文”
这说明官方认为邮件组不是普通对象消息流,而是更像一个带规则的讨论列表。
为什么访问模式有 public、members、groups
mail.group 的 access_mode 很有代表性:
publicmembersgroups
这三种模式说明官方不是只考虑“内部团队自由发”,而是在设计不同开放度的邮件协作空间:
public
更像面向更宽范围的开放讨论入口。
members
强调只有已订阅成员才能发。
groups
强调只有指定用户组里的成员才能发。
这意味着 mail.group 从一开始就把“谁能发进来”视为核心规则,而不是事后补丁。
为什么邮件先变成 mail.group.message
在 message_post() 里,Odoo 的流程是:
- 先创建
mail.message - 再创建对应的
mail.group.message - 再根据审核规则决定后续动作
这个设计很妙。
因为它把两层语义拆开了:
mail.message
负责“这是一封消息”
mail.group.message
负责“这封消息在邮件组里处于什么审核状态、属于哪条讨论链”
这样一来,审核、父子消息关系、发件人状态这些邮件组特有语义,就不会硬塞进通用消息模型里。
审核为什么不是一个简单开关
很多人会以为审核就是:
- 开启后所有邮件先审批
- 审批通过再发
但源码明显更细:
moderation决定组是否启用审核mail.group.moderation维护某个邮箱在某个组里的 allow / ban 规则mail.group.message维护消息当前是pending_moderation、accepted还是rejected
这三层加在一起,才构成完整审核体系。
也就是说,Odoo 的审核不是“总开关”,而是:
- 组级规则
- 发件人级规则
- 消息级状态
三层叠加。
为什么 allow / ban 会影响同一作者的其他待审邮件
mail_group_message.py 里,action_moderate_allow() 和 action_moderate_ban() 都会调用 _get_pending_same_author_same_group()。
意思是:
- 当 moderator 对一个作者做 allow / ban
- 系统会把同组、同作者、还在 pending 的消息一起处理
这很符合现实邮件列表管理:
- 你不是只审批这一封
- 你是在判断“这个发件人以后值不值得信任”
所以 Odoo 的审核思路不是逐封手工劳作,而是逐步沉淀发件人信誉规则。
为什么待审核消息还要主动通知 moderator
_notify_moderators() 非常关键。
很多系统只把待审消息堆在后台列表里,等管理员自己去看。
但 Odoo 会:
- 定时扫描 pending moderation
- 对 moderator 发 Inbox / Email 通知
这说明在官方视角里,审核并不是“可有可无的后台管理功能”,而是邮件协同链里必须被及时消费的一环。
否则消息就会卡在中间,协作链断掉。
为什么退订要做 one-click token
源码里有几层退订相关方法:
_generate_action_token()_generate_email_access_token()_get_email_unsubscribe_url()
同时发信时还会注入:
List-UnsubscribeList-Unsubscribe-Post: List-Unsubscribe=One-Click
这说明 Odoo 对邮件组的理解是成熟的邮件列表,而不是随便发封通知邮件。
也就是说:
一旦你把讨论做成邮件列表,就必须同时把“优雅退出”做完整。
否则这个协作空间很快就会变成打扰源。
为什么群发时还要加列表头、页脚和回帖语义
_notify_members() 里会处理:
List-ArchiveList-SubscribeList-UnsubscribeList-IdList-PostPrecedence: list- 每个成员专属的 footer
这说明 Odoo 不是把邮件当“通知渠道”而已,而是真的在模拟一套规范邮件列表行为。
这对协同质量非常重要,因为邮件客户端、自动回复规则、退订入口,很多都依赖这些头信息。
这套设计对办公协同有什么启发
1. 邮件讨论空间一定要同时设计入口和退出
订阅容易,退订也必须清晰。
2. 审核要区分“组规则 / 发件人规则 / 单条消息状态”
否则很快会失控。
3. moderator 不是被动管理员,而是消息链中的关键节点
如果不主动提醒,审核就会积压。
4. 邮件列表不是发件功能,而是长期协作空间
所以必须考虑成员、信誉、回帖链和规范头信息。
做相关定制时最容易踩的坑
1. 只做群发,不做订阅与退订机制
结果用户体验会很差。
2. 审核只做布尔字段
后面根本支撑不了不同作者的信誉积累。
3. 不处理 reply-to / List-* headers
邮件线程和客户端行为会乱。
4. 不通知 moderator
待审消息容易积压成死队列。
一句话记忆法
Odoo 的
mail.group不是简单群发邮箱,而是一套带成员规则、审核队列、信誉沉淀和退订协议的邮件协同空间。
理解这一句,就能看懂为什么官方在这个模块里做了这么多“看似麻烦”的设计。
DISCUSSION
评论区