先说结论
很多人以为项目任务的邮件网关逻辑,无非就是:
- 发一封邮件到项目 alias
- Odoo 建一条 task
但 addons/project/models/project_task.py 里的 message_new()、message_update() 和 _message_post_after_hook() 说明,
官方真正想做的不是“把邮件存进去”,而是:
把一封邮件尽量完整地翻译成一个可以继续协作的任务上下文。
这包括:
- 识别收件人里哪些是内部用户,并转成 assignee
- 把非内部收件人保留在
email_cc - 避免把项目 alias 自己错误写进 CC
- 用首封邮件正文初始化任务描述
- 用图片附件补出任务展示图
- 后续回信继续把新的相关人订阅进来
这已经不是简单建档,而是“邮件 → 协作对象”的接力设计。
一、为什么任务邮件入口要先区分“内部协作者”和“外部联系人”
project.task.message_new() 里很有意思的一段,是 _find_internal_users_from_address_mail()。
它会拿邮件的 to 地址做一轮拆分与匹配,然后分成三类:
- 能匹配到内部用户的邮箱
- 能匹配到 partner 但没有内部用户身份的邮箱
- 完全未匹配上的邮箱
然后 Odoo 做了一个非常符合协同现实的决定:
- 内部用户 → 放进
user_ids,也就是任务负责人 / 协作者 - 外部联系人与未匹配地址 → 保留进
email_cc
这说明官方非常清楚一件事:
同样出现在收件人里的人,并不属于同一协作层。
有人是团队内执行者, 有人只是需要被抄送或保留外部联系链路。
如果系统不做这层拆分,任务协作会很快失真。
二、为什么不能把项目 alias 自己也塞进 CC
_find_internal_users_from_address_mail() 还有个很细但非常实战的处理:
- 如果当前有
project_id - 会把项目 alias 地址从 unresolved_emails 里剔掉
这看起来像小事,其实很关键。
因为在真实收件列表里,项目 alias 本来就常常出现在 To 或 Cc 中。
如果系统不把它剔掉,后面就会出现很怪的问题:
- 任务上保存了一串无意义的 alias 地址
- 系统像是把项目邮箱也当成外部联系人留档
- 再往后发信时,地址链路越来越脏
所以这一步实际上是在保护任务的邮件上下文不被技术入口污染。
三、为什么首封邮件正文要落到任务描述,而不只是留在 Chatter
_message_post_after_hook() 里有一段非常有产品感的逻辑。
如果满足这些条件:
- 任务当前还没有
description - 当前消息是 creation subtype
- 任务
partner_id与消息作者一致 - 当前消息类型是 email
- 并且 body 存在
那么 Odoo 会:
- 清理邮箱签名
- 对 HTML 做 sanitize
- 把清洗后的正文写入
description
这个设计特别值得注意。
因为官方并没有满足于“邮件已经在 Chatter 里可见”。
它意识到对于任务来说, 首封邮件往往不只是沟通记录,还是任务本体的需求说明。
所以正文落到 description,意味着:
- 任务打开后,核心上下文直接可读
- 不必每次都翻第一条 Chatter 才知道来龙去脉
- 邮件输入更自然地变成任务说明书
这就是“协作上下文同步”,不是单纯消息留痕。
四、为什么要主动去掉签名
同一段逻辑里,Odoo 还会专门处理:
id="Signature"- Gmail signature 标记
- 以及仅有
--的签名分隔片段
这说明官方知道,如果不清理签名, 任务描述会迅速变成一团噪音:
- 手机尾注
- 公司免责声明
- 自动签名横线
- 联系方式重复堆叠
对于邮件线程来说,这些内容偶尔还能接受; 但对于任务 description,这些内容会直接降低可读性。
因此 Odoo 不是简单搬运原文, 而是在做一次“从邮件正文到任务说明”的内容净化。
五、为什么图片附件还能顺手变成任务展示图
_message_post_after_hook() 前半段还做了一件很适合协同场景的事:
- 如果消息带附件
- 且任务还没有
displayed_image_id - 就从图片附件里取第一张作为展示图
这背后的产品意义很强。
很多任务型协作都不是纯文字:
- 设计稿
- 现场照片
- 截图
- 缺陷复现图片
如果第一封邮件附带了关键图片, 把它顺手提炼成任务的可视化封面, 会让协作者更快理解任务内容。
这说明 Odoo 在任务邮件协作上,想保留的不只是“文本对话”,还包括可视化线索。
六、为什么后续回信还要继续 message_subscribe()
不论 message_new() 还是 message_update(),项目任务都会在处理邮件时继续找 To / Cc 里的 partner,
并调用 message_subscribe()。
这意味着 Odoo 的思路不是:
- 任务创建那一刻定下协作者名单
- 后续对话永远不再变化
而是:
- 邮件往来本身也在持续暴露新的参与者
- 这些参与者应该被逐步纳入任务后续沟通圈
这很符合现实。
因为一个任务的参与边界往往会随着讨论逐步展开:
- 最初只有发起人与项目入口
- 后来加上执行人
- 再后来可能加上客户联系人、产品经理、支持同事
Odoo 允许任务协作边界随着邮件链路自然增长,而不是一次性写死。
七、为什么这套设计比“邮件转任务”更高级
很多系统也支持“邮件转工单 / 转任务”, 但常见做法只是:
- 建一条记录
- 把原邮件存档
Odoo 在项目任务这里明显走得更远:
- 收件人语义拆分:内部用户 vs 外部联系人
- 正文语义升级:邮件正文不只是归档,还会成为任务描述
- 附件语义升级:图片不只是附件,还可能成为展示图
- 协作者边界扩展:后续邮件继续拉新参与者入场
这套设计的核心不是“邮件兼容”, 而是“让邮件成为任务协作的自然入口”。
八、这对文档与任务协同有什么启发
用户要求里提到“文档与任务协同”, 而这段源码给出的启发其实很直接:
- 一封邮件正文,本质上就是一份临时需求文档
- 附件里的截图和图片,本质上就是任务的上下文材料
- To / Cc 本质上是在表达协作角色
Odoo 并没有另起一套“文档转任务”复杂流程, 而是让邮件在进入任务的那一刻,就尽量保留这些协作文档属性。
也就是说:
任务不是只接收一个标题,它接收的是一整包带人物、正文、附件和后续往来的上下文。
九、实战里最容易踩的坑
1. 把所有收件人都硬塞成 assignee
会把外部联系人错误内部化。
2. 忽略 project alias 自身地址
最后 email_cc 里会堆出脏地址。
3. 只把邮件留在 Chatter,不同步 description
任务正文可读性会差很多。
4. 不处理签名
描述区域会充满无效噪音。
5. 任务创建后不再根据邮件扩展参与者
后续协作者容易被排除在沟通圈外。
一句话记忆法
Odoo 项目任务的邮件入口不是“收到邮件就建 task”,而是把收件人、正文、附件和后续往来一起翻译成可继续协作的任务上下文。
DISCUSSION
评论区