Migration notes
MailDesk uses Odoo's standard migrations/<version>/{pre,post}-migration.py mechanism. This page covers the notable schema/data migrations in the v18 line — what each one changes, why, and the upgrade-ordering gotchas. Migrations run on -u upgrades; fresh installs run none of them, so anything a fresh install needs (extensions, indexes created in init) lives in model init(), not here.
MailDesk uses Odoo's standard migrations/<version>/{pre,post}-migration.py mechanism. This page covers the
notable schema/data migrations in the v18 line — what each one changes, why, and the upgrade-ordering
gotchas. Migrations run on -u upgrades; fresh installs run none of them, so anything a fresh install
needs (extensions, indexes created in _init) lives in model init(), not here.
Audience: developers / operators. Version directories, parameter keys, and helper names below are code references, verified against source (see footer).
Migration map (Basic)
| Version | Script | What it does |
|---|---|---|
| 18.0.2.0.2 | pre + post | early schema sync helpers (_column_exists guards) |
| 18.0.3.0.0 | post | v3 re-bootstrap reset — resets provider cursors + folder state to "re-bootstrap needed"; preserves OAuth tokens; no network calls |
| 18.0.3.1.4 | — | empty placeholder (directory exists, no source) |
| 18.0.3.1.5 | post | neutralizes legacy ingest-queue rows pointing at ingest_allowed = FALSE SSOT rows (backfill stopped reaching the mail gateway) |
| 18.0.3.1.9 | — | empty placeholder (directory exists, no source) |
| 18.0.3.1.10 | post | per-company OAuth promote + identity-reconciliation partial indexes (detail below) |
| 18.0.4.0.0 | pre + post | pre: backfill company_id on mail rows; post: backfill body_text search excerpt (detail below) |
Migration map (Pro)
| Version | Script | What it does |
|---|---|---|
| 18.0.2.0.2 | pre | table-existence-guarded schema prep |
| 18.0.3.0.0 | pre | Message-ID normalization prep (_MSGID_RE) |
| 18.0.3.1.2 | pre | repair invalid maildesk_email_link.message_index_id via repair_maildesk_email_link_message_index_id |
| 18.0.4.0.0 | pre | enforce maildesk_email_link.message_index_id NOT NULL (detail below) |
18.0.3.1.10 — per-company OAuth + identity indexes (Basic, shipped)
maildesk_mail_client/migrations/18.0.3.1.10/post-migration.py does two unrelated things in one
migrate(cr, version), each guarded by its own completion flag so re-runs are no-ops:
a) Promote legacy global OAuth credentials to base.main_company
Release 18.0.3.1.10 made OAuth configuration company-aware — Gmail/Outlook client id + secret moved from
global ir.config_parameter to fields on res.company. To keep single-company installs working without
manual re-entry, the migration copies the legacy global params onto the default company
(env.ref("base.main_company")):
Legacy ir.config_parameter |
→ res.company field |
|---|---|
google_gmail_client_id |
google_gmail_client_identifier |
google_gmail_client_secret |
google_gmail_client_secret |
microsoft_outlook_client_id |
microsoft_outlook_client_identifier |
microsoft_outlook_client_secret |
microsoft_outlook_client_secret |
A field is only written when the legacy param is non-empty and the company field is still empty (no
clobber). Guard flag: maildesk_mail_client.migration_18_0_3_1_10_oauth_credentials_promoted.
Operator note / precedence: after this migration, per-company res.company fields are the source of
truth; the legacy global params remain in the DB as a fallback but are no longer the configuration UI. The
old global params are not deleted.
b) Identity-reconciliation partial indexes (MailDesk-307)
Two partial indexes on maildesk_message_index, each covering only tombstoned rows (near-zero footprint
on healthy installs):
maildesk_message_index_identity_reconciliation_idx—(account_id, message_id) WHERE deleted_on_server = TRUEmaildesk_message_index_gmail_identity_idx—(account_id, uid) WHERE provider = 'gmail' AND deleted_on_server = TRUE
Both are existence-checked (pg_indexes) and table-existence-checked before creation. Guard flags:
...identity_reconciliation_index_done / ...gmail_identity_reconciliation_index_done.
18.0.3.1.4 / 18.0.3.1.9 — empty placeholders
Both directories exist under maildesk_mail_client/migrations/ but contain no migration source (only a
__pycache__/ from a prior build). They are version-bump placeholders — Odoo runs nothing for these
versions. Do not treat them as missing scripts; there is intentionally nothing to run.
18.0.4.0.0 — company_id backfill + search body excerpt (Basic, shipped)
pre-migration — backfill company_id
maildesk_mail_client/migrations/18.0.4.0.0/pre-migration.py. 18.0.3.1.10 added a required=True
company_id to mailbox.account (and added company_id to fetchmail.server / ir.mail_server) but
shipped without a pre-migration. On DBs with existing rows, Odoo's automatic schema sync raced the
@api.constrains cross-check (mail_server_id.company_id == mailbox.account.company_id), breaking the upgrade
transaction (root cause: task #705).
This pre-migration runs before model setup and, per affected table:
- adds the
company_idcolumn if missing (NULLABLE), then - backfills it from the most authoritative available source.
It must run as pre-migration precisely so the column is populated before the not-null constraint and the constrains check execute.
post-migration — seed body_text search excerpt
maildesk_mail_client/migrations/18.0.4.0.0/post-migration.py. 18.0.4.0.0 added
maildesk.message_index.body_text — a bounded plain-text excerpt the mailbox search matches against. New
mail populates it at sync; this script seeds it for existing mail from maildesk.ui_cache (the rendered
body of every message a user has opened, within TTL). Rows without a cache entry stay NULL and gain
body_text on the next sync/open.
Important: the pg_trgm extension and the GIN search indexes are not created here — they live in
maildesk.message_index.init() so fresh installs (which run no migrations) also get them. Idempotent via a
completion flag; only touches rows whose body_text is still empty.
18.0.4.0.0 — link NOT-NULL enforcement (Pro, shipped at tag v18.0.4.0.0)
maildesk_mail_client_pro/migrations/18.0.4.0.0/pre-migration.py calls
repair_maildesk_email_link_message_index_id(cr, ...) (from tools/email_link_schema.py) before schema
checks: it detects NULL maildesk_email_link.message_index_id, best-effort backfills from legacy columns
(account_id, folder, message_id) via normalized Message-ID match, fail-closed deletes any remaining
NULL rows (no synthetic index rows), then enforces NOT NULL. Logs updated / dropped / set_not_null.
Provenance note for editors: in the current working tree this Pro 18.0.4.0.0 migration directory holds only a
__pycache__/artifact (the working tree is on a feature branch). The source is committed at release tagv18.0.4.0.0(and branchMailDesk-824-v18) — so it is a shipped 4.0.0 migration, not a branch-only one. Read it withgit -C maildesk_mail_client_pro show v18.0.4.0.0:maildesk_mail_client_pro/migrations/18.0.4.0.0/pre-migration.py.
18.0.4.2.0 — prefetch defaults (Pro only, branch-only / near-release)
Status banner. This migration is not in
18.0HEAD. It lives on the 4.2.0 branches (MailDesk-prefetch-cache-v18), part of the realtime/prefetch work tracked by MRs !237 (Basic) / !124 (Pro). Treat it as forward-looking until the MRs merge.
maildesk_mail_client_pro/migrations/18.0.4.2.0/post-migration.py (branch only) — "Set prefetch defaults
and prime IMAP profile on existing accounts." On upgrade it:
- sets prefetch config defaults only if unset —
maildesk.prefetch.enabled=1,maildesk.prefetch.batch_per_run=5,maildesk.prefetch.max_per_account_per_hour=200,maildesk.prefetch.folder_open_top_n=30; - primes the IMAP profile for active IMAP accounts not yet classified
(
last_capability_check_at = False, non-Gmail/non-Outlook) viaaction_maildesk_probe_imap_capabilities()— probe failures are non-fatal (the weekly profile cron retries).
This migration is Pro-only. Basic has no migrations/18.0.4.2.0/ on either 4.2.0 branch — the
prefetch queue, hooks, and defaults all live in Pro. (The Basic 4.2.0 changes are model/controller code, not a
data migration.) See Realtime architecture §2.3–§2.4 and
Extension points → Coming in 4.2.0 for the runtime side.
Upgrade-ordering cheatsheet
- company-aware OAuth (3.1.10) → company_id backfill (4.0.0 pre): if you skip directly from a
pre-3.1.10 DB to 4.0.0, both run in version order; the 4.0.0 pre-migration is the safety net for the
missing 3.1.10 pre-migration. Do not hand-create the
company_idcolumn. - Search indexes / pg_trgm are created in
maildesk.message_index.init(), not in the 4.0.0 post-migration — re-running-u maildesk_mail_clientis enough to (re)create them; you never need to re-run a migration for them. - Idempotency: every notable migration above is flag-guarded or condition-guarded; re-running an upgrade is safe.
- No network calls happen in any migration (the v3 reset explicitly preserves tokens and performs no provider I/O); the only provider touch is the branch-only 4.2.0 IMAP capability probe, which is best-effort.