Inbox Agent — Canonical Data Model & Entity Relationship Specification

Status: Authoritative Data Model Baseline
Audience: Cursor, Charter, engineering, data architecture
Critical correction: MailMessage and MessageInstance are distinct entities and MUST NOT be collapsed.

1. Modeling Principles

The canonical model separates logical mail content from the provider/account-specific occurrence of that content. This prevents mailbox state, provider identifiers, and synchronization concerns from contaminating message identity.

Primary principles:

2. MailMessage vs MessageInstance — Authoritative Rule

MailMessage

MailMessage represents the logical RFC-style message/content object.

It owns content-level attributes such as:

There should normally be one MailMessage for the same logical message even when it appears in multiple accounts, folders, or provider representations.

MessageInstance

MessageInstance represents a specific occurrence of a MailMessage within a connected account/provider context.

It owns instance/provider state such as:

Cardinality: MailMessage 1 -> N MessageInstance.

A MessageInstance MUST reference exactly one MailMessage. A MailMessage may exist with one or many instances.

This correction supersedes any earlier interpretation in which MailMessage represented a mailbox-specific provider row.

3. Core Entity Catalog

User

Application principal/owner.

Key attributes: user_id, identity-provider subject, status, created/updated timestamps.

ConnectedAccount

A mail account authorized for synchronization.

Key attributes: account_id, user_id, provider type, provider account identity, display address, capability profile, auth-secret reference, sync status, created/updated timestamps.

Unique logical constraint: provider + provider account identity per user.

MailboxContainer

Canonical representation of provider folders/labels/mailboxes.

Key attributes: container_id, account_id, provider container ID, name, canonical semantic type, parent ID where hierarchical, provider metadata.

MailThread

Canonical conversation/thread abstraction.

Key attributes: thread_id, normalized subject/key, created/latest timestamps.

Provider conversation IDs belong in mappings/instances and are not assumed globally canonical.

MailMessage

Logical message/content entity as defined above.

Suggested attributes:

Deduplication must not rely solely on Internet Message-ID because it may be absent or malformed. Canonicalization uses provider/source evidence plus content/header fingerprints under deterministic rules.

MessageInstance

Provider/account-specific occurrence.

Suggested attributes:

Unique constraint: (account_id, provider_message_id).

MessageContainerMembership

Many-to-many relationship between MessageInstance and MailboxContainer, supporting label-based providers.

Key: (message_instance_id, container_id) plus provider membership metadata if required.

Party

A person/organization identity concept used for mail participation and future contact intelligence.

Key attributes: party_id, type, display name, organization relationship where known.

EmailAddress

Normalized email address identity.

Key attributes: email_address_id, normalized address, display/local metadata.

Email addresses are not assumed one-to-one with a person.

PartyEmailAddress

Association between Party and EmailAddress with source/provenance, confidence, type, validity dates.

This supports future contact consolidation without forcing it into MVP.

MessageParticipant

Associates a MailMessage with an email address/party and role.

Roles include: from, sender, reply-to, to, cc, bcc, resent variants as needed.

Key attributes: mail_message_id, ordinal, role, email_address_id, optional party_id, source display name.

Participants are content-level and therefore belong to MailMessage, not MessageInstance, unless a provider exposes an instance-specific envelope distinction that must be separately modeled.

Attachment

Logical attachment associated with MailMessage.

Key attributes: attachment_id, mail_message_id, content ID, filename, MIME type, size, content hash, inline flag, storage reference, scan/processing state.

Provider-specific attachment resource IDs may be held in AttachmentInstance.

AttachmentInstance

Optional mapping from logical attachment to a specific MessageInstance provider resource.

Key attributes: attachment_instance_id, attachment_id, message_instance_id, provider attachment ID, provider metadata.

ThreadMessage

Associates MailMessage to MailThread, with sequence/relationship metadata.

A message may normally belong to one canonical thread, but use an association entity to avoid coupling provider threading to the canonical message row.

AIInference

Immutable record of an AI task execution.

Key attributes: inference ID, mail_message_id, optional message_instance_id, task, model, prompt version, schema/taxonomy version, input fingerprint, output, confidence, validation status, timestamps, cost/latency metadata.

Classification

Normalized/current user-facing classification derived from inference or human override.

Key attributes: classification ID, mail_message_id, dimension, value, source type, source inference ID nullable, confidence, effective/current timestamps.

ExtractedEntity

Structured extraction from a message.

Key attributes: extracted entity ID, mail_message_id, inference ID, type, normalized value, evidence reference, confidence.

ActionItem

Structured requested/committed work identified in mail.

Key attributes: action item ID, mail_message_id, inference ID/source, description, responsible party, due date/time, status, confidence, user override metadata.

ProviderMutation

Durable requested change to provider state.

Key attributes: mutation ID, message_instance_id, account ID, mutation type, desired state, idempotency key, status, requested/sent/confirmed timestamps, provider response metadata, failure details.

SyncCheckpoint

Per-account/provider synchronization cursor/checkpoint.

Key attributes: checkpoint ID, account ID, scope, cursor/token reference, status, last success, recovery metadata.

SyncWorkItem

Durable unit of synchronization/background work.

Key attributes: work ID, account ID, type, payload reference, status, attempts, next attempt, correlation ID, error metadata.

DomainEventOutbox

Transactional event publication record.

Key attributes: event ID, event type, aggregate type/ID, payload, schema version, correlation/causation IDs, created/published timestamps.

ReconciliationRun

Tracks reconciliation execution.

Key attributes: run ID, account ID, scope/window, start/end, status, compared counts, drift counts, repaired counts, diagnostics.

AuditEvent

Append-oriented material action/decision record.

Key attributes: audit ID, actor, action, target type/ID, timestamp, correlation ID, sanitized metadata.

4. Relationship Summary

User
  1 ─── N ConnectedAccount
             1 ─── N MailboxContainer
             1 ─── N MessageInstance ─── N MessageContainerMembership ─── 1 MailboxContainer
                         N
                         │
                         1
                    MailMessage
                     │  │  │
                     │  │  ├── N MessageParticipant ─── 1 EmailAddress
                     │  ├──── N Attachment ─── N AttachmentInstance ─── 1 MessageInstance
                     ├─────── N AIInference
                     ├─────── N Classification
                     ├─────── N ExtractedEntity
                     └─────── N ActionItem

MailThread 1 ─── N ThreadMessage N ─── 1 MailMessage

Party N ─── N EmailAddress (via PartyEmailAddress)

MessageInstance 1 ─── N ProviderMutation
ConnectedAccount 1 ─── N SyncCheckpoint
ConnectedAccount 1 ─── N SyncWorkItem
ConnectedAccount 1 ─── N ReconciliationRun

5. Identity and Deduplication

Instance Identity

Provider instance identity is deterministic:

ConnectedAccount + provider_message_id -> MessageInstance

Canonical Message Identity

Canonical message resolution uses a ranked deterministic strategy, such as:

  1. known provider mapping to an existing canonical message;
  2. trustworthy normalized Internet Message-ID plus corroborating metadata;
  3. content/header fingerprint under strict matching rules;
  4. create new MailMessage when ambiguity remains.

False merges are more damaging than duplicate canonical messages. Ambiguous cases should remain separate and be eligible for later reconciliation.

6. Threading

Canonical threading must not blindly equate provider conversation IDs across accounts/providers.

Use:

Thread repair/recalculation must not change MailMessage identity.

7. State Ownership Matrix

DataOwner
Subject/body/core headersMailMessage
ParticipantsMailMessage
Logical attachmentsMailMessage
AI content classificationMailMessage
Provider message IDMessageInstance
Provider thread IDMessageInstance/source mapping
Read/unreadMessageInstance
Flag/starMessageInstance
Folder/label membershipMessageInstance
Trash/spam/archive stateMessageInstance
Provider change key/versionMessageInstance
Sync timestampsMessageInstance/account sync entities

This matrix is authoritative for implementation decisions.

8. Persistence Rules

9. Contact Future-Proofing

Contacts are currently a future consolidation concern. The model therefore distinguishes:

Do not make EmailAddress itself the canonical person. Do not overwrite a person/contact based on signature extraction without governed merge logic.

10. Migration Rule for Earlier Model

If an earlier schema used MailMessage as the provider-specific mailbox row:

  1. introduce the new canonical MailMessage;
  2. introduce/rename provider-specific rows as MessageInstance;
  3. move provider/account/folder/read/flag/sync attributes to MessageInstance;
  4. group instances into canonical messages conservatively;
  5. move participants/content-level attachments and AI classification to MailMessage;
  6. preserve provider identifiers and history;
  7. add constraints only after data migration validation;
  8. run deduplication/reconciliation reports.

No destructive merge should occur solely from matching subject or provider thread ID.

11. Data Model Acceptance Criteria

The model is correctly implemented when: