先说结论
Odoo 的邮件模板不是“写完 HTML 就能发”。
从 mail.template 模型可以看到,系统在保存和发送前最在意三件事:
- 模板字段能不能在目标模型上成功渲染
- 收件人到底来自默认对象,还是来自模板里显式写的表达式
- 模板挂的附件是否正确转移到消息上下文里
所以模板问题大多数不是“编辑器抽风”,而是:
渲染上下文、收件人来源和附件归属三者没有对齐。
为什么模板保存时就会做渲染校验
mail.template.create() 和 write() 之后,都会走 _check_can_be_rendered()。
这意味着 Odoo 的思路很明确:
- 与其让错误拖到真正发信时爆炸
- 不如在保存模板时,就先拿一个实际记录试渲染
因此常见报错并不是“系统挑剔”,而是模板里某个动态表达式在当前模型上根本跑不通。
比如:
- 目标模型没有这个字段
- 语法能过,但值链路会遇到空对象
- 某段表达式在 sample record 上直接抛异常
这也是为什么很多人明明看着模板写得很像对,保存却失败。
use_default_to 为什么经常被误解
use_default_to 的帮助文案说得很直接:默认收件人来自记录本身。
换句话说,模板默认优先尊重业务对象已有的“应该通知谁”。
只有当你显式填写:
email_topartner_toemail_cc
时,系统才会引入额外的模板级收件人逻辑。
这对协同邮件特别重要。
因为很多业务对象本来就已经有一套默认联系人关系,比如:
- 客户
- 跟进人
- 参与人
- attendee / partner 关联
如果你不理解 use_default_to,最常见的后果就是:
- 以为模板没收件人,其实对象早就有默认收件人
- 或者反过来,手写了
email_to,却把对象原本的通知关系绕开了
为什么 reply_to 不是随便填个邮箱
源码里对 reply_to 的描述很克制:它主要用于 mass 模式下,且回复不会回到原 discussion thread 时。
这句话背后的意思是:
- 正常线程化协作,最好让回复回到原线程
reply_to主要是在“无法自然续帖”的群发场景下做引流
很多团队一上来就把 reply_to 当万能回邮口,其实容易制造新的混乱:
- 本该回原业务对象的邮件,被改投到另一个地址
- Chatter 连续性被打断
- 邮件看起来发出去了,但回复再也回不到正确上下文
所以 reply_to 更像边界修正工具,不是默认必填项。
附件为什么要做 ownership 修复
_fix_attachment_ownership() 会把模板附件写回到模板自身的 res_model / res_id。
这一步很像后台 housekeeping,但其实非常关键。
原因是模板附件不是普通文件列表,它还涉及:
- 访问权限
- 后续消息引用
- 发送时是否能被正确复制到 mail compose / mail message
如果附件归属乱了,常见表现是:
- 内部用户看得到,外部流程拿不到
- 发信时附件偶尔丢失
- 附件看似在模板上,实际上关联到了错误对象
这类 bug 很隐蔽,而 Odoo 选择在模板写入后主动修正归属,就是为了减小这种隐患。
为什么模板分类会影响协作治理
template_category 会把模板区分成:
- base template
- hidden template
- custom template
这不是 UI 装饰,而是在告诉你:
- 哪些是系统级基准模板
- 哪些是 XML 自带但已隐藏或不建议直接用
- 哪些是业务自己新增的模板
对协同办公团队来说,这个分类非常有用。
因为真正麻烦的不是“模板太少”,而是:
- 同一业务流程被人复制出很多近似版本
- 没人知道哪一份才是主模板
- 某个自定义模板长久没有校验,却一直挂在自动动作里
最容易踩的误区
误区一:模板能预览,就一定能保存
不一定。预览往往只是局部成功,保存时的 _check_can_be_rendered() 更严格。
误区二:把收件人全部硬编码进模板
这样会绕过很多模型自己的默认收件逻辑,长期维护很痛苦。
误区三:把模板附件当普通素材库
模板附件是“可发送对象”的组成部分,不只是素材托管。
模板报错时的排查顺序
建议按下面顺序查:
- 目标
model_id是否选对 - 有问题的字段是不是动态字段:subject、body_html、email_to、reply_to 等
- 拿一个真实记录,验证表达式是否能渲染到具体值
- 检查
use_default_to与显式email_to是否互相打架 - 附件是否是模板自己的附件,而不是历史消息残留附件
一句话记忆
Odoo 邮件模板的稳定性,不取决于编辑器写得多漂亮,而取决于渲染上下文、默认收件人和附件归属是否一致。
DISCUSSION
评论区