warmbox/docs/03-domain-model.md

3.7 KiB

Domain Model (Target)

Evolution from flat Inbox to campaign-centric model. Implement incrementally; not all entities ship in phase 1.

Entity relationship (target)

erDiagram
    Workspace ||--o{ Mailbox : owns
    Workspace ||--o{ WarmupCampaign : has
    Mailbox ||--o{ WarmupCampaign : "primary"
    WarmupCampaign ||--o{ CampaignSatellite : assigns
    Mailbox ||--o{ CampaignSatellite : "satellite"
    WarmupCampaign ||--o{ DailySchedule : plans
    WarmupCampaign ||--o{ DailyStat : tracks
    WarmupCampaign ||--o{ WarmupLog : generates
    Mailbox ||--o{ WarmupLog : sends
    Mailbox ||--o{ WarmupLog : receives

Mailbox (evolved Inbox)

Field Type Notes
id cuid
workspaceId cuid Future multi-tenant; nullable for self-hosted
email string Unique per workspace
role enum primary | satellite | unassigned
smtpHost, smtpPort, imapHost, imapPort Connection
username, password Encrypted
provider enum gmail | yahoo | outlook | custom
status enum active | paused | error | warming | completed
industry string AI context
dnsStatus json SPF/DKIM/DMARC check results (future)
lastError, errorAt

Rule: A mailbox can be primary in one campaign and satellite in another only if explicitly allowed (default: one role per campaign assignment).

WarmupCampaign

One campaign = one primary mailbox warming with one recipe.

Field Type Notes
id cuid
workspaceId cuid
primaryMailboxId cuid FK Mailbox
name string e.g. "FillCare hello@"
recipe enum grow | flat | randomized | custom
status enum draft | active | paused | completed | failed
durationDays int Default 30, max 45
startedAt datetime
endsAt datetime Computed
minEmailsPerDay int Default 1
maxEmailsPerDay int Default 40, max 50
replyRatePercent int Default 30, max 45
customSchedule json For custom recipe: [{ day, sends }]
timezone string Send window TZ
sendWindowStart time e.g. 08:00
sendWindowEnd time e.g. 18:00
currentDay int Day index since start
rampState json Recipe-specific state

CampaignSatellite (join)

Field Type
campaignId cuid
satelliteMailboxId cuid
weight int
active boolean

DailySchedule

Pre-computed plan for a campaign day (generated at campaign start or nightly).

Field Type
campaignId cuid
date date
dayIndex int
plannedSends int
plannedReplies int
actualSends int
actualReplies int
spamDetected int
spamRescued int

WarmupLog (evolved)

Add campaignId, placement (inbox | spam | unknown), scheduledAt, sentAt, repliedAt.

DailyStat (materialized)

Rollup for fast dashboard queries.

Field Type
campaignId cuid
date date
sent, received, replied, spamDetected, spamRescued int
replyRateActual float
inboxPlacementRate float

Setting (global / workspace)

Move cron and AI config to workspace scope over time.

Migration from current schema

Current Target
Inbox Mailbox + role
Per-inbox dailyLimit, currentRamp WarmupCampaign fields
Flat dispatcher Campaign-aware dispatcher
WarmupLog Add campaignId

Phase 1 migration (minimal)

  1. Add role to Inbox (primary | satellite)
  2. Add WarmupCampaign table
  3. Add campaignId to WarmupLog
  4. Keep backward compat: inboxes without campaign use legacy flat mode (deprecated)