第23讲、Odoo18 邮件系统整体架构
目录
- Odoo 邮件系统整体架构
- 邮件发送方式
- 邮件模板配置
- SMTP 邮件服务器配置
- 邮件发送过程
- 开发中常见邮件发送需求
- 常见问题排查
- 提示与最佳实践
- 完整示例:审批通过自动发邮件
- 门户表单自动邮件通知案例
- 邮件队列与异步发送
- 邮件添加附件
- 邮件日志与调试
- 多语言邮件模板
- 邮件安全与反垃圾建议
- 常见报错案例
- 参考链接
Odoo 18 的邮件系统基于强大的邮件引擎,广泛应用于用户通知、自动化邮件、营销推广、模板化邮件及附件发送等多种场景。本文将从系统架构、发送方式、配置方法、开发实践等多个维度,系统梳理 Odoo 邮件功能的原理与实战技巧,助你高效掌握企业级邮件自动化。
🧱 一、Odoo 邮件系统整体架构
Odoo 的邮件系统围绕以下核心模型构建:
模型 | 说明 |
---|---|
mail.mail | 单条待发送邮件记录(每封邮件生成一条) |
mail.message | 邮件消息记录(通知、聊天、讨论、评论等) |
mail.template | 邮件模板,支持变量替换 |
ir.mail_server | SMTP 邮件服务器配置 |
res.partner | 接收者(通常为联系人) |
mail.thread | 支持消息、跟踪和通知的模型继承类 |
🛠️ 二、邮件发送方式
Odoo 支持多种邮件发送方式,满足不同业务需求:
1. 使用 mail.template
模板发送(推荐,适合业务通知)
适用于大多数业务场景,支持变量替换和批量发送。
# 通过模板发送邮件,force_send=True 表示立即发送,否则进入队列
template = self.env.ref('your_module.email_template_project_approval')
template.send_mail(record_id, force_send=True)
2. 使用 mail.mail
模型手动构建邮件
适合自定义内容、附件等特殊场景。
mail = self.env['mail.mail'].create({'subject': '通知标题','body_html': '<p>这是一封自动邮件</p>','email_to': 'abc@example.com','email_from': 'no-reply@yourcompany.com',
})
mail.send()
3. 使用 record.message_post()
发送内部通知
适合在模型记录上快速发送系统消息或评论。
record.message_post(body="您的申请已审批通过。",subject="审批通过通知",message_type='notification',subtype_xmlid='mail.mt_comment',partner_ids=[user.partner_id.id],
)
⚙️ 三、邮件模板配置(mail.template)
邮件模板支持灵活的变量替换和 HTML 格式,便于统一管理邮件内容。
字段 | 示例 | 说明 |
---|---|---|
Subject | 项目 ${object.name} 已通过 | 支持 Jinja 表达式 |
Body HTML | <p>尊敬的 ${object.partner_id.name}...</p> | 支持 HTML |
Model | project.project | 指定模型 |
Email To | ${object.user_id.email} | 收件人 |
XML 示例:
<record id="email_template_project_approval" model="mail.template"><field name="name">Project Approved</field><field name="model_id" ref="project.model_project_project"/><field name="subject">项目 ${object.name} 审批通过</field><field name="email_to">${object.user_id.email}</field><field name="body_html"><![CDATA[<p>您好,${object.user_id.name}:</p><p>您的项目申请 <strong>${object.name}</strong> 已通过审批。</p>]]></field>
</record>
📡 四、SMTP 邮件服务器配置
路径:设置 > 技术 > 邮件 > 外发邮件服务器
字段 | 示例 |
---|---|
SMTP 服务器 | smtp.gmail.com |
端口 | 587 |
使用 TLS | 是 |
登录名 | you@example.com |
密码 | yourpassword |
配置完成后可测试邮件发送是否正常。
🧪 五、邮件发送过程(背后流程)
mail.template.send_mail()
创建mail.mail
记录mail.mail.send()
通过 SMTP 发出邮件- 发送成功状态为
sent
,失败为exception
- 可通过计划任务定期处理
mail.mail
队列
📌 六、开发中常见邮件发送需求
场景:审批通过后自动发送通知邮件
def action_approve(self):self.state = 'approved'template = self.env.ref('your_module.email_template_project_approval')template.send_mail(self.id, force_send=True)
🧩 七、常见问题排查
问题 | 解决方案 |
---|---|
邮件发不出 | 检查 SMTP 设置、日志、是否有未发 mail.mail |
收件人收不到 | 检查 email_to 、SMTP 服务器是否被拒发 |
模板变量渲染失败 | 检查模型/字段拼写,建议开发模式调试 |
HTML 内容乱码 | 使用 CDATA 标签包裹 HTML |
📌 八、提示与最佳实践
- 优先使用
mail.template
统一管理邮件格式 - 可用
email_cc
、email_bcc
设置抄送 - 立即发送用
force_send=True
- 大量邮件建议用队列机制异步处理
📄 九、完整示例:审批通过自动发邮件
以下为审批通过后自动发送邮件的完整流程,包括模板、模型方法和控制器:
1. 邮件模板 XML(data/email_template.xml
)
<odoo><data><record id="email_template_project_approved" model="mail.template"><field name="name">Project Approval Notification</field><field name="model_id" ref="your_module.model_project_project"/><field name="subject">[Odoo] Project "${object.name}" Approved</field><field name="email_to">${object.user_id.email or ''}</field><field name="email_from">${(user.email or 'admin@example.com')}</field><field name="body_html" type="html"><![CDATA[<p>Dear ${object.user_id.name},</p><p>Your project <strong>${object.name}</strong> has been <strong>approved</strong>.</p><p>Thank you,<br/>The Management Team</p>]]></field></record></data>
</odoo>
2. 模型方法发送邮件(models/project.py
)
from odoo import models, fields, apiclass ProjectProject(models.Model):_name = 'project.project'_inherit = ['mail.thread'] # 支持消息通知name = fields.Char(string='Project Name')user_id = fields.Many2one('res.users', string='Responsible')state = fields.Selection([('draft', 'Draft'),('approved', 'Approved'),], default='draft')def action_approve(self):for rec in self:rec.state = 'approved'# 发送邮件template = self.env.ref('your_module.email_template_project_approved')if template:template.send_mail(rec.id, force_send=True)# 记录系统消息rec.message_post(body="项目已审批通过,通知已发送。")
3. 控制器触发发送(controllers/main.py
)
from odoo import http
from odoo.http import requestclass ProjectController(http.Controller):@http.route('/project/approve/<int:project_id>', type='http', auth='user', website=True)def approve_project(self, project_id):project = request.env['project.project'].sudo().browse(project_id)if project.exists():project.action_approve()return request.render('your_module.project_approval_success', {'project': project})else:return request.not_found()
你可以在浏览器访问
/project/approve/1
来审批 ID 为 1 的项目,并发送邮件。
🧪 十、测试步骤
- 在自定义模块中添加上述 XML、Python 和控制器代码
- 安装模块并确保 SMTP 设置正确
- 创建项目记录(填写
name
和user_id
) - 浏览器访问
/project/approve/1
- 检查收件人邮箱是否收到邮件
💡 十一、门户表单自动邮件通知案例
适用于 Odoo 14~18,常见于"项目申请"、“需求提交”、"报名"等门户场景。
1. 场景描述
用户(如客户、员工)在门户网站提交"项目申请表单"后:
- 项目记录保存进
project.project
模型 - 自动通知相关负责人(如管理员)
- 返回提交成功页面
2. 模型定义(models/project.py
)
from odoo import models, fieldsclass ProjectProject(models.Model):_name = 'project.project'_description = 'Project Application'_inherit = ['mail.thread']name = fields.Char("Project Name", required=True)description = fields.Text("Description")applicant_email = fields.Char("Applicant Email")state = fields.Selection([('draft', 'Draft'),('submitted', 'Submitted'),], default='draft')
3. 邮件模板(data/email_template.xml
)
<odoo><data><record id="email_template_project_submit_notice" model="mail.template"><field name="name">New Project Application</field><field name="model_id" ref="your_module.model_project_project"/><field name="subject">[Odoo] New Project Application: ${object.name}</field><field name="email_to">admin@example.com</field> <!-- 可用对象字段替换 --><field name="body_html" type="html"><![CDATA[<p>Dear Admin,</p><p>A new project has been submitted:</p><ul><li><strong>Name:</strong> ${object.name}</li><li><strong>Email:</strong> ${object.applicant_email}</li><li><strong>Description:</strong> ${object.description}</li></ul>]]></field></record></data>
</odoo>
4. 控制器 + 表单页面(controllers/main.py
)
from odoo import http
from odoo.http import requestclass ProjectFormController(http.Controller):@http.route('/project/apply', type='http', auth='public', website=True)def show_form(self, **kwargs):return request.render('your_module.project_form_template')@http.route('/project/apply/submit', type='http', auth='public', website=True, csrf=False)def submit_form(self, **post):project = request.env['project.project'].sudo().create({'name': post.get('name'),'description': post.get('description'),'applicant_email': post.get('email'),'state': 'submitted',})# 发送邮件通知template = request.env.ref('your_module.email_template_project_submit_notice')if template:template.sudo().send_mail(project.id, force_send=True)return request.render('your_module.project_form_thankyou', {'project': project})
5. 前端模板 QWeb(views/portal_form_templates.xml
)
<template id="project_form_template" name="Project Apply Form"><t t-call="website.layout"><div class="container mt-5"><h2>Project Application</h2><form action="/project/apply/submit" method="post"><div class="form-group"><label>Project Name</label><input type="text" name="name" class="form-control" required="required"/></div><div class="form-group"><label>Email</label><input type="email" name="email" class="form-control" required="required"/></div><div class="form-group"><label>Description</label><textarea name="description" class="form-control" rows="4"></textarea></div><button type="submit" class="btn btn-primary mt-3">Submit</button></form></div></t>
</template><template id="project_form_thankyou" name="Thank You Page"><t t-call="website.layout"><div class="container mt-5"><h2>Thank You!</h2><p>Your project "<t t-esc="project.name"/>" has been submitted successfully.</p></div></t>
</template>
6. 模块加载配置(__manifest__.py
)
确保加载视图、模板、邮件模板:
'data': ['data/email_template.xml','views/portal_form_templates.xml',
],
7. 测试流程
- 浏览器访问:http://yourdomain.com/project/apply
- 填写并提交表单
- 邮件自动发送到设置的收件人邮箱(如
admin@example.com
) - 页面跳转到感谢页
8. 可选扩展
- 使用
res.users
查找管理员动态收件人 - 设置
email_from
为申请人邮箱(如${object.applicant_email}
) - 后端审批后自动通知申请人
🕒 十二、邮件队列与异步发送
Odoo 默认通过队列机制异步发送邮件,适合大批量场景。可在"设置 > 技术 > 自动化 > 计划任务"找到 Email Queue Manager
,定时处理 mail.mail
队列。
如需手动触发队列发送,可在 Odoo shell 执行:
self.env['mail.mail'].process_email_queue()
大批量发送建议不要用
force_send=True
,避免阻塞事务。
📎 十三、邮件添加附件
发送邮件时可通过 attachment_ids
字段添加附件:
attachment = self.env['ir.attachment'].create({'name': 'example.pdf','datas': base64.b64encode(b'file content'),'type': 'binary','mimetype': 'application/pdf',
})
mail = self.env['mail.mail'].create({'subject': '带附件的邮件','body_html': '<p>请查收附件</p>','email_to': 'abc@example.com','attachment_ids': [(4, attachment.id)],
})
mail.send()
📝 十四、邮件日志与调试
- 所有邮件发送记录可在"设置 > 技术 > 邮件 > 外发邮件"查看
- 邮件失败时可在"外发邮件"列表中查看异常原因
- 开发调试时可在 Odoo 日志中搜索
mail.mail
相关日志,定位问题
🌏 十五、多语言邮件模板
Odoo 支持多语言邮件模板。可在模板表单页切换语言,分别填写不同语言内容。发送时会自动根据收件人语言选择模板内容。
🔒 十六、邮件安全与反垃圾建议
- 建议配置 SPF、DKIM、DMARC 提升送达率,防止被判为垃圾邮件
- 邮件内容避免敏感词或大量外链
email_from
建议用已验证域名邮箱
❓ 十七、常见报错案例
SMTPAuthenticationError
:检查邮箱账号和密码,部分邮箱需开启"应用专用密码"ConnectionRefusedError
:检查 SMTP 服务器地址和端口,服务器是否允许外部连接- 模板变量渲染失败:确认字段拼写、模型是否正确,建议开发者模式调试
🔗 参考链接
- Odoo 官方文档 - 邮件
- Odoo 邮件模板开发指南
结语
Odoo 邮件系统功能强大且灵活,既能满足日常通知,也能支撑复杂的业务自动化。建议开发者优先使用模板统一管理邮件格式,合理利用队列机制提升性能,并关注邮件日志与异常处理,保障邮件送达率。欢迎在评论区留言交流更多实战经验!
相关文章:

第23讲、Odoo18 邮件系统整体架构
目录 Odoo 邮件系统整体架构邮件发送方式邮件模板配置SMTP 邮件服务器配置邮件发送过程开发中常见邮件发送需求常见问题排查提示与最佳实践完整示例:审批通过自动发邮件门户表单自动邮件通知案例邮件队列与异步发送邮件添加附件邮件日志与调试多语言邮件模板邮件安…...
【QT面试题】(三)
文章目录 Qt信号槽的优点及缺点Qt中的文件流和数据流区别?Qt中show和exec区别QT多线程使用的方法 (4种)QString与基本数据类型如何转换?QT保证多线程安全事件与信号的区别connect函数的连接方式?信号与槽的多种用法Qt的事件过滤器有哪些同步和…...
DeepSeek09-open-webui使用
Open WebUI 完全指南:从安装到知识库搭建与异常处理 最后更新:2025年6月7日 | 适用版本:Open WebUI v0.6.x 一、安装部署 1.1 系统要求 **Python 3.12 **(严格版本要求,更高版本3.13不兼容)Node.js 20.x内…...

HarmonyOS:Counter计数器组件
一、概述 计数器组件,提供相应的增加或者减少的计数操作。 说明 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 二、属性 除支持通用属性外,还支持以下属性。 enableInc enableInc(value: b…...
数据类型 -- 字符
在C中,字符型(char)用于存储单个字符,如字母、数字、符号等。字符型是最基本的数据类型之一,常用于处理文本、字符数组(字符串)等场景。 1. 基本类型 • char:标准字符类型&#x…...
WordZero:让Markdown与Word文档自由转换的Golang利器
在日常工作中,我们经常需要在Markdown和Word文档之间进行转换。Markdown方便编写和版本控制,而Word文档更适合正式的商务环境。作为一名Golang开发者,我开发了WordZero这个库,专门解决这个痛点。 项目背景 GitHub仓库࿱…...

sqlsugar WhereIF条件的大于等于和等于查出来的坑
一、如下图所示,当我用 .WhereIF(input.Plancontroltype > 0, u > u.Plancontroltype (DnjqPlancontroltype)input.Plancontroltype) 这里面用等于的时候,返回结果一条数据都没有。 上图中生成的SQL如下: SELECT id AS Id ,code AS …...

Pandas 技术解析:从数据结构到应用场景的深度探索
序 我最早用Python做大数据项目时,接触最早的就是Pandas了。觉得对于IT技术人员而言,它是可以属于多场景的存在,因为它的本身就是数据驱动的技术生态中,对于软件工程师而言,它是快速构建数据处理管道的基石࿱…...

数据库系统概论(十七)超详细讲解数据库规范化与五大范式(从函数依赖到多值依赖,再到五大范式,附带例题,表格,知识图谱对比带你一步步掌握)
数据库系统概论(十七)超详细讲解数据库规范化与五大范式(从函数依赖到多值依赖,再到五大范式,附带例题,表格,知识图谱对比带你一步步掌握) 前言一、为什么需要规范化1. 我们先想一个…...
[c#]判定当前软件是否用管理员权限打开
有时一些软件的逻辑中需要使用管理员权限对某些文件进行修改时,那么该软件在执行或者打开的场合,就需要用使用管理员身份运行才能达到效果。那么在c#里,如何判定该软件是否是对管理员身份运的呢? 1.取得当前的windows用户。 2.取得…...

并发编程实战(生产者消费者模型)
在并发编程中使用生产者和消费者模式能够解决绝大多数的并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序整体处理数据的速度。 生产者和消费者模式: 在线程的世界中生产者就是产生数据的线程,而消费者则是消费数据的线程。在多线程开…...
分布式微服务系统架构第144集:FastAPI全栈开发教育系统
加群联系作者vx:xiaoda0423 仓库地址:https://webvueblog.github.io/JavaPlusDoc/ https://1024bat.cn/ https://github.com/webVueBlog/fastapi_plus https://webvueblog.github.io/JavaPlusDoc/ 使用docker搭建常用开发环境 docker安装mysql docker ru…...
el-tabs 切换时数据不更新的问题
最近业务需求,需要在页面中使用tabs,使用过程中出现tabs切换,数据不更新的问题,以下是思路和解决办法。 Vue 会追踪你在模板中绑定的数据,并在数据发生变化时重新渲染相应的部分。但在使用 el-tabs 时,有时…...

git小乌龟不显示图标状态解决方案
第一步 在开始菜单的搜索处,输入regedit命令,打开注册表。 第二步 在注册表编辑器中,找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers 这一项。 第三步 让Tortoise相关的项目排在前…...

获取 OpenAI API Key
你可以按照以下步骤来获取 openai.api_key,用于调用 OpenAI 的 GPT-4、DALLE、Whisper 等 API 服务: 🧭 获取 OpenAI API Key 的步骤: ✅ 1. 注册或登录 OpenAI 账号 打开 https://platform.openai.com/ 使用你的邮箱或 Google/…...

【Android基础回顾】五:AMS(Activity Manager Service)
Android 的 AMS(Activity Manager Service)是 Android 系统中的核心服务之一,负责管理整个应用生命周期、任务栈、进程和四大组件(Activity、Service、BroadcastReceiver、ContentProvider)的运行。它运行在系统进程 s…...

pycharm中提示C++ compiler not found -- please install a compiler
1.最近用pycharm编译一个开源库,编译的依赖c compiler 2.单单使用pycharm编译,编译器报错C compiler not found – please install a compiler 3.需要在配置环境中引入对应库 4.从新编译后没有提示:C compiler not found – please install a compiler错误。...
类型别名与类型自动推导
类型别名与类型的自动推导 类型别名 为什么要引入类型别名? 为了给类型赋予特殊含义或便于使用 典型用途 (1)增强代码可移植性 例如:size_t (在不同系统中可能是unsigned int 或 unsigned long) 首先是…...

一站式直播工具:助力内容创作者高效开启直播新时代
近年来,随着互联网技术的不断进步和短视频、直播行业的爆发式增长,越来越多的企业和个人投入到直播电商、互动娱乐、在线教育等场景。直播运营过程中,涉及到数据统计、弹幕互动、流程自动化、内容同步等诸多环节。如何提升运营效率、减少人工…...
【学习笔记】Lamba表达式[匿名函数]
【学习笔记】Lamba表达式[匿名函数] Lamba表达式格式函数模板Lamba表达式例子 Lamba表达式格式 格式: [捕获列表](参数列表) -> 返回类型 { 函数体 }1、捕获列表:指定如何访问外部变量(如 [&x] 引用捕获,[x] 值捕获&#…...
学习笔记(26):线性代数-张量的降维求和,简单示例
学习笔记(26):线性代数-张量的降维求和,简单示例 1.先理解 “轴(Axis)” 的含义 张量的 “轴” 可以理解为 维度的方向索引 。对于形状为 (2, 3, 4) 的张量,3 个轴的含义是: 轴 0(axis0&…...

以智能管理为基础,楼宇自控打造建筑碳中和新路径
在全球气候变化的严峻形势下,“碳中和”已成为各国发展的重要战略目标。建筑行业作为能源消耗与碳排放的“大户”,其运行阶段的能耗占全社会总能耗近40%,碳排放占比与之相当,实现建筑碳中和迫在眉睫。传统建筑管理模式下ÿ…...
81 实战一:给root目录扩容
添加一块100G硬盘 vgextend centos /dev/sdb1 /dev/sdc lvextend -L +120G /dev/centos/root xfs_growfs /dev/centos/root df -h 看是否扩容成功 82 实战二:给swap空间扩容 添加一块20G硬盘 fdisk -l 可以看到新添加的硬盘 vgextend centos /dev/sdd …...
1130 - Host ‘xxx.x.xx.xxx‘is not allowed to connect to this MySQL server
以下为本次问题的解决办法: 1、暂停mysql容器: docker stop mysql 2、删除mysql容器:docker rm mysql 3、查看mysql容器是否被删除:docker ps -a #没有mysql容器就是删除成功 4、run mysql容器: docker run -d --…...
HttpURLConnection实现
我有一个接口 http://ip:port/Others/airportnew/,采用post方法调用,采用body方式传值,其body内容为{"data": {"data": {"image": ""}} },现在我需要在java中调用这个接口,帮…...

day029-Shell自动化编程-计算与while循环
文章目录 1. read 交互式初始化变量1.1 案例-安装不同的软件1.2 案例-比较大小 2. 计算2.1 bc2.2 awk2.3 expr2.4 let2.5 案例-计算内存的空闲率2.6 案例-检查域名过期时间和https整数过期时间 3. 循环3.1 循环控制语句3.2 for循环-c语言格式3.3 while循环3.3.1 案例-猜数字3.3…...

Linux命令基础(2)
su和exit命令 可以通过su命令切换到root账户 语法:su [-] 用户名 -符号是可选的,表示是否在切换用户后加载环境变量,建议带上 参数:用户名,表示要切换的用户,用户名可以省略,省略表示切换到ro…...

vue3 + vite实现动态路由,并进行vuex持久化设计
在后台管理系统中,如何根据后端返回的接口,来动态的设计路由呢,今天一片文章带你们解 1、在vuex中设置一个方法 拿到完整的路由数据 const state {routerList: []}; const mutations { dynameicMenu(state, payload) {// 第一步 通过glob…...
ThingsCloud事物云平台搭建-微信小程序
ThingsCloud云平台与微信小程序设计 本文主要是介绍ThingsCloud云平台的搭建及微信小程序与app的使用。 当前文章是作为一个通用案例,介绍如何快速使用 ThingsCloud云平台 以及 利用 ThingsCloud云平台平台的框架快速设计手机APP和微信小程序。 可以快速让硬件接入,实现硬件…...
为什么 uni-app 开发的 App 没有明显出现屏幕适配问题Flutter 开发的 App 出现了屏幕适配问题
🧩 一、为什么 uni-app 开发的 App 没有明显出现屏幕适配问题? ✅ 1. uni-app 是基于 H5 的运行环境(或类 H5) uni-app 默认使用的是 H5 的渲染引擎(如 WebView 或小程序渲染引擎)。在 H5 中,…...