models:
- name: MailMessageSchedule
collection: mail_message_schedules
description: "mail.message.schedule — delays the NOTIFICATION of an already-posted mail.message (MAIL-M8). The message exists; the bus/notify fan-out is held until scheduled_datetime. Dispatch deletes the row (Odoo has no state column; the port keeps delete-on-dispatch and takes claim safety from FOR UPDATE SKIP LOCKED per ADR-0020)."
fields:
id:
type: uuid
attributes: ["@id", "@default(uuid)"]
description: "Primary key"
mail_message_id:
type: uuid
attributes: ["@required", "@foreign_key(MailMessage.schedules)", "@exclude_from_foreign_key_check", "@indexed"]
description: "The posted message whose notifications are delayed # logical FK to MailMessage.id (Odoo: cascade)"
notification_parameters:
type: string?
description: "JSON-encoded notify kwargs propagated to the notify pump at fire time (Odoo: Text)."
scheduled_datetime:
type: datetime
attributes: ["@required"]
description: "When the notification fan-out should fire."
metadata:
type: Metadata
attributes: ["@audit_metadata"]
description: "Audit metadata (created_at, updated_at, deleted_at, created_by, updated_by, deleted_by)"
indexes:
- type: index
fields: [scheduled_datetime]
name: mail_message_schedule_due_idx
- name: MailScheduledMessage
collection: mail_scheduled_messages
description: "mail.scheduled.message — holds the COMPOSER'S post values to delay the POSTING of a message (MAIL-M9; nothing exists in mail.message until it fires). Dispatch re-checks the creator's post permission on the target document (via the ThreadAccessResolver seam, MAIL-B1), posts through the message_post pump as the creator, and unlinks; the cron path notifies the author instead of raising on failure. Target (model,res_id) is immutable after create (Odoo raises on write)."
fields:
id:
type: uuid
attributes: ["@id", "@default(uuid)"]
description: "Primary key"
subject:
type: string?
description: "Message subject (composer value)."
body:
type: string
attributes: ["@required"]
description: "HTML body (sanitized on write — composer value)."
scheduled_date:
type: datetime
attributes: ["@required"]
description: "When the post should fire (service-validated not-in-past at create/write)."
composition_comment_option:
type: string?
description: "Comment mode marker: reply_all | forward (plain string — no lifecycle semantics)."
model:
type: string
attributes: ["@required", "@indexed"]
description: "Target document model (must be a chatter host — checked via ThreadAccessResolver at write time; immutable after create)."
res_id:
type: uuid
attributes: ["@required", "@exclude_from_foreign_key_check"]
description: "Target document id (immutable after create). LOGICAL column, no cross-module FK."
author_party_id:
type: uuid
attributes: ["@required", "@foreign_key(party.Party.scheduled_messages)", "@exclude_from_foreign_key_check"]
description: "Author partner # logical FK to party.Party.id"
recipient_party_ids:
type: json?
description: "Recipient partner ids as a JSON array (Odoo m2m res.partner; short-lived composer values — a join table is not worth a cross-module relation here)."
is_note:
type: boolean
attributes: ["@default(false)"]
description: "Whether the message will be posted as a note (subtype mt_note instead of mt_comment)."
notification_parameters:
type: string?
description: "JSON-encoded notify kwargs whitelist (email_from, message_type, reply_to, ...) applied at post time."
send_context:
type: json?
description: "Posting context JSON (e.g. trigger document state changes on send)."
metadata:
type: Metadata
attributes: ["@audit_metadata"]
description: "Audit metadata (created_at, updated_at, deleted_at, created_by, updated_by, deleted_by)"
indexes:
- type: index
fields: [scheduled_date]
name: mail_scheduled_message_due_idx
- type: index
fields: [model, res_id]
name: mail_scheduled_message_target_idx