MailDesk docs
Get MailDesk
Technical

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.

5 min read

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 = TRUE
  • maildesk_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:

  1. adds the company_id column if missing (NULLABLE), then
  2. 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.


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 tag v18.0.4.0.0 (and branch MailDesk-824-v18) — so it is a shipped 4.0.0 migration, not a branch-only one. Read it with git -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.0 HEAD. 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:

  1. 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;
  2. primes the IMAP profile for active IMAP accounts not yet classified (last_capability_check_at = False, non-Gmail/non-Outlook) via action_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_id column.
  • Search indexes / pg_trgm are created in maildesk.message_index.init(), not in the 4.0.0 post-migration — re-running -u maildesk_mail_client is 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.