How to Inherit and Update a Mail Template in Odoo 19: Complete Guide
Odoo provides a powerful and flexible email templating system that allows you to automate communication across different modules, but default templates may not always match your branding or business requirements. Whether you need to modify the subject line, enhance the email content, add dynamic fields, or personalize customer communications, understanding how to inherit and update mail templates provides the flexibility needed to create a more effective communication experience. The key to customizing mail templates in Odoo 19 lies in the noupdate flag, a mechanism that controls whether XML-defined records can be overwritten during module upgrades. By temporarily disabling this flag, applying your custom changes, and re-enabling it, developers can safely update standard Odoo email templates without directly altering core files.
What You'll Learn:
- How mail templates are structured in Odoo 19 XML
- How to temporarily disable the noupdate flag to enable template modification
- How to customize the subject line with dynamic fields like invoice date
- How to modify body HTML content with QWeb expressions
- How to restore the noupdate flag to protect customizations from upgrades
Understanding Mail Template Structure in Odoo 19
Each email template in Odoo 19 is defined in XML and includes several key components that control what the email will contain. These components specify details such as the subject line, the message body, who the email should be sent to, and any files that need to be attached. Templates are declared inside a noupdate="1" block, which prevents them from being overwritten during module upgrades so users can freely customize them.
Template Name & Model
Each template has a unique XML ID and name, linked to a specific model through the model_id field using a ref reference to the target model.
Subject & Body HTML
The subject field supports QWeb expressions for dynamic content. The body_html field contains the full email HTML with t-out, t-if, and t-else QWeb directives for conditional rendering.
Recipients & Sender
The email_from field defines the sender using dynamic expressions. The partner_to field specifies recipients, and use_default_to can be enabled to use the partner's default email address.
Attachments & Reports
The report_template_ids field attaches PDF or XLSX reports to outgoing emails. The auto_delete flag controls whether the template is automatically removed after sending.
Step 1: Understanding the Default Invoice Email Template
For this tutorial, we will customize the email template used in the Accounting module when sending invoices. When the user clicks the Send button on an account move record, Odoo opens a wizard that uses the template with the XML ID email_template_edi_invoice. This template is defined in the account module inside a noupdate="1" block.
<?xml version="1.0" ?>
<odoo>
<!-- Mail template declared in a NOUPDATE block -->
<data noupdate="1">
<record id="email_template_edi_invoice" model="mail.template">
<field name="name">Invoice: Sending</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="email_from">{{ (object.invoice_user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
<field name="partner_to" eval="False"/>
<field name="use_default_to" eval="True"/>
<field name="subject">{{ object.company_id.name }} Invoice (Ref {{ object.name or 'n/a' }})</field>
<field name="description">Sent to customers with their invoices in attachment</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear <t t-out="object.partner_id.name or ''">...</t>,
<br/><br/>
Here is your invoice
<span style="font-weight:bold;" t-out="object.name or ''">INV/2021/05/0005</span>
amounting in <span style="font-weight:bold;" t-out="format_amount(object.amount_total, object.currency_id) or ''">$ 143,750.00</span>
from <t t-out="object.company_id.name or ''">YourCompany</t>.
... (full template continues)
</p>
</div>
</field>
<field name="report_template_ids" eval="[]"/>
<field name="auto_delete" eval="True"/>
</record>
</data>
</odoo>
Step 2: Disable the noupdate Flag
Since the invoice email template is defined inside a noupdate="1" block, it cannot be modified directly through XML during module upgrades. To customize it, you must first locate the ir.model.data record that controls this flag using the template name and module name, then set noupdate to False. This is done using a function tag that searches for the matching record and updates its noupdate attribute.
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('name', '=', 'email_template_edi_invoice'), ('module', '=', 'account')]" />
</function>
<value eval="{'noupdate': False}" />
</function>
In this code, the function name="search" locates the ir.model.data record where the name is email_template_edi_invoice and the module is account. The outer function name="write" then updates that record by setting noupdate to False. This allows your subsequent template modification to be applied.
Step 3: Modify the Subject Line
With the noupdate flag disabled, you can now modify the template's fields. In this example, we will update the subject field to include the invoice date using object.invoice_date. This makes it easier for recipients to identify which invoice the email refers to without opening the attachment. The record tag references the template using its full XML ID: account.email_template_edi_invoice.
Reference the Template by Full XML ID
Use the record tag with id="account.email_template_edi_invoice" and model="mail.template" to target the existing invoice email template for modification.
Add the Invoice Date to the Subject
Set the subject field to include {{ object.invoice_date }} alongside the existing content. The complete subject becomes: {{ object.company_id.name }} Invoice (Ref {{ object.name or 'n/a' }}) {{ object.invoice_date }}.
<record id="account.email_template_edi_invoice" model="mail.template">
<field name="subject">{{ object.company_id.name }} Invoice (Ref {{ object.name or 'n/a' }}) {{ object.invoice_date }}</field>
</record>
After applying this change, when a user sends an invoice via the Accounting module, the email subject will include the invoice date at the end, making it easier for both the sender and recipient to track communications by date.
Step 4: Customize the Body HTML Content
The body_html field contains the full HTML structure of the email. Odoo uses QWeb expressions to dynamically render content. The t-out directive outputs field values, t-if and t-else provide conditional rendering, and t-att-href creates dynamic links. You can customize any part of the body, such as the greeting, invoice details, payment instructions, and signature.
<!-- Output a field value -->
<t t-out="object.partner_id.name" />
<!-- Conditional rendering -->
<t t-if="object.payment_state in ('paid', 'in_payment')">
This invoice is already paid.
</t>
<t t-else="">
Please remit payment at your earliest convenience.
</t>
<!-- Dynamic link -->
<a t-att-href="'/my/invoices/' + object.id">View Invoice</a>
<!-- Formatted currency amount -->
<span t-out="format_amount(object.amount_total, object.currency_id)" />
<!-- Check if a field exists on the model -->
<t t-if="hasattr(object, 'timesheet_count') and object.timesheet_count">
Review your timesheets from the portal.
</t>
Step 5: Restore the noupdate Flag
After completing your modifications, you must set the noupdate attribute back to True. This is essential because it prevents your customized template from being overwritten during future module updates or installations. Without this step, the next time the account module is upgraded, the default template would replace your carefully crafted customizations.
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('name', '=', 'email_template_edi_invoice'), ('module', '=', 'account')]" />
</function>
<value eval="{'noupdate': True}" />
</function>
Complete Customization Workflow
The complete workflow for inheriting and updating an existing mail template in Odoo 19 follows three distinct phases. First, you disable the noupdate flag to make the template editable. Second, you apply your customizations to the subject, body HTML, or any other field. Finally, you re-enable the noupdate flag to protect your changes. This approach ensures that customizations remain maintainable and upgrade-friendly without directly altering core Odoo files.
| Phase | Action | Purpose |
|---|---|---|
| 1. Unlock | Set noupdate="0" via function write on ir.model.data | Temporarily removes upgrade protection to allow XML modifications |
| 2. Customize | Modify subject, body_html, email_from, or other fields | Apply branding changes, add dynamic fields, update content |
| 3. Lock | Set noupdate="1" via function write on ir.model.data | Restores upgrade protection to preserve customizations |
Important: Always Restore noupdate After Changes
Forgetting to set noupdate back to True is the most common mistake when customizing mail templates. If left as False, the next module upgrade will overwrite your customizations with the default template. Always restore the flag immediately after applying your modifications to ensure your changes survive future updates.
Frequently Asked Questions
Why do we need to change noupdate='1' to noupdate='0' before modifying a mail template in Odoo 19?
Records defined inside a noupdate='1' block are protected from being updated through module upgrades. To modify an existing mail template using XML, you must temporarily set the noupdate flag to False, apply your custom changes, and then restore it to True to safeguard your customizations from being overwritten by future updates.
What happens if I do not set noupdate back to True after modifying the template?
If noupdate remains set to False, future module updates may overwrite your customized template with the default version. Resetting it to True after modifications ensures your changes are preserved and prevents accidental overwrites during subsequent module installations or upgrades.
Can I add custom model fields to an existing email template in Odoo 19?
Yes, you can display any accessible field from a related model using QWeb expressions such as {{ object.custom_field_name }}. This makes it easy to include custom information like invoice references, delivery dates, customer notes, and internal tracking numbers directly in your email templates.
Is it possible to customize mail templates without using XML in Odoo 19?
Yes, Odoo allows users to edit email templates directly from the user interface through Settings > Technical > Email > Email Templates. However, XML-based customization is recommended for module development because changes remain portable, version-controlled, and easier to maintain across environments.
What are the key components of an Odoo 19 mail template definition?
Each mail template includes the template name, model_id (the model the template applies to), email_from (sender address), partner_to (recipient), subject (subject line with QWeb expressions), body_html (the email body HTML), report_template_ids (attached reports), and auto_delete (whether to auto-delete after sending).
Need Help with Odoo Customization?
Our Odoo development experts can help you customize mail templates, build custom modules, implement email automation workflows, and optimize your Odoo 19 communication systems.
About the author
Founder & Odoo Practice Lead, Braincuber Technologies
Founder of Braincuber. Has scoped and shipped 500+ Odoo implementations for US mid-market and global brands. Takes every founder call personally — no SDR layer between buyers and the people building the system.
