Skip to content

Domain Model

Entity Hierarchy

Organization (from JWT `iss` claim)
  └── Domain
        └── Inbox
              └── Thread
                    └── Message
                          ├── Recipients (to/cc/bcc)
                          ├── References (Message-ID chain)
                          ├── Attachments
                          └── Delivery Events

Threading uses standard RFC 5322 headers. When a message arrives or is sent with an In-Reply-To header that matches a known MessageId, it is assigned to that thread. If no match is found, the References chain and subject fallback are tried before creating a new thread. The message_references table stores the full References chain for robust thread reconstruction.

Threads support soft-delete: DELETE /threads/:id sets deleted_at rather than removing the row. Messages remain accessible by direct ID lookup.

Core Types

Domain

A sending/receiving domain with SES identity and DNS verification.

FieldTypeDescription
idUUIDPrimary key
nameStringDomain name (e.g., mail.acme.com)
ses_identity_arnOption<String>ARN after SES identity creation
dkim_tokensVec<String>DKIM CNAME tokens from SES
from_nameOption<String>Default sender display name
reply_to_addressOption<String>Default Reply-To address
bounce_addressOption<String>Bounce notification address
verificationDnsVerificationMX, SPF, DKIM, DMARC status (all booleans)
created_atDateTimeCreation timestamp
updated_atDateTimeLast update timestamp
deleted_atOption<DateTime>Soft-delete timestamp

Inbox

A named receiving address within a domain.

FieldTypeDescription
idUUIDPrimary key
domain_idUUIDParent domain
nameStringDisplay name
local_partStringAddress prefix (e.g., support)
is_catchallboolAccept mail for any unmatched address
created_atDateTimeCreation timestamp
updated_atDateTimeLast update timestamp
deleted_atOption<DateTime>Soft-delete timestamp

EmailMessage

Source: crates/core/src/lib.rsmessage::EmailMessage

FieldTypeDescription
message_idMessageId (String)RFC 5322 Message-ID header (primary key)
thread_idThreadId (UUID)Resolved thread
inbox_idUUIDOwning inbox
directionInbound | OutboundMessage direction
fromEmailAddressSender
to / cc / bccVec<EmailAddress>Recipients
subjectStringSubject line
sent_atDateTimeSend timestamp
in_reply_toOption<MessageId>Parent message reference
referencesVec<MessageId>Full reference chain
body_textOption<String>Plain text body
body_htmlOption<String>HTML body
attachmentsVec<EmailAttachment>Attachment metadata
raw_storage_keyStringS3 key for raw MIME
ses_message_idOption<String>SES-assigned ID (outbound only)

Supporting Types

Source: crates/core/src/lib.rs (message module) and crates/core/src/threading.rs

  • MessageId — Newtype around String. RFC 5322 Message-ID header value (e.g., <unique-id@domain>).
  • ThreadId — Newtype around Uuid. Identifies a conversation thread.
  • EmailAddress{ name: Option<String>, address: String }. Used for from/to/cc/bcc fields.
  • EmailEnvelope — Headers and routing info without body content: message_id, from, to/cc/bcc, subject, sent_at, in_reply_to, references.
  • EmailAttachment — Attachment metadata: filename, content_type, storage_key, size_bytes. Content stored in S3.
  • EmailThread — Thread container: thread_id, inbox_id, subject, created_at, updated_at, message_count, deleted_at.

DeliveryStatus

Source: crates/core/src/lib.rsmessage::DeliveryStatus

Variants: Pending, Sent, Delivered, Bounced { reason }, Complained, Failed { reason }.

State transitions:

Pending ──► Sent ──► Delivered

              ├──► Bounced
              ├──► Complained
              └──► Failed

DomainError

Source: crates/core/src/lib.rserror::DomainError

VariantFieldsHTTP Status
Validation{ field: &'static str, reason: String }400 Bad Request
NotFound{ resource: &'static str, id: String }404 Not Found
AttachmentTooLarge{ size_bytes: u64, limit_bytes: u64 }413 Payload Too Large
StorageBox<dyn Error + Send + Sync>500 Internal Server Error
DeliveryBox<dyn Error + Send + Sync>500 Internal Server Error

See crates/http/src/error.rs for the full HTTP mapping implementation.

Database Tables

TablePurpose
threadsConversation containers
messagesIndividual emails with envelope metadata
message_recipientsTo/CC/BCC addresses per message
message_referencesReferences header chain per message
attachmentsFile metadata with S3 storage keys
delivery_eventsSES status tracking per message
webhook_endpointsRegistered webhook URLs and config
webhook_deliveriesPending/completed webhook delivery records
suppressed_addressesBounce/complaint suppression list
domainsSending domains with verification state
inboxesReceiving addresses within domains
auth_keysJWT public key registry

See Schema Reference for full SQL DDL, column definitions, and indexes.

S3 Storage Layout

raw-email-bucket/
  {yyyy}/{mm}/{dd}/{message-id-hash}.eml

attachments-bucket/
  {yyyy}/{mm}/{dd}/{message-id-hash}/{attachment-uuid}.{ext}

Key format:

  • Date partitioning for lifecycle policies
  • Message-ID hashed to avoid special characters in keys
  • Original extension preserved for attachments