diff --git a/addons/mail/views/mail_mail_views.xml b/addons/mail/views/mail_mail_views.xml
index 795589938eff734ae2b58e8d02f6d9c8ebe1eb26..b58cc0367828fb710b0089c8cb56c6733a09d90f 100644
--- a/addons/mail/views/mail_mail_views.xml
+++ b/addons/mail/views/mail_mail_views.xml
@@ -39,7 +39,9 @@
                         </group>
                         <notebook>
                             <page string="Body" name="body">
-                                <field name="body_content" options="{'style-inline': true}"/>
+                            <field name="body_html" widget="html"
+                                   options="{'sandboxedPreview': true}"
+                                   attrs="{'readonly': [('state', 'not in', ['outgoing', 'exception'])]}"/>
                             </page>
                             <page string="Advanced" name="advanced" groups="base.group_no_one">
                                 <group>
diff --git a/addons/web_editor/static/src/js/backend/html_field.js b/addons/web_editor/static/src/js/backend/html_field.js
index 9deebe4414b44b63c579e664a6c76bb448a46af8..26867911413afab43b3eef65191ce5d0b666399c 100644
--- a/addons/web_editor/static/src/js/backend/html_field.js
+++ b/addons/web_editor/static/src/js/backend/html_field.js
@@ -74,11 +74,8 @@ export class HtmlFieldWysiwygAdapterComponent extends ComponentAdapter {
 
 export class HtmlField extends Component {
     setup() {
-        // readonly if nodes in <head> as the head will be removed on insertion in the DOM
-        // this prevents the field from overwriting the value with invalid HTML when relevant
-        const domParser = new DOMParser();
-        const parsedOriginal = domParser.parseFromString(this.props.value || '', 'text/html');
-        this.containsComplexHTML = !!parsedOriginal.head.innerHTML.trim();
+        this.containsComplexHTML = this.computeContainsComplexHTML();
+        this.sandboxedPreview = this.props.sandboxedPreview || this.containsComplexHTML;
 
         this.readonlyElementRef = useRef("readonlyElement");
         this.codeViewRef = useRef("codeView");
@@ -117,7 +114,7 @@ export class HtmlField extends Component {
             res_id: this.props.record.resId,
         };
         onWillUpdateProps((newProps) => {
-            if (!newProps.readonly && this.state.iframeVisible) {
+            if (!newProps.readonly && !this.sandboxedPreview && this.state.iframeVisible) {
                 this.state.iframeVisible = false;
             }
 
@@ -135,7 +132,7 @@ export class HtmlField extends Component {
                 if (this._qwebPlugin) {
                     this._qwebPlugin.destroy();
                 }
-                if (this.props.readonly) {
+                if (this.props.readonly || (!this.state.showCodeView && this.sandboxedPreview)) {
                     if (this.showIframe) {
                         await this._setupReadonlyIframe();
                     } else if (this.readonlyElementRef.el) {
@@ -170,11 +167,24 @@ export class HtmlField extends Component {
         });
     }
 
+    /**
+     * Check whether the current value contains nodes that would break
+     * on insertion inside an existing body.
+     *
+     * @returns {boolean} true if 'this.props.value' contains a node
+     * that can only exist once per document.
+     */
+    computeContainsComplexHTML() {
+        const domParser = new DOMParser();
+        const parsedOriginal = domParser.parseFromString(this.props.value || '', 'text/html');
+        return !!parsedOriginal.head.innerHTML.trim();
+    }
+
     get markupValue () {
         return markup(this.props.value);
     }
     get showIframe () {
-        return this.props.readonly && this.props.cssReadonlyAssetId;
+        return (this.sandboxedPreview && !this.state.showCodeView) || (this.props.readonly && this.props.cssReadonlyAssetId);
     }
     get wysiwygOptions() {
         let dynamicPlaceholderOptions = {};
@@ -393,7 +403,10 @@ export class HtmlField extends Component {
         return this.state.showCodeView && this.codeViewRef.el;
     }
     async _setupReadonlyIframe() {
-        const iframeTarget = this.iframeRef.el.contentDocument.querySelector('#iframe_target');
+        const iframeTarget = this.sandboxedPreview
+            ? this.iframeRef.el.contentDocument.documentElement
+            : this.iframeRef.el.contentDocument.querySelector('#iframe_target');
+
         if (this.iframePromise && iframeTarget) {
             if (iframeTarget.innerHTML !== this.props.value) {
                 iframeTarget.innerHTML = this.props.value;
@@ -422,7 +435,6 @@ export class HtmlField extends Component {
 
             this.iframeRef.el.addEventListener('load', async () => {
                 const _avoidDoubleLoad = ++avoidDoubleLoad;
-                const asset = await ajax.loadAsset(this.props.cssReadonlyAssetId);
 
                 if (_avoidDoubleLoad !== avoidDoubleLoad) {
                     console.warn('Wysiwyg immediate iframe double load detected');
@@ -434,52 +446,66 @@ export class HtmlField extends Component {
                 } catch (_e) {
                     return;
                 }
-                cwindow.document
-                    .open("text/html", "replace")
-                    .write(
-                        '<!DOCTYPE html><html>' +
-                        '<head>' +
-                            '<meta charset="utf-8"/>' +
-                            '<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>\n' +
-                            '<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>\n' +
-                        '</head>\n' +
-                        '<body class="o_in_iframe o_readonly" style="overflow: hidden;">\n' +
-                            '<div id="iframe_target"></div>\n' +
-                        '</body>' +
-                        '</html>');
-
-                for (const cssLib of asset.cssLibs) {
-                    const link = cwindow.document.createElement('link');
-                    link.setAttribute('type', 'text/css');
-                    link.setAttribute('rel', 'stylesheet');
-                    link.setAttribute('href', cssLib);
-                    cwindow.document.head.append(link);
-                }
-                for (const cssContent of asset.cssContents) {
-                    const style = cwindow.document.createElement('style');
-                    style.setAttribute('type', 'text/css');
-                    const textNode = cwindow.document.createTextNode(cssContent);
-                    style.append(textNode);
-                    cwindow.document.head.append(style);
+                if (!this.sandboxedPreview) {
+                    cwindow.document
+                        .open("text/html", "replace")
+                        .write(
+                            '<!DOCTYPE html><html>' +
+                            '<head>' +
+                                '<meta charset="utf-8"/>' +
+                                '<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>\n' +
+                                '<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>\n' +
+                            '</head>\n' +
+                            '<body class="o_in_iframe o_readonly" style="overflow: hidden;">\n' +
+                                '<div id="iframe_target"></div>\n' +
+                            '</body>' +
+                            '</html>');
                 }
 
-                const iframeTarget = cwindow.document.querySelector('#iframe_target');
-                iframeTarget.innerHTML = value;
+                if (this.props.cssReadonlyAssetId) {
+                    const asset = await ajax.loadAsset(this.props.cssReadonlyAssetId);
+                    for (const cssLib of asset.cssLibs) {
+                        const link = cwindow.document.createElement('link');
+                        link.setAttribute('type', 'text/css');
+                        link.setAttribute('rel', 'stylesheet');
+                        link.setAttribute('href', cssLib);
+                        cwindow.document.head.append(link);
+                    }
+                    for (const cssContent of asset.cssContents) {
+                        const style = cwindow.document.createElement('style');
+                        style.setAttribute('type', 'text/css');
+                        const textNode = cwindow.document.createTextNode(cssContent);
+                        style.append(textNode);
+                        cwindow.document.head.append(style);
+                    }
+                }
 
-                const script = cwindow.document.createElement('script');
-                script.setAttribute('type', 'text/javascript');
-                const scriptTextNode = document.createTextNode(
-                    `if (window.top.${this._onUpdateIframeId}) {` +
-                        `window.top.${this._onUpdateIframeId}(${_avoidDoubleLoad})` +
-                    `}`
-                );
-                script.append(scriptTextNode);
-                cwindow.document.body.append(script);
+                if (!this.sandboxedPreview) {
+                    const iframeTarget = cwindow.document.querySelector('#iframe_target');
+                    iframeTarget.innerHTML = value;
+
+                    const script = cwindow.document.createElement('script');
+                    script.setAttribute('type', 'text/javascript');
+                    const scriptTextNode = document.createTextNode(
+                        `if (window.top.${this._onUpdateIframeId}) {` +
+                            `window.top.${this._onUpdateIframeId}(${_avoidDoubleLoad})` +
+                        `}`
+                    );
+                    script.append(scriptTextNode);
+                    cwindow.document.body.append(script);
+                } else {
+                    cwindow.document.documentElement.innerHTML = value;
+                }
 
                 const height = cwindow.document.body.scrollHeight;
                 this.iframeRef.el.style.height = Math.max(30, Math.min(height, 500)) + 'px';
 
                 retargetLinks(cwindow.document.body);
+                if (this.sandboxedPreview) {
+                    this.state.iframeVisible = true;
+                    this.onIframeUpdated();
+                    resolve();
+                }
             });
             // Force the iframe to call the `load` event. Without this line, the
             // event 'load' might never trigger.
@@ -626,6 +652,7 @@ HtmlField.props = {
     cssReadonlyAssetId: { type: String, optional: true },
     cssEditAssetId: { type: String, optional: true },
     isInlineStyle: { type: Boolean, optional: true },
+    sandboxedPreview: {type: Boolean, optional: true},
     wrapper: { type: String, optional: true },
     wysiwygOptions: { type: Object },
 };
@@ -672,6 +699,7 @@ HtmlField.extractProps = ({ attrs, field }) => {
         isTranslatable: field.translate,
         fieldName: field.name,
         codeview: Boolean(odoo.debug && attrs.options.codeview),
+        sandboxedPreview: Boolean(attrs.options.sandboxedPreview),
         placeholder: attrs.placeholder,
 
         isCollaborative: attrs.options.collaborative,
diff --git a/addons/web_editor/static/src/js/backend/html_field.xml b/addons/web_editor/static/src/js/backend/html_field.xml
index 41cbc1ec98ea79c60f76e939f80d5276b5b8dbce..80ca01b02ac13887fb2905061322d825bab80068 100644
--- a/addons/web_editor/static/src/js/backend/html_field.xml
+++ b/addons/web_editor/static/src/js/backend/html_field.xml
@@ -2,9 +2,9 @@
 <templates id="template" xml:space="preserve">
 
     <t t-name="web_editor.HtmlField" owl="1">
-        <t t-if="props.readonly || props.notEditable || (containsComplexHTML and !state.showCodeView)">
+        <t t-if="props.readonly || props.notEditable || (sandboxedPreview and !state.showCodeView)">
             <t t-if="this.showIframe">
-                <iframe t-ref="iframe" t-att-class="{'d-none': !this.state.iframeVisible, 'o_readonly': true}"></iframe>
+                <iframe t-ref="iframe" t-att-class="{'d-none': !this.state.iframeVisible, 'o_readonly': true}" t-att-sandbox="sandboxedPreview ? 'allow-same-origin' : null"></iframe>
             </t>
             <t t-else="">
                 <div  t-ref="readonlyElement" class="o_readonly" t-out="markupValue" />
@@ -29,7 +29,7 @@
                 </span>
             </t>
         </t>
-        <t t-if="state.showCodeView || (containsComplexHTML and !props.readonly and !props.notEditable)">
+        <t t-if="state.showCodeView || (sandboxedPreview and !props.readonly and !props.notEditable)">
             <div t-ref="codeViewButton" id="codeview-btn-group" class="btn-group" t-on-click="() => this.toggleCodeView()">
                 <button class="o_codeview_btn btn btn-primary">
                     <i class="fa fa-code" />
diff --git a/addons/web_editor/static/tests/html_field_tests.js b/addons/web_editor/static/tests/html_field_tests.js
index 3b3caa25a897fb04dc4645ea71b26c2cd575dbe9..659cc7dd44020d9a229a3675166559a2bb1a2aab 100644
--- a/addons/web_editor/static/tests/html_field_tests.js
+++ b/addons/web_editor/static/tests/html_field_tests.js
@@ -1,11 +1,22 @@
 /** @odoo-module **/
 
-import { click, editInput, getFixture, makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils";
+import { click, editInput, getFixture, makeDeferred, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
 import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
 import { registry } from "@web/core/registry";
 import { HtmlField } from "@web_editor/js/backend/html_field";
 import { onRendered } from "@odoo/owl";
 
+async function iframeReady(iframe) {
+    const iframeLoadPromise = makeDeferred();
+    iframe.addEventListener("load", function () {
+        iframeLoadPromise.resolve();
+    });
+    if (!iframe.contentDocument.body) {
+        await iframeLoadPromise;
+    }
+    await nextTick(); // ensure document is loaded
+}
+
 QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
     let serverData;
     let target;
@@ -29,16 +40,91 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
         registry.category("fields").add("html", HtmlField, { force: true });
     });
 
-    /**
-     * Check that documents with data in a <head> node are set to readonly
-     * with a codeview option.
-     */
-    QUnit.test("html fields with complete HTML document", async (assert) => {
-        assert.timeout(2000);
-        assert.expect(12);
+
+    QUnit.module('Sandboxed Preview');
+
+    QUnit.test("complex html is automatically in sandboxed preview mode", async (assert) => {
+        serverData.models.partner.records = [{
+            id: 1,
+            txt: `
+            <!DOCTYPE HTML>
+            <html xml:lang="en" lang="en">
+                <head>
+
+                    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+                    <meta name="format-detection" content="telephone=no"/>
+                    <style type="text/css">
+                        body {
+                            color: blue;
+                        }
+                    </style>
+                </head>
+                <body>
+                    Hello
+                </body>
+            </html>
+            `,
+        }];
+        await makeView({
+            type: "form",
+            resId: 1,
+            resModel: "partner",
+            serverData,
+            arch: `
+                <form>
+                    <field name="txt" widget="html"/>
+                </form>`,
+        });
+
+        assert.containsOnce(target, '.o_field_html[name="txt"] iframe[sandbox="allow-same-origin"]');
+    });
+
+    QUnit.test("readonly sandboxed preview", async (assert) => {
+        serverData.models.partner.records = [{
+            id: 1,
+            txt: `
+            <!DOCTYPE HTML>
+            <html xml:lang="en" lang="en">
+                <head>
+
+                    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+                    <meta name="format-detection" content="telephone=no"/>
+                    <style type="text/css">
+                        body {
+                            color: blue;
+                        }
+                    </style>
+                </head>
+                <body>
+                    Hello
+                </body>
+            </html>`,
+        }];
+        await makeView({
+            type: "form",
+            resId: 1,
+            resModel: "partner",
+            serverData,
+            arch: `
+                <form string="Partner">
+                    <field name="txt" widget="html" readonly="1" options="{'sandboxedPreview': true}"/>
+                </form>`,
+        });
+
+        const readonlyIframe = target.querySelector('.o_field_html[name="txt"] iframe[sandbox="allow-same-origin"]');
+        assert.ok(readonlyIframe);
+        await iframeReady(readonlyIframe);
+        assert.strictEqual(readonlyIframe.contentDocument.body.innerText, 'Hello');
+        assert.strictEqual(readonlyIframe.contentWindow.getComputedStyle(readonlyIframe.contentDocument.body).color, 'rgb(0, 0, 255)');
+
+        assert.containsN(target, '#codeview-btn-group > button', 0, 'Codeview toggle should not be possible in readonly mode.');
+    });
+
+    QUnit.test("sandboxed preview display and editing", async (assert) => {
         let codeViewState = false;
-        let togglePromiseId = 0;
         const togglePromises = [makeDeferred(), makeDeferred()];
+        let togglePromiseId = 0;
+        const writePromise = makeDeferred();
         patchWithCleanup(HtmlField.prototype, {
             setup: function () {
                 this._super(...arguments);
@@ -51,13 +137,9 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
             },
         });
         const htmlDocumentTextTemplate = (text, color) => `
-        <!DOCTYPE HTML>
-        <html xml:lang="en" lang="en">
+        <html>
             <head>
-
-                <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-                <meta name="format-detection" content="telephone=no"/>
-                <style type="text/css">
+                <style>
                     body {
                         color: ${color};
                     }
@@ -72,55 +154,55 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
             id: 1,
             txt: htmlDocumentTextTemplate('Hello', 'red'),
         }];
-        const writePromise = makeDeferred();
         await makeView({
             type: "form",
             resId: 1,
             resModel: "partner",
             serverData,
             arch: `
-                <form string="Partner">
+                <form>
                     <sheet>
                         <notebook>
                                 <page string="Body" name="body">
-                                    <field name="txt" widget="html"/>
+                                    <field name="txt" widget="html" options="{'sandboxedPreview': true}"/>
                                 </page>
                         </notebook>
                     </sheet>
                 </form>`,
             mockRPC(route, args) {
                 if (args.method === "write" && args.model === 'partner') {
-                    assert.equal(args.args[1].txt, htmlDocumentTextTemplate('Hi', 'black'));
+                    assert.equal(args.args[1].txt, htmlDocumentTextTemplate('Hi', 'blue'));
                     writePromise.resolve();
                 }
             }
         });
 
-        const fieldHtml = target.querySelector('.o_field_html');
-        let readonlyNode = fieldHtml.querySelector('.o_readonly');
-        assert.ok(readonlyNode);
-        assert.equal(readonlyNode.innerText, 'Hello');
-        assert.equal(window.getComputedStyle(readonlyNode).color, 'rgb(255, 0, 0)');
-
-        const codeViewButton = fieldHtml.querySelector('.o_codeview_btn');
-        assert.ok(codeViewButton);
-
-        await click(codeViewButton);
+        // check original displayed content
+        let iframe = target.querySelector('.o_field_html[name="txt"] iframe[sandbox="allow-same-origin"]');
+        assert.ok(iframe, 'Should use a sanboxed iframe');
+        await iframeReady(iframe);
+        assert.strictEqual(iframe.contentDocument.body.textContent.trim(), 'Hello');
+        assert.strictEqual(iframe.contentDocument.head.querySelector('style').textContent.trim().replace(/\s/g, ''),
+                           'body{color:red;}', 'Head nodes should remain unaltered in the head');
+        assert.equal(iframe.contentWindow.getComputedStyle(iframe.contentDocument.body).color, 'rgb(255, 0, 0)');
+        // check button is there
+        assert.containsOnce(target, '#codeview-btn-group > button');
+        // edit in xml editor
+        await click(target, '#codeview-btn-group > button');
         await togglePromises[togglePromiseId];
-        const codeView = fieldHtml.querySelector('textarea.o_codeview');
-        assert.ok(codeView);
-        assert.equal(codeView.value, htmlDocumentTextTemplate('Hello', 'red'));
-
-        await editInput(codeView, null, htmlDocumentTextTemplate('Hi', 'black'));
-
-        assert.ok(codeViewButton);
         togglePromiseId++;
-        await click(codeViewButton);
+        assert.containsOnce(target, '.o_field_html[name="txt"] textarea');
+        await editInput(target, '.o_field_html[name="txt"] textarea', htmlDocumentTextTemplate('Hi', 'blue'));
+        await click(target, '#codeview-btn-group > button');
         await togglePromises[togglePromiseId];
-        readonlyNode = fieldHtml.querySelector('.o_readonly');
-        assert.ok(readonlyNode);
-        assert.equal(readonlyNode.innerText, 'Hi');
-        assert.equal(window.getComputedStyle(readonlyNode).color, 'rgb(0, 0, 0)');
+        // check dispayed content after edit
+        iframe = target.querySelector('.o_field_html[name="txt"] iframe[sandbox="allow-same-origin"]');
+        await iframeReady(iframe);
+        assert.strictEqual(iframe.contentDocument.body.textContent.trim(), 'Hi');
+        assert.strictEqual(iframe.contentDocument.head.querySelector('style').textContent.trim().replace(/\s/g, ''),
+                          'body{color:blue;}', 'Head nodes should remain unaltered in the head');
+        assert.equal(iframe.contentWindow.getComputedStyle(iframe.contentDocument.body).color, 'rgb(0, 0, 255)',
+                     'Style should be applied inside the iframe.');
 
         const saveButton = target.querySelector('.o_form_button_save');
         assert.ok(saveButton);
@@ -128,30 +210,15 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
         await writePromise;
     });
 
-    /**
-     * Check that documents with data in a <head> node and with the readonly prop
-     * do not display the codeview button
-     */
-    QUnit.test("html fields with complete HTML document in readonly mode", async (assert) => {
+
+    QUnit.test("sanboxed preview mode not automatically enabled for regular values", async (assert) => {
         serverData.models.partner.records = [{
             id: 1,
             txt: `
-            <!DOCTYPE HTML>
-            <html xml:lang="en" lang="en">
-                <head>
-
-                    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-                    <meta name="format-detection" content="telephone=no"/>
-                    <style type="text/css">
-                        body {
-                            color: blue;
-                        }
-                    </style>
-                </head>
                 <body>
-                    Hello
+                    <p>Hello</p>
                 </body>
-            </html>`,
+            `,
         }];
         await makeView({
             type: "form",
@@ -159,16 +226,34 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
             resModel: "partner",
             serverData,
             arch: `
-                <form string="Partner">
-                    <field name="txt" widget="html" readonly="1"/>
+                <form>
+                    <field name="txt" widget="html"/>
                 </form>`,
         });
 
-        const readonlyElement = target.querySelector('.o_field_html .o_readonly');
-        assert.ok(readonlyElement);
-        assert.strictEqual(readonlyElement.innerText, 'Hello');
-        assert.strictEqual(window.getComputedStyle(readonlyElement).color, 'rgb(0, 0, 255)');
+        assert.containsN(target, '.o_field_html[name="txt"] iframe[sandbox]', 0);
+        assert.containsN(target, '.o_field_html[name="txt"] textarea', 0);
+    });
 
-        assert.containsN(target, '.o_codeview_btn', 0, 'Codeview toggle should not be possible in readonly mode.');
+    QUnit.test("sandboxed preview option applies even for simple text", async (assert) => {
+        serverData.models.partner.records = [{
+            id: 1,
+            txt: `
+                Hello
+            `,
+        }];
+        await makeView({
+            type: "form",
+            resId: 1,
+            resModel: "partner",
+            serverData,
+            arch: `
+                <form>
+                    <field name="txt" widget="html" options="{'sandboxedPreview': true}"/>
+                </form>`,
+        });
+
+        assert.containsOnce(target, '.o_field_html[name="txt"] iframe[sandbox="allow-same-origin"]');
     });
+
 });
diff --git a/addons/website_sale/static/tests/tours/website_sale_shop_mail.js b/addons/website_sale/static/tests/tours/website_sale_shop_mail.js
index 7946bc4d4d92f5ae12f56d2662f917280b8f8d91..f7580d2a64ca87f55c4cf756699f3e1d9476e49f 100644
--- a/addons/website_sale/static/tests/tours/website_sale_shop_mail.js
+++ b/addons/website_sale/static/tests/tours/website_sale_shop_mail.js
@@ -84,18 +84,6 @@ tour.register('shop_mail', {
     {
         content: "wait mail to be sent, and go see it",
         trigger: '.o_Message_content:contains("Your"):contains("order")',
-        run: function () {
-            window.location.href = "/web#action=mail.action_view_mail_mail&view_type=list";
-        },
-    },
-    {
-        content: "click on the first email",
-        trigger: '.o_data_cell:contains("(Ref S")',
-    },
-    {
-        content: "check it's the correct email, and the URL is correct too",
-        trigger: 'div.o_field_html[name="body_content"] p:contains("Your"):contains("order")',
-        extra_trigger: 'div.o_field_html[name="body_content"] a[href^="https://my-test-domain.com"]',
     },
 ]);
 });
diff --git a/addons/website_sale/tests/test_website_sale_mail.py b/addons/website_sale/tests/test_website_sale_mail.py
index d714532d50466ca2e5ef5003fa6359d9c560dc68..001cb9113155c577a4c07dbb8a08252ddc153290 100644
--- a/addons/website_sale/tests/test_website_sale_mail.py
+++ b/addons/website_sale/tests/test_website_sale_mail.py
@@ -4,6 +4,7 @@
 from unittest.mock import patch
 
 import odoo
+from odoo import fields
 from odoo.tests import tagged
 from odoo.tests.common import HttpCase
 
@@ -29,4 +30,11 @@ class TestWebsiteSaleMail(HttpCase):
         self.env['ir.config_parameter'].sudo().set_param('mail_mobile.disable_redirect_firebase_dynamic_link', True)
 
         with patch.object(MailMail, 'unlink', lambda self: None):
+            start_time = fields.Datetime.now()
             self.start_tour("/", 'shop_mail', login="admin")
+            new_mail = self.env['mail.mail'].search([('create_date', '>=', start_time),
+                                                     ('body_html', 'ilike', 'https://my-test-domain.com')],
+                                                    order='create_date DESC', limit=1)
+            self.assertTrue(new_mail)
+            self.assertIn('Your', new_mail.body_html)
+            self.assertIn('Order', new_mail.body_html)