MailDesk docs
Get MailDesk
Technical

Frontend assets

MailDesk's UI is an Owl single-page client action loaded into the Odoo web backend. Basic ships the full desktop client; Pro extends it in place — almost entirely through Odoo's patch() mechanism plus a small set of new components, services, and a mobile-adaptive layer. This page maps the OWL components, services, hooks, registries, asset bundles, and the extension model.

7 min read

MailDesk's UI is an Owl single-page client action loaded into the Odoo web backend. Basic ships the full desktop client; Pro extends it in place — almost entirely through Odoo's patch() mechanism plus a small set of new components, services, and a mobile-adaptive layer. This page maps the OWL components, services, hooks, registries, asset bundles, and the extension model.

All frontend files live under each module's static/src/ and carry the proprietary Metzler IT license header.


1. Asset bundles

Both modules declare assets into the standard Odoo backend bundle (no custom bundle keys):

Bundle Basic Pro
web.assets_backend full client (SCSS + JS + XML templates) extension SCSS/JS/XML, mobile layer, AI surface, patches
web.assets_unit_tests Hoot tests (static/tests/*.test.js) Hoot tests

Notes: - Basic loads web/static/lib/jquery/jquery.js first, then scss/maildesk.scss, then services → hooks → utils → registries → fields → the maildesk.esm.js action → composer → components/dialogs. - Pro loads its SCSS first (theme tokens → shared panels → AI → adaptive → desktop dark), then its services, hooks, mobile components, AI-surface components, and finally the patches/* (so the base classes exist before they are patched). There is no separate dark-mode or mobile bundle — the adaptive/dark assets ship unconditionally with Pro and are activated at runtime (see §6). - Asset order within a bundle is load order; Pro's patches are listed last in the manifest for exactly this reason.

Source: maildesk_mail_client/__manifest__.py and maildesk_mail_client_pro/__manifest__.py assets keys.


2. Basic frontend (maildesk_mail_client/static/src)

2.1 Client action

js/maildesk.esm.js defines class MailDesk extends Component (static template = "maildesk_mail_client.MailDesk") and registers it as the client action:

registry.category("actions").add("maildesk_mail_client.maildesk_action", MailDesk);

It is the shell: it owns initial data loading, message/thread loading and pagination, folder/account selection, the composer launch handlers, optimistic delete with undo, and URL-hash routing. Its static components = { FolderTree, MailList, MailDetail, UndoToast }. It wires action callbacks onto the maildesk.store service so child components can trigger them, and starts/stops the maildesk.sync bus listeners on mount/unmount.

2.2 OWL components (16)

# Component Path (under static/src/) Role
1 FolderTree components/maildesk/folder_tree/ account + folder sidebar tree
2 MailList components/maildesk/mail_list/ message list / pagination
3 MailDetail components/maildesk/mail_detail/ reading pane + toolbar host
4 ThreadContainer components/maildesk/thread_container/ thread view container
5 ThreadMessage components/maildesk/thread_message/ one message in a thread (iframe body)
6 Toolbar components/maildesk/toolbar/ detail/list action toolbar
7 FilterMenu components/maildesk/filter_menu/ filter controls
8 UndoToast components/maildesk/undo_toast/ undoable trash toast
9 DebugStats components/maildesk/debug_stats/ developer diagnostics overlay
10 AssignTagsDialog components/dialogs/assign_tags/ tag-assignment dialog
11 ContactPickerDialog components/dialogs/contact_picker/ contact picker dialog
12 MoveToFolderDialog components/dialogs/move_to_folder/ move-to-folder dialog
13 PartnerCardPopover components/popovers/partner_card/ sender card popover
14 EmailInputField components/fields/email_input/ recipient input field widget
15 ComposeSystray components/composer/ systray "compose" launcher
16 Composer hub core/composer/ ComposerContainer + window/input/bubble/record

The composer is built from several files under core/composer/ (composer_container, composer_window, composer_input, composer_bubble, composer_record, composer_shortcuts, composer_attachment_list). ComposerContainer is mounted once via the main_components registry (core/composer/main_components.esm.js), mirroring Odoo Mail's global-composer pattern.

2.3 Services (4)

Registered in registry.category("services"):

Service name File Dependencies Role
maildesk.store js/services/maildesk_store.esm.js reactive client state (accounts, folders, messages, threads, selection, filters)
maildesk.sync js/services/maildesk_sync.esm.js orm, bus_service, maildesk.store, maildesk.notification, multi_tab websocket bus listeners + optional background polling; account-scoped channels
maildesk.notification js/services/maildesk_notification.esm.js in-app + native notification handling
maildesk.composer core/composer/composer_service.esm.js global composer open/close (reply/replyAll/forward/draft/new)

The MailDesk action consumes all four via useService(...); maildesk.sync is the bus client that keeps the open UI fresh after a scheduled sync ingests a message (see Realtime architecture §1.2).

2.4 Hooks (2)

Hook File Role
useIframeResize hooks/useIframeResize.esm.js size the email-body iframe to its content
useMessageSelection hooks/useMessageSelection.esm.js list selection state/helpers

(utils/maildesk_iframe.esm.js and utils/maildesk_dom.esm.js are plain helper modules — e.g. normalizeMessage(...) — not OWL hooks.)

2.5 Registries — the public extension surface

registries/mail_detail_registry.esm.js defines four named registry categories that Pro and the bridges extend without forking MailDetail:

Category Slot in the reading pane
maildesk.detail.toolbar toolbar action contributors
maildesk.detail.header_before_subject header (above the subject) contributors
maildesk.detail.inline_cards inline cards rendered in the reading pane
maildesk.detail.side_panels side-panel contributors

Each has a register…Contributor(key, contributor, {sequence}) helper. A contributor is { key, Component, getProps?, isVisible?, sequence? }; resolveMailDetailRegistryEntries(category, context) filters by isVisible(context) and returns ordered {key, Component, props} for the component to render. This is the clean extension point (no monkey-patch needed) — see §4.

Components also register into Odoo core categories: actions (the client action), main_components (the composer hub), and systray (the compose launcher).

2.6 SCSS + service worker

  • scss/maildesk.scss and core/composer/composer.scss — Basic styling.
  • service_worker/maildesk_service_worker.js — prepended onto Odoo's native service worker by the webmanifest.py controller (mailbox-user-gated); handles notificationclick for maildesk.message payloads to open MailDesk deep links. This is the only "mobile-ish" Basic asset — Basic has no responsive/adaptive views.

3. Pro frontend (maildesk_mail_client_pro/static/src)

Pro adds bidirectional actions, the AI surface, signatures/linking, and the entire mobile-adaptive + dark layer. Most of it bolts onto Basic via patch() (§4).

3.1 New OWL components

AI surface (components/ai_surface/): AiPanel, AiPanelContainer, AiComposer, AiMessage, AiMarkdown (+ ai_markdown_parse), AiIcon, AiProviderIcon. AiPanelContainer is mounted once via the main_components registry (same pattern as the Basic composer hub):

registry.category("main_components").add("maildesk.AiPanelContainer", { Component: AiPanelContainer });

so the unified AI surface persists across navigation. AI insight (components/ai_insight/): AiThreadDigestCard — an inline thread-digest card.

Dialogs / fields (components/dialogs/, components/fields/): LinkToRecordDialog, BindToRecordDialog, SignaturePickerDialog, TemplatePickerDialog, ComposeSignaturePickerField, SelectedFollowerPickerField, plus the chatter SelectedFollowerPicker.

Mobile components (components/mobile/, each with its own SCSS + XML): MobileHeader, SidebarDrawer, BottomNav, BottomSheet, KebabMenu, BulkActionBar, ComposeFab, DetailOverlay, MobileModeToggle, ThemeToggle, MdtIcon.

3.2 Services (7)

Service name File Dependencies Role
maildesk.pro_ai services/pro_ai_service.esm.js orm, notification AI RPC client (summarize, security check, reply)
maildesk.ai_surface services/pro_ai_surface_service.esm.js (multiple) drives the unified AI panel/surface
maildesk.pro_linking services/pro_linking_service.esm.js orm email→Odoo-record linking RPC
maildesk.email_navigation services/email_navigation_service.esm.js orm open/navigate to emails from records
maildesk_pro.mobile_mode services/mobile_mode_service.esm.js detect/track mobile mode (touch/coarse-pointer or ≤480px; localStorage override maildesk_pro.mobileMode)
maildesk_pro.drawer_state services/drawer_state_service.esm.js folder-drawer open/close state
maildesk_pro.theme services/theme_service.esm.js dark/light theme state (mobile dark)

3.3 Hooks (6)

Hook File Role
useMobileMode hooks/use_mobile_mode.esm.js reactive mobile-mode flag (wraps the service)
useTheme hooks/use_theme.esm.js reactive theme state
useLongPress hooks/use_long_press.esm.js long-press → multi-select
useSwipe hooks/use_swipe.esm.js swipe gestures (swipe-to-archive)
useFocusTrap hooks/use_focus_trap.esm.js focus trapping for overlays/sheets
useIframeAutoHeight hooks/use_iframe_autoheight.esm.js full-height email-body iframe sizing (mobile detail + thread)

3.4 SCSS layers

scss/theme/maildesk_theme_tokens.scss (the --mdt-* design tokens), scss/maildesk_shared_panels.scss, scss/maildesk.scss, scss/maildesk_ai_insight.scss, scss/maildesk_ai_surface.scss, scss/maildesk_pro_actions.scss, the scss/adaptive/* set (dialogs, composer, panels, chatter, mobile dialogs), and scss/theme/desktop_dark.scss (desktop dark is a separate stylesheet from the mobile dark path). Each mobile component also ships a co-located SCSS file.


4. Extension model — how Pro (and bridges) extend Basic

Two complementary mechanisms, both first-class Odoo/OWL patterns:

4.1 Registry contributors (preferred, no patch)

A module adds a contributor to one of the Basic mail_detail_* registry categories. Example — the AI thread-digest card registers itself as an inline card, visible only when the message has a thread:

// components/ai_insight/ai_thread_digest_card.esm.js
import { registerMailDetailInlineCardContributor } from "@maildesk_mail_client/registries/mail_detail_registry.esm";
registerMailDetailInlineCardContributor(
    /* key */ "...",
    { Component: AiThreadDigestCard, isVisible: ({ message }) => Boolean(message && message.thread_id) },
    { sequence: 15 },
);

MailDetail renders all visible contributors in sequence order — so Pro and the bridges add panels, cards, header chips, and toolbar buttons without touching the Basic component.

4.2 patch() on prototypes (when behavior must change in place)

For deeper behavior changes Pro uses import { patch } from "@web/core/utils/patch" and patches the prototype of a Basic OWL component (or an Odoo core component), calling super.setup() / super.<method>() to chain. The patches/ directory holds all of these; the patched targets include:

Patch file Patches Adds
maildesk_patch.esm.js MailDesk.prototype Pro action behavior on the shell
maildesk_shell_mobile_patch.esm.js shell mobile shell wiring
mail_detail_patch.esm.js MailDetail.prototype Ask AI, Link-to-Record, delete-confirm, mobile detail menu, iframe auto-height
mail_list_patch.esm.js + mail_list_ai_patch.esm.js + mail_list_context_menu_patch.esm.js + mail_list_kebab_patch.esm.js + mail_list_touch_patch.esm.js MailList.prototype AI badges, context menu, kebab, touch/long-press/swipe
thread_message_patch.esm.js ThreadMessage.prototype dark-mode iframe, auto-height
composer_input_patch.esm.js ComposerInput.prototype signatures/templates
folder_tree_ai_patch.esm.js FolderTree.prototype "Ask MailDesk AI" sidebar launcher (useService("maildesk.ai_surface").open())
store_selected_recipients_patch.esm.js Odoo Store.prototype selected-recipient state
chatter_integration_patch.esm.js, chatter_selected_followers_patch.esm.js, chatter_message_navigation_patch.esm.js Odoo Thread/Chatter/Composer/Message chatter integration
chatgpt_dialog_patch.esm.js Odoo ChatGPTDialog/ChatGPTPromptDialog route the editor AI dialog through MailDesk
attachment_model_maildesk_patch.esm.js, message_model_maildesk_patch.esm.js Odoo Attachment/Message models MailDesk metadata

Because patches mutate prototypes, load order matters — Pro lists patches/* last in web.assets_backend so every base class is defined first (§1).


5. Runtime layering summary

graph TD
  subgraph Basic [Basic — web.assets_backend]
    A[MailDesk action] --> CMP[16 components]
    A --> SVC[4 services: store / sync / notification / composer]
    A --> HK[2 hooks]
    REG[4 mail_detail registries]:::ext
    SW[service worker]
  end
  subgraph Pro [Pro — same bundle]
    PSVC[7 services]:::pro
    PHK[6 hooks]:::pro
    AI[AI surface + insight]:::pro
    MOB[mobile components + dark theme]:::pro
    PAT[patches/*]:::pro
  end
  PAT -->|patch prototype| CMP
  AI -->|registry contributor| REG
  PAT -->|registry contributor| REG
  classDef pro fill:#efe6ff,stroke:#714697;
  classDef ext fill:#fff3e0,stroke:#e07b00;
  • Basic = the whole desktop client; non-adaptive; no AI; no dark mode.
  • Pro = same bundle, extended via patch() + registry contributors; adds AI, two-way actions, signatures/linking, the mobile-adaptive layer, and the mobile dark theme. Mobile/dark are auto-detected at runtime, never configured.

6. Mobile / dark activation (Pro)

Pro's adaptive assets load unconditionally; they activate at runtime via body markers, not via a separate bundle:

  • maildesk_pro.mobile_mode service detects touch/coarse-pointer or viewport ≤480px (override via localStorage["maildesk_pro.mobileMode"]) and toggles the .md-pro-mobile body class.
  • maildesk_pro.theme service toggles body.md-pro-dark (mobile dark; desktop dark is the separate scss/theme/desktop_dark.scss).
  • Stable Playwright selectors and breakpoints are listed in the discovery doc §7 and on the Mobile page.