The message body — stored once, regardless of how many mailboxes receive
it. Per-recipient state (which folder it's in, whether it's been read,
starred, trashed) lives on PhoenixKitInbox.Schemas.Delivery, one row per
recipient mailbox.
This is the same split Gmail and every other webmail uses, and it's why "mark as read in my mailbox" doesn't mark it read for the four other people on the thread.
Threading
thread_uuid groups a conversation. A brand-new message gets
thread_uuid == uuid (it is the root); a reply inherits the parent's
thread_uuid and sets parent_uuid. That's enough for
Messages.list_thread/2 to return a conversation in one indexed query, and
it leaves room for a threaded reading pane later without a migration.
Drafts
A draft is a message with status: "draft" and no sent_at. It gets exactly
one delivery row — into the author's own "drafts" folder. Sending flips
status to "sent", stamps sent_at, and fans out the recipient deliveries.
Tables are created by PhoenixKitInbox.Migrations, never by this schema.
Summary
Functions
Valid values for the body_format column.
Changeset for creating or updating a draft.
Changeset applied at send time. A sent message must carry a subject or a body — an entirely blank message is never worth delivering to anyone.
Valid values for the status column.
Types
@type t() :: %PhoenixKitInbox.Schemas.Message{ __meta__: term(), body: term(), body_format: term(), inserted_at: term(), metadata: term(), parent_uuid: term(), sender_mailbox_uuid: term(), sender_user_uuid: term(), sent_at: term(), status: term(), subject: term(), thread_uuid: term(), updated_at: term(), uuid: term() }
Functions
@spec body_formats() :: [String.t()]
Valid values for the body_format column.
Changeset for creating or updating a draft.
Deliberately lenient about subject/body: an empty draft is a legitimate
thing to autosave. sent_changeset/2 is where the real requirements land.
Changeset applied at send time. A sent message must carry a subject or a body — an entirely blank message is never worth delivering to anyone.
@spec statuses() :: [String.t()]
Valid values for the status column.