Skip to main content
iVentureTeam

html_mail

The editor behind Odoo's email bodies. html_mail is the html field retuned for mail clients: styles inlined on save, layouts email clients cannot render stripped out.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 12, 2026Updated August 12, 20266 min read
Technical namehtml_mail
Field typeshtml
Viewsform
Modulemail, installed in practically every Odoo database
Used in core12 occurrences across 9 modules, including mail, hr_recruitment, mail_group, survey, website_slides, product_email_template
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0
No-code setupNo. Studio's Html field widgets do not include the mail variant; it is applied in XML.
Alternativeshtml, text

What the Email HTML field does

Email clients are the most hostile rendering environment in business software: no external stylesheets, wildly inconsistent CSS support, and layouts that fall apart outside a browser. html_mail exists because of that. It subclasses the standard html editor field and changes exactly the things that break email.

Users see the familiar rich text editor. The difference happens when content is read back for saving: the widget walks the edited document, computes the styles the browser actually applied, and writes them into inline style attributes using the conversion helpers that ship next to it. What lands in the database is markup a mail client can render without ever seeing Odoo's stylesheets. That is why an email body authored in Odoo survives Gmail, and why this widget rather than plain html backs mail templates, group messages and recruitment correspondence.

What this means for your team

For a business the widget's constraints are features. Every capability it removes, multi column layouts, embedded interactive blocks, attachment generating image drops, is something that would have looked fine in Odoo and broken in your customer's inbox. Guardrails beat guidelines: authors physically cannot produce the layouts that fail.

The practical implication for teams building templates: author inside Odoo, not by pasting from design tools. Content pasted from a web design export arrives with class based styling that only partially survives the inlining pass, which is the root of most "our newsletter looks different in Outlook" tickets. Keep master content in the template, use the editor's own formatting, and test with a real send to a Gmail and an Outlook account before a campaign, not after.

Supported options in Odoo 19

html_mail declares no options of its own in 19.0. It spreads the base html field descriptor, so the base field's options apply, read through the html field's extractProps, with one exception the mail variant enforces. The table lists the inherited options as read from html_field.js, with the mail specific behavior noted.

OptionTypeWhat it does
heightnumberFixed editor height in pixels, with overflow scrolling enabled. Inherited from the html field.
allowImagebooleanSwitches image insertion in the editor. Inherited; note html_mail additionally never converts dropped images into attachments.(default: true)
allowVideobooleanSwitches video embedding. Inherited from the html field; video support in mail clients is its own adventure.(default: true)
allowMediaDocumentsbooleanSwitches document insertion from the media dialog. Inherited.(default: true)
allowFilebooleanSwitches file insertion. Inherited from the html field.(default: true)
allowChecklistbooleanSwitches checklist blocks in the editor. Inherited.(default: true)
allowAttachmentCreationbooleanConvenience switch that sets both allowImage and allowFile at once, read after them in extractProps so it wins when both are present. Inherited.
dynamic_placeholderbooleanEnables the dynamic placeholder picker for template style fields. Inherited; the Odoo 20 branch adds permission filtering on top.(default: false)
dynamic_placeholder_model_reference_fieldfield nameField on the record whose value defines the model placeholders are picked from, the mail template pattern. Inherited.
embedded_componentsbooleanBase html field switch for interactive embedded blocks. Forced to false by html_mail's extractProps regardless of the view's value.(default: true)
codeviewbooleanAdds the raw code view toggle, honored only when developer mode is active because the extract multiplies it by odoo.debug. Inherited.(default: false)
migrateHTMLbooleanLets the editor run its markup migration pass on stored content when loading; disable to hand the editor stored HTML untouched. Inherited.(default: true)
collaborativebooleanEnables collaborative editing on the field, with collaborative_trigger passed alongside to the collaboration setup. Inherited from the html field.
sandboxedPreviewbooleanRequests the editor's sandboxed preview handling for the content. Inherited; declared in the base extractProps.(default: false)
cssReadonlystringAsset bundle id used when rendering the field read only. Inherited from the html field.

embedded_components is ignored here. The base html field defaults it to true, but html_mail's own extractProps overrides the result to false unconditionally, so interactive embedded blocks never reach an email body regardless of what the view requests. Set expectations accordingly when copying options from an html field recipe.

Working examples

Standard email body field

<field name="body_html" widget="html_mail"/>

Fixed height editing box

<field name="body_html"
       widget="html_mail"
       options="{'height': 400}"/>

Text only email body, media disabled

<field name="reply_body"
       widget="html_mail"
       options="{'allowImage': False, 'allowVideo': False, 'allowFile': False}"/>

Dynamic placeholders on a template model

<field name="body_html"
       widget="html_mail"
       options="{'dynamic_placeholder': True, 'dynamic_placeholder_model_reference_field': 'model_id'}"/>

Four changes that make HTML safe for inboxes

The 19.0 subclass makes exactly four moves on top of the html field, all visible in getConfig and getEditorContent:

CSS inlining on read back. getEditorContent clones the edited document, temporarily mounts the clone so computed styles resolve, and runs toInline with rules gathered by getCSSRules, cached per editor instance in a WeakMap. Class driven appearance becomes inline styles; markup leaves the field email ready.

Column plugin removed. The editor's plugin list is filtered to drop the column layout plugin, because multi column structures are the classic Outlook casualty.

Attachment free image drops. dropImageAsAttachment is off, so dragging an image into the body does not mint an ir.attachment the way it does in the standard html field.

Safe link defaults. Links created in the editor get target="_blank" and rel="noreferrer noopener" automatically.

Everything else, toolbars, powerbox, history, is the shared html editor. That inheritance is also the risk surface: options added to the base field arrive here untested for email fitness, which is why the option table's descriptions note where mail overrides them.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Placeholder permission system, timezone aware datetime placeholders and no_move_node_plugin on the development branch. Details below.
Odoo 19.0VerifiedVerified against the shipped source of the mail wrapper and the html_editor base.
Odoo 18.0VerifiedMail wrapper effectively identical to 19.0; underlying editor differences belong to the html field.

Upgrade note for 18 to 19. The mail wrapper itself is effectively unchanged, the diff is two trivial lines, so template bodies and view options carry over. What actually evolves between versions is the underlying html editor; treat editor behavior differences as html field topics rather than html_mail regressions.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. Everything below is read from the public development branch, which can shift until feature freeze, and this page is re-verified against the shipped build.

Dynamic placeholders gain a permission system. The branch adds a field filter wired to the allowed_qweb_expressions service: users without the mail template editor group can only insert placeholder paths from an approved whitelist, and one2many, many2many, boolean and non searchable fields are excluded outright. Template injection through placeholder pickers gets materially harder.

Datetime placeholders localize themselves. Inserting a datetime field now generates a format_datetime expression carrying the recipient partner's timezone when the model exposes one, with the label as fallback text. Timestamps in emails stop defaulting to server time.

One new option appears: no_move_node_plugin. It disables the editor's block drag handles inside the mail variant.

Teams with heavy template customization should budget a re-test of placeholder driven templates during the upgrade; our Odoo migration service includes exactly that pass.

Common problems and fixes

SymptomCause and fix
Email looks right in Odoo, wrong in Outlook or GmailContent pasted from design tools relies on classes and structures that do not fully survive inlining, or on CSS mail clients refuse. Author in the editor itself and test with a real send; keep layouts single column.
No column or grid layout option in the toolbarThe column plugin is deliberately removed from this widget. Expected. Emails needing columns require a hand built, table based template from a developer.
Embedded blocks from an html field recipe do not appearhtml_mail forces embedded components off regardless of options. Expected. Those blocks are not email safe.
Dropping an image does not create an attachment like elsewheredropImageAsAttachment is disabled in this widget's editor config. Expected. Host campaign images properly and insert by URL for reliable rendering.
codeview option set but no code view appearsThe base field only honors codeview when developer mode is active. Enable developer mode, or accept that end users do not get raw HTML editing.
Placeholder picker missing fields after a future Odoo 20 upgradeThe development branch restricts non template editors to whitelisted expressions and drops o2m, m2m, boolean and non searchable fields. Grant the mail template editor group to power users, and re-test templates during the upgrade.

Email HTML field vs the alternatives

WidgetBest forKey difference
html_mailEmail bodies and mail templatesInlines CSS on save and strips email hostile features
htmlRich content that lives inside OdooFull editor with embedded components, no inlining pass
textPlain text that must stay plainNo formatting, no editor, nothing to break in a mail client

The decision is destination driven: content leaving Odoo by email wants html_mail, content living inside Odoo wants html, and content that must stay plain wants text. Mixing those up is how emails break or how internal notes end up with inlined style noise.

Frequently asked questions

What is the html_mail widget for?+
It is the html editor variant the mail module registers for email bodies: mail templates, group messages, recruitment emails. Same editing experience as the html field, plus an inlining pass and email safety restrictions applied automatically.
Why does Odoo inline CSS into email HTML?+
Most mail clients ignore external and embedded stylesheets, so class based styling dies in transit. On save the widget computes the styles the browser applied and writes them into style attributes, producing markup Gmail and Outlook can render standalone.
Why can I not add columns to my email body?+
The widget removes the editor's column plugin on purpose, because multi column flex and grid layouts break in major mail clients. Column style emails need a table based template built by a developer, which is its own discipline.
Which options does html_mail accept?+
The base html field's options, since the descriptor is inherited: height, the media allow switches, dynamic placeholder settings, codeview and friends. The one exception is embedded_components, which html_mail overrides to false no matter what the view says.
How do dynamic placeholders work with this widget?+
Enable dynamic_placeholder and point dynamic_placeholder_model_reference_field at the field holding the target model, as mail templates do. Typing the trigger opens the field picker and inserts an expression resolved at send time. In the Odoo 20 branch this gains permission filtering and timezone aware datetime handling.
Why did my dropped image not become an attachment?+
The mail variant disables drop to attachment conversion, unlike the standard html field. For campaign emails, host images at a stable URL and insert them by address so they render for every recipient.
Is html_mail available through Odoo Studio?+
No. Studio's widget list for Html fields offers the plain editor and a raw text mode, so applying html_mail is an XML view change. Anywhere core already sends email from a body field, the widget is already in place.
What changes for html_mail in Odoo 20?+
The development branch shows placeholder whitelisting for non template editors via the allowed expressions service, automatic format_datetime with recipient timezone for datetime placeholders, and a new no_move_node_plugin option. All subject to change until the September 2026 release, and re-verified here after it ships.

Emails that sell in Odoo and die in Outlook?

Template HTML, placeholder logic and deliverability live at the intersection of Odoo and thirty mail clients. We build and rescue Odoo email templates, quotation mails, portal notifications and campaign flows, tested against the inboxes your customers actually read.

Book a free consultation

How this page was produced

Verified by reading html_mail_field.js in the Odoo 19.0 mail module together with the base html_field.js in html_editor, whose extractProps defines every inherited option listed above, then diffing the mail file against 18.0, effectively unchanged, and against the public development branch for the Odoo 20 findings, including the placeholder whitelist machinery. Behavior claims about inlining come from getEditorContent and the convert_inline helpers, not from testing every mail client. Corrections are welcome via our contact page.