123 lines
3.7 KiB
Markdown
123 lines
3.7 KiB
Markdown
# Domain Model (Target)
|
|
|
|
Evolution from flat `Inbox` to campaign-centric model. Implement incrementally; not all entities ship in phase 1.
|
|
|
|
## Entity relationship (target)
|
|
|
|
```mermaid
|
|
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 | Optional load balancing (default 1) |
|
|
| 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)
|