Add initial documentation for Warmbox, including product brief, current state analysis, glossary, domain model, warmup recipes, primary-satellite topology, scheduling, reply rate engine, spam rescue, AI content policy, statistics, ESP compliance, system architecture, API evolution, and frontend UX specification.
This commit is contained in:
parent
3b540371cf
commit
4eedbbfb1e
21 changed files with 2253 additions and 0 deletions
84
docs/00-product-brief.md
Normal file
84
docs/00-product-brief.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# Product Brief — Warmbox
|
||||||
|
|
||||||
|
## One-liner
|
||||||
|
|
||||||
|
Warmbox is a self-hosted (today) / SaaS (tomorrow) cold email warmup engine that safely builds sender reputation for **primary outbound mailboxes** using a controlled network of **satellite inboxes**, realistic AI-generated conversations, and human-like engagement signals — without triggering ESP anti-abuse systems.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
New or cold mailboxes used for B2B outreach land in spam. ESPs (Google, Microsoft, Yahoo) evaluate:
|
||||||
|
|
||||||
|
- Sending volume patterns (ramps, spikes, consistency)
|
||||||
|
- Authentication (SPF, DKIM, DMARC)
|
||||||
|
- Engagement (replies, not-spam actions, spam complaints)
|
||||||
|
- Content signals (spam triggers, templated/robotic text)
|
||||||
|
|
||||||
|
Commercial warmup tools exist but carry risks: toxic peer pools, detectable patterns, placeholder-filled AI content, and ESP policies against artificial warmup networks.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Warmbox gives operators **full control** over:
|
||||||
|
|
||||||
|
- Which mailboxes are warming (primaries) vs supporting (satellites)
|
||||||
|
- Warmup **recipe** (progressive ramp, flat, randomized, custom)
|
||||||
|
- Volume bounds (1–50/day), duration (30–45 days), reply rate (30–45%)
|
||||||
|
- Spam rescue and placement visibility per primary
|
||||||
|
- AI content quality guardrails (no placeholders, no spam triggers)
|
||||||
|
|
||||||
|
## Target users (personas)
|
||||||
|
|
||||||
|
### P1 — Cold email operator (FillCare / agency owner)
|
||||||
|
|
||||||
|
- Warms `hello@fillcareapp.com` and future sales domains
|
||||||
|
- Owns 4–10 personal Gmail/Yahoo/Live satellites
|
||||||
|
- Needs confidence before connecting Instantly/Smartlead
|
||||||
|
- Wants dashboard: sent, replied, spam rate, ramp progress
|
||||||
|
|
||||||
|
### P2 — Deliverability-focused growth lead
|
||||||
|
|
||||||
|
- Manages multiple primaries across brands
|
||||||
|
- Needs recipes, custom schedules, alerts on spam spikes
|
||||||
|
- Cares about Google Postmaster / Microsoft SNDS integration (future)
|
||||||
|
|
||||||
|
### P3 — SaaS customer (future)
|
||||||
|
|
||||||
|
- Signs up, connects mailboxes via OAuth or app passwords
|
||||||
|
- Pays per primary mailbox warmed
|
||||||
|
- Never sees other customers' satellites (tenant isolation)
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
| Goal | Measure |
|
||||||
|
|------|---------|
|
||||||
|
| Inbox placement | ≥95% warmup emails in inbox (not spam) after week 3 |
|
||||||
|
| Spam rescue | 100% of detected warmup-in-spam rescued within rescuer cycle |
|
||||||
|
| Reply realism | Reply rate within configured ±5% of target |
|
||||||
|
| Content quality | 0% emails with bracket placeholders; <1% AI validation failures |
|
||||||
|
| ESP safety | No account suspensions attributable to Warmbox patterns |
|
||||||
|
| Operator trust | Per-primary daily stats accurate within 1 message |
|
||||||
|
|
||||||
|
## Non-goals (MVP SaaS phase)
|
||||||
|
|
||||||
|
- Sending live cold campaigns (delegate to Instantly, Smartlead, Apollo)
|
||||||
|
- Shared public peer network at launch (optional later)
|
||||||
|
- Mobile native apps
|
||||||
|
- White-label reseller portal (future)
|
||||||
|
|
||||||
|
## Competitive positioning
|
||||||
|
|
||||||
|
| Capability | Warmbox | Typical warmup SaaS |
|
||||||
|
|------------|---------|---------------------|
|
||||||
|
| Self-hosted option | Yes | Rare |
|
||||||
|
| Own satellite pool | Yes | Often forced shared pool |
|
||||||
|
| Recipe control | Full (4 types) | Often progressive only |
|
||||||
|
| Open source core | Possible | No |
|
||||||
|
| ESP-aware behavior | Designed-in | Varies |
|
||||||
|
|
||||||
|
## Success criteria for "production ready"
|
||||||
|
|
||||||
|
1. Multiple primaries warming concurrently with independent recipes
|
||||||
|
2. Satellites only engage assigned primaries (no random cross-primary noise)
|
||||||
|
3. Statistics dashboard matches raw logs
|
||||||
|
4. Auto-pause on auth errors, bounce spikes, or spam-rate thresholds
|
||||||
|
5. Web UI for all operator workflows
|
||||||
|
6. Documented ESP compliance checklist passed before go-live
|
||||||
79
docs/01-current-state-and-gaps.md
Normal file
79
docs/01-current-state-and-gaps.md
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Current State & Gap Analysis
|
||||||
|
|
||||||
|
## What exists today (MVP)
|
||||||
|
|
||||||
|
### Data layer
|
||||||
|
|
||||||
|
| Model | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `Inbox` | Flat mailbox record (no role) |
|
||||||
|
| `WarmupLog` | Message lifecycle: sent → rescued_from_spam → replied → complete |
|
||||||
|
| `Setting` | Global key-value config |
|
||||||
|
|
||||||
|
### Jobs
|
||||||
|
|
||||||
|
| Job | Behavior |
|
||||||
|
|-----|----------|
|
||||||
|
| Dispatcher | Random sender/receiver from all active inboxes; AI email; SMTP send |
|
||||||
|
| Rescuer | Spam folder scan; move to INBOX; flag; AI reply (~100% reply when found) |
|
||||||
|
| Ramp bump | Midnight `currentRamp += 2` up to `dailyLimit` |
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
- Inbox CRUD + connection test
|
||||||
|
- Paginated logs
|
||||||
|
- Global settings (cron, industries)
|
||||||
|
|
||||||
|
### Strengths
|
||||||
|
|
||||||
|
- Working SMTP/IMAP connector with encrypted credentials
|
||||||
|
- Spam rescue on Gmail/Yahoo/Outlook folder names
|
||||||
|
- AI via OpenRouter (free models)
|
||||||
|
- Error inbox marking (`status: error`)
|
||||||
|
- Prisma 7 + SQLite portability
|
||||||
|
|
||||||
|
## Gap matrix
|
||||||
|
|
||||||
|
| Feature | Current | Target | Priority |
|
||||||
|
|---------|---------|--------|----------|
|
||||||
|
| Primary vs satellite roles | All equal | Role-based pairing | P0 |
|
||||||
|
| Multiple primaries | Implicit | Explicit campaigns per primary | P0 |
|
||||||
|
| Grow recipe | Partial ramp | Named recipe engine | P0 |
|
||||||
|
| Flat recipe | Missing | Constant daily volume | P0 |
|
||||||
|
| Randomized recipe | Missing | Daily variance algorithm | P1 |
|
||||||
|
| Custom recipe | Missing | Per-day schedule editor | P1 |
|
||||||
|
| Warmup duration 30–45d | Missing | Campaign start/end | P0 |
|
||||||
|
| Min emails/day (1) | Missing | Enforced floor | P0 |
|
||||||
|
| Max emails/day (40–50) | Partial | Validated cap | P0 |
|
||||||
|
| Reply rate 30% | ~100% | Probabilistic sampling | P0 |
|
||||||
|
| Per-primary statistics | Missing | Dashboard API | P0 |
|
||||||
|
| Spam count per primary | Partial (per log) | Aggregated metrics | P0 |
|
||||||
|
| AI placeholder blocking | Prompt only | Post-generation validation | P0 |
|
||||||
|
| Send time jitter | Missing | Human-like windows | P1 |
|
||||||
|
| Web UI | Missing | Full operator console | P1 |
|
||||||
|
| Multi-tenant | Missing | Org/workspace model | P2 |
|
||||||
|
| Peer network | Missing | Optional phase 3 | P3 |
|
||||||
|
| DNS/auth checks | Missing | SPF/DKIM/DMARC validation | P1 |
|
||||||
|
| Auto-pause on bad signals | Partial (auth only) | Spam/bounce thresholds | P1 |
|
||||||
|
| Tests | Missing | Unit + integration | P1 |
|
||||||
|
|
||||||
|
## Architectural debt
|
||||||
|
|
||||||
|
1. **Flat inbox pool** — Dispatcher treats every inbox as equal; primaries can email each other.
|
||||||
|
2. **Global ramp** — Not tied to campaign start date or recipe.
|
||||||
|
3. **SQLite** — Fine for self-hosted; need Postgres path for SaaS.
|
||||||
|
4. **No schedule entity** — "Planned sends today" is implicit, not queryable.
|
||||||
|
5. **Spam rescue granularity** — Marks all pending logs from sender, not per UID.
|
||||||
|
6. **No idempotency** — Cron overlap could double-send without locks.
|
||||||
|
|
||||||
|
## Recommended migration path
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase A: Schema + roles + recipes (backend only)
|
||||||
|
Phase B: Statistics API + reply rate engine
|
||||||
|
Phase C: Web UI (self-hosted)
|
||||||
|
Phase D: Hardening (jitter, validation, auto-pause)
|
||||||
|
Phase E: Multi-tenant + optional peer network
|
||||||
|
```
|
||||||
|
|
||||||
|
See [16-roadmap.md](./16-roadmap.md) for sprint breakdown.
|
||||||
20
docs/02-glossary.md
Normal file
20
docs/02-glossary.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Glossary
|
||||||
|
|
||||||
|
| Term | Definition |
|
||||||
|
|------|------------|
|
||||||
|
| **Primary mailbox** | The cold outbound address being warmed (e.g. `hello@fillcareapp.com`). Receives engagement signals; volume ramps per recipe. |
|
||||||
|
| **Satellite mailbox** | Supporting inbox (Gmail, Yahoo, Live) that sends to and replies from primaries. Not the main outreach address. |
|
||||||
|
| **Warmup campaign** | Configuration binding one primary to a recipe, duration, volume bounds, reply rate, and assigned satellites. |
|
||||||
|
| **Recipe** | Algorithm that determines how many emails to send per day over the campaign timeline. |
|
||||||
|
| **Ramp** | Progressive increase in daily send volume (Grow recipe). |
|
||||||
|
| **Reply rate** | Percentage of received warmup emails that get an AI-generated reply (not 100%). |
|
||||||
|
| **Spam rescue** | IMAP action: find warmup email in Spam/Junk, move to INBOX, mark read/flagged/important. |
|
||||||
|
| **Placement** | Where a message landed: inbox, spam, or unknown. |
|
||||||
|
| **ESP** | Email Service Provider (Google, Microsoft, Yahoo). |
|
||||||
|
| **Engagement signal** | Reply, not-spam move, flag, open (future) — positive reputation input. |
|
||||||
|
| **Peer network** | Shared pool of third-party mailboxes (optional future feature). |
|
||||||
|
| **Send window** | Time-of-day range when sends are allowed (e.g. 08:00–18:00 local). |
|
||||||
|
| **Jitter** | Random delay between sends to avoid machine-like cadence. |
|
||||||
|
| **Warmup log** | Single message transaction with lifecycle status. |
|
||||||
|
| **Daily stat** | Aggregated per-primary metrics for a calendar day. |
|
||||||
|
| **Tenant** | Future SaaS customer organization with isolated data. |
|
||||||
123
docs/03-domain-model.md
Normal file
123
docs/03-domain-model.md
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
# 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)
|
||||||
126
docs/04-warmup-recipes.md
Normal file
126
docs/04-warmup-recipes.md
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
# Warmup Recipes
|
||||||
|
|
||||||
|
Four recipes control **how many emails the primary receives/sends per day** over the campaign duration.
|
||||||
|
|
||||||
|
## Global constraints (all recipes)
|
||||||
|
|
||||||
|
| Parameter | Default | Min | Max |
|
||||||
|
|-----------|---------|-----|-----|
|
||||||
|
| Duration | 30 days | 14 | 45 |
|
||||||
|
| Min emails/day | 1 | 1 | — |
|
||||||
|
| Max emails/day | 40 | — | 50 |
|
||||||
|
| Reply rate | 30% | 10% | 45% |
|
||||||
|
| Volume change cap | +20% / 48h | — | Industry best practice |
|
||||||
|
|
||||||
|
## 1. Grow — Progressive (recommended)
|
||||||
|
|
||||||
|
**Use when:** New domain or cold mailbox. Safest default.
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
- Start at `minEmailsPerDay` (default 1) on day 1
|
||||||
|
- Increase daily toward `maxEmailsPerDay` over `durationDays`
|
||||||
|
- Never increase more than 20% vs prior 48h average
|
||||||
|
- After reaching max, hold flat until campaign end
|
||||||
|
- Optionally continue background warmup post-campaign at 20% of max
|
||||||
|
|
||||||
|
### Algorithm (pseudocode)
|
||||||
|
|
||||||
|
```
|
||||||
|
totalGrowthDays = durationDays * 0.7 // reach max by ~70% of campaign
|
||||||
|
dailyIncrement = (maxEmails - minEmails) / totalGrowthDays
|
||||||
|
|
||||||
|
for day in 1..durationDays:
|
||||||
|
target = minEmails + floor(dailyIncrement * min(day, totalGrowthDays))
|
||||||
|
target = min(target, maxEmails)
|
||||||
|
target = cap48hIncrease(previousTargets, target, 0.20)
|
||||||
|
schedule[day] = target
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example (30 days, min=1, max=40)
|
||||||
|
|
||||||
|
| Day | Planned sends |
|
||||||
|
|-----|---------------|
|
||||||
|
| 1 | 1 |
|
||||||
|
| 7 | ~8 |
|
||||||
|
| 14 | ~18 |
|
||||||
|
| 21 | ~30 |
|
||||||
|
| 24+ | 40 |
|
||||||
|
|
||||||
|
## 2. Flat
|
||||||
|
|
||||||
|
**Use when:** Mailbox already warm; maintain reputation during live outreach.
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
- Constant `maxEmailsPerDay` every day
|
||||||
|
- No ramp
|
||||||
|
- Same reply rate and rescue rules
|
||||||
|
|
||||||
|
```
|
||||||
|
schedule[day] = maxEmailsPerDay // for all days
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Randomized
|
||||||
|
|
||||||
|
**Use when:** Experimentation; avoid perfectly predictable patterns.
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
- Daily volume random within `[minEmailsPerDay, maxEmailsPerDay]`
|
||||||
|
- Weighted toward middle of range (normal distribution)
|
||||||
|
- 48h spike cap still applies
|
||||||
|
- Seed per campaign for reproducibility (debugging)
|
||||||
|
|
||||||
|
```
|
||||||
|
schedule[day] = randomInt(min, max, seed=campaignId+day)
|
||||||
|
schedule[day] = cap48hIncrease(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Custom
|
||||||
|
|
||||||
|
**Use when:** Expert operators want per-day control.
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
- User defines array: `[{ day: 1, sends: 2 }, { day: 2, sends: 5 }, ...]`
|
||||||
|
- Validate: each value within [min, max], length ≤ durationDays
|
||||||
|
- Missing days: hold last defined value or interpolate (configurable)
|
||||||
|
- UI: spreadsheet-style editor + import CSV
|
||||||
|
|
||||||
|
## Recipe selection API (future)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recipe": "grow",
|
||||||
|
"durationDays": 30,
|
||||||
|
"minEmailsPerDay": 1,
|
||||||
|
"maxEmailsPerDay": 40,
|
||||||
|
"replyRatePercent": 30
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dispatcher integration
|
||||||
|
|
||||||
|
1. Load active campaigns
|
||||||
|
2. For each campaign, read `DailySchedule` for today
|
||||||
|
3. If `actualSends < plannedSends`, schedule next send slot with jitter
|
||||||
|
4. Pick satellite from `CampaignSatellite` (weighted random)
|
||||||
|
5. Primary is always receiver for satellite→primary sends; primary may send replies back
|
||||||
|
|
||||||
|
## Post-campaign
|
||||||
|
|
||||||
|
| Mode | Behavior |
|
||||||
|
|------|----------|
|
||||||
|
| Complete | Stop sends; mark campaign `completed` |
|
||||||
|
| Maintenance | Switch to Flat at 20% of max (recommended) |
|
||||||
|
| Pause | Operator pause; resume later |
|
||||||
|
|
||||||
|
## UI copy (recommended labels)
|
||||||
|
|
||||||
|
| Recipe | Label | Subtitle |
|
||||||
|
|--------|-------|----------|
|
||||||
|
| grow | Grow | Progressive ramp — recommended for new mailboxes |
|
||||||
|
| flat | Flat | Steady volume — for already-warm inboxes |
|
||||||
|
| randomized | Randomized | Variable daily volume — for testing |
|
||||||
|
| custom | Custom | Define your own day-by-day schedule |
|
||||||
120
docs/05-primary-satellite-topology.md
Normal file
120
docs/05-primary-satellite-topology.md
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
# Primary & Satellite Topology
|
||||||
|
|
||||||
|
## Core principle
|
||||||
|
|
||||||
|
**Primaries** are the mailboxes whose reputation matters for cold outreach.
|
||||||
|
**Satellites** exist only to generate realistic, controlled engagement with primaries.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Primary │
|
||||||
|
│ hello@fillcare │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌───────────────────┼───────────────────┐
|
||||||
|
│ │ │
|
||||||
|
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
||||||
|
│Satellite│ │Satellite│ │Satellite│
|
||||||
|
│ Gmail 1 │ │ Yahoo │ │ Live │
|
||||||
|
└─────────┘ └─────────┘ └─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Traffic rules
|
||||||
|
|
||||||
|
| Direction | Allowed | Notes |
|
||||||
|
|-----------|---------|-------|
|
||||||
|
| Satellite → Primary | Yes | Main warmup send |
|
||||||
|
| Primary → Satellite | Yes | Reply to satellite (completes thread) |
|
||||||
|
| Satellite → Satellite | No | Wastes quota; looks unnatural |
|
||||||
|
| Primary → Primary | No | Never warm primaries against each other |
|
||||||
|
| Satellite → unrelated external | No | Only campaign-assigned primaries |
|
||||||
|
|
||||||
|
## Multi-primary support
|
||||||
|
|
||||||
|
One operator can run **N campaigns** concurrently:
|
||||||
|
|
||||||
|
```
|
||||||
|
Campaign A: primary=hello@fillcareapp.com ← satellites [gmail1, yahoo1, live1]
|
||||||
|
Campaign B: primary=sales@otherdomain.com ← satellites [gmail2, yahoo2]
|
||||||
|
```
|
||||||
|
|
||||||
|
Satellites may be **shared** across campaigns only if explicitly enabled (default: dedicated per primary to avoid cross-brand leakage).
|
||||||
|
|
||||||
|
## FillCare example setup
|
||||||
|
|
||||||
|
| Mailbox | Role | Provider |
|
||||||
|
|---------|------|----------|
|
||||||
|
| hello@fillcareapp.com | Primary | Google Workspace |
|
||||||
|
| personal@gmail.com | Satellite | Gmail |
|
||||||
|
| personal@yahoo.com | Satellite | Yahoo |
|
||||||
|
| personal@live.com | Satellite | Outlook |
|
||||||
|
| (+ 1–2 more) | Satellite | Mixed |
|
||||||
|
|
||||||
|
### Minimum viable pool
|
||||||
|
|
||||||
|
- 1 primary + **2 satellites** (technical minimum)
|
||||||
|
- Recommended: 1 primary + **4–6 satellites** for natural variety and reply distribution
|
||||||
|
|
||||||
|
## Satellite lifecycle
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Unassigned: Add mailbox
|
||||||
|
Unassigned --> Active: Assign to campaign
|
||||||
|
Active --> Paused: Operator pause
|
||||||
|
Paused --> Active: Resume
|
||||||
|
Active --> Error: Auth/IMAP failure
|
||||||
|
Error --> Active: Fix + test
|
||||||
|
Active --> Unassigned: Remove from campaign
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add / remove operations
|
||||||
|
|
||||||
|
### Add satellite
|
||||||
|
|
||||||
|
1. `POST /mailboxes` with `role: satellite`
|
||||||
|
2. Test SMTP/IMAP
|
||||||
|
3. `POST /campaigns/:id/satellites` to assign
|
||||||
|
|
||||||
|
### Remove satellite
|
||||||
|
|
||||||
|
1. `DELETE /campaigns/:id/satellites/:mailboxId` — stops new sends
|
||||||
|
2. In-flight threads complete or timeout
|
||||||
|
3. Optional: `DELETE /mailboxes/:id` if fully removed
|
||||||
|
|
||||||
|
### Add primary
|
||||||
|
|
||||||
|
1. `POST /mailboxes` with `role: primary`
|
||||||
|
2. `POST /campaigns` with recipe + bounds
|
||||||
|
3. Assign satellites
|
||||||
|
4. Start campaign (`status: active`)
|
||||||
|
|
||||||
|
## Peer network (phase 3 — optional)
|
||||||
|
|
||||||
|
When enabled, additional engagement from vetted peer mailboxes:
|
||||||
|
|
||||||
|
| Aspect | Own satellites | Peer network |
|
||||||
|
|--------|----------------|--------------|
|
||||||
|
| Control | Full | Limited |
|
||||||
|
| ESP risk | Lower | Higher (pool quality) |
|
||||||
|
| Cost | User supplies accounts | Platform fee |
|
||||||
|
| Detection | User responsible | Platform must maintain pool health |
|
||||||
|
|
||||||
|
**Recommendation:** Ship own-satellite-only first. Peer network requires bounce monitoring, DNS validation, and pool rotation — significant ops burden.
|
||||||
|
|
||||||
|
## Dispatcher pairing (target logic)
|
||||||
|
|
||||||
|
```
|
||||||
|
for each campaign where status=active:
|
||||||
|
if today.sends < schedule.plannedSends:
|
||||||
|
satellite = pickWeighted(campaign.satellites)
|
||||||
|
primary = campaign.primaryMailbox
|
||||||
|
queueSend(from=satellite, to=primary)
|
||||||
|
```
|
||||||
|
|
||||||
|
Reply path (rescuer):
|
||||||
|
|
||||||
|
```
|
||||||
|
if shouldReply(campaign.replyRatePercent):
|
||||||
|
reply(from=primary, to=satellite, inReplyTo=original)
|
||||||
|
```
|
||||||
91
docs/06-scheduling-and-timing.md
Normal file
91
docs/06-scheduling-and-timing.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Scheduling & Timing
|
||||||
|
|
||||||
|
## Campaign duration
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|---------|-------|
|
||||||
|
| Recommended | 30–45 days |
|
||||||
|
| Minimum | 14 days (not recommended for new domains) |
|
||||||
|
| Maximum | 45 days (MVP cap) |
|
||||||
|
|
||||||
|
`endsAt = startedAt + durationDays`
|
||||||
|
|
||||||
|
## Daily send distribution
|
||||||
|
|
||||||
|
### Send windows
|
||||||
|
|
||||||
|
Avoid off-hours sends that signal automation.
|
||||||
|
|
||||||
|
| Parameter | Default |
|
||||||
|
|-----------|---------|
|
||||||
|
| Timezone | Campaign or mailbox TZ |
|
||||||
|
| Window start | 08:00 |
|
||||||
|
| Window end | 18:00 |
|
||||||
|
| Weekends | Optional: reduce volume 50% or pause |
|
||||||
|
|
||||||
|
### Jitter between sends
|
||||||
|
|
||||||
|
| Parameter | Default | Rationale |
|
||||||
|
|-----------|---------|-----------|
|
||||||
|
| Min gap | 45 minutes | Avoid burst patterns |
|
||||||
|
| Max gap | 180 minutes | Stay within daily quota |
|
||||||
|
| Distribution | Uniform random | Human-like variance |
|
||||||
|
|
||||||
|
```
|
||||||
|
nextSendAt = lastSendAt + random(45min, 180min)
|
||||||
|
if nextSendAt outside window: roll to next window start
|
||||||
|
```
|
||||||
|
|
||||||
|
## 48-hour volume cap
|
||||||
|
|
||||||
|
Industry guidance: never increase average daily volume more than **20% in 48 hours**.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function cap48hIncrease(history: number[], proposed: number): number {
|
||||||
|
const avg48h = average(last48h(history));
|
||||||
|
return Math.min(proposed, Math.ceil(avg48h * 1.20));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Applies to all recipes except explicit expert override in Custom (with warning).
|
||||||
|
|
||||||
|
## Cron vs queue architecture
|
||||||
|
|
||||||
|
### Current (MVP)
|
||||||
|
|
||||||
|
- `node-cron` fires dispatcher every 15 min
|
||||||
|
- All eligible sends in one batch
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
| Component | Role |
|
||||||
|
|-----------|------|
|
||||||
|
| Scheduler cron | Every 5 min: enqueue due sends |
|
||||||
|
| Send queue | BullMQ / pg-boss / SQLite queue |
|
||||||
|
| Worker | One send per job with jitter |
|
||||||
|
| Rescuer cron | Every 10 min: IMAP scan per mailbox |
|
||||||
|
|
||||||
|
Benefits: precise timing, backoff, idempotency keys, pause per campaign.
|
||||||
|
|
||||||
|
## Campaign day rollover
|
||||||
|
|
||||||
|
At midnight (campaign TZ):
|
||||||
|
|
||||||
|
1. Increment `currentDay`
|
||||||
|
2. Load `DailySchedule` for new day
|
||||||
|
3. Reset `actualSends`, `actualReplies` counters
|
||||||
|
4. If `currentDay > durationDays` → complete or maintenance mode
|
||||||
|
|
||||||
|
## Maintenance mode (post-campaign)
|
||||||
|
|
||||||
|
After primary campaign completes:
|
||||||
|
|
||||||
|
- Continue Flat recipe at **20% of maxEmailsPerDay**
|
||||||
|
- Keeps engagement signals during live cold outreach
|
||||||
|
- Operator can disable
|
||||||
|
|
||||||
|
## Timezone handling
|
||||||
|
|
||||||
|
- Store all timestamps UTC in DB
|
||||||
|
- Display in campaign timezone in UI
|
||||||
|
- Send windows evaluated in campaign timezone
|
||||||
94
docs/07-reply-rate-engine.md
Normal file
94
docs/07-reply-rate-engine.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Reply Rate Engine
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Current MVP replies to **every** matched warmup email (~100%). Real humans and reputable warmup tools reply to a **fraction** of mail (typically 30%, max recommended 45%).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Parameter | Default | Min | Max recommended |
|
||||||
|
|-----------|---------|-----|-----------------|
|
||||||
|
| `replyRatePercent` | 30 | 10 | 45 |
|
||||||
|
|
||||||
|
Set per `WarmupCampaign`. Hard cap at 45% in validation.
|
||||||
|
|
||||||
|
## Semantics
|
||||||
|
|
||||||
|
**Reply rate** = of warmup emails received by the primary (or sent to primary from satellites), the percentage that receive an AI-generated reply that day.
|
||||||
|
|
||||||
|
Not the same as cold-email response rate — this is warmup engagement simulation only.
|
||||||
|
|
||||||
|
## Algorithm
|
||||||
|
|
||||||
|
### On rescuer cycle (per received warmup message)
|
||||||
|
|
||||||
|
```
|
||||||
|
function shouldReply(campaign, message, statsToday):
|
||||||
|
target = campaign.replyRatePercent
|
||||||
|
actual = statsToday.replies / max(statsToday.received, 1) * 100
|
||||||
|
|
||||||
|
if actual >= target:
|
||||||
|
return false // quota met for today
|
||||||
|
|
||||||
|
// Probabilistic: bias toward hitting target by end of day
|
||||||
|
remaining = statsToday.received - statsToday.replies
|
||||||
|
needed = ceil(statsToday.received * target/100) - statsToday.replies
|
||||||
|
probability = needed / max(remaining, 1)
|
||||||
|
|
||||||
|
return random() < probability
|
||||||
|
```
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
- Deterministic enough to converge on daily target
|
||||||
|
- Stochastic enough to avoid fixed patterns
|
||||||
|
- Never exceeds 45% even if configured wrong (clamp)
|
||||||
|
|
||||||
|
## What still happens at 0% reply (for a given message)
|
||||||
|
|
||||||
|
Even when not replying:
|
||||||
|
|
||||||
|
1. Spam rescue still runs (move to inbox, flag)
|
||||||
|
2. Mark as read
|
||||||
|
3. Log status may be `complete` without `replied` — new status: `engaged_no_reply`
|
||||||
|
|
||||||
|
### Extended WarmupStatus enum
|
||||||
|
|
||||||
|
| Status | Meaning |
|
||||||
|
|--------|---------|
|
||||||
|
| sent | Delivered from satellite |
|
||||||
|
| delivered | Confirmed in primary inbox (optional) |
|
||||||
|
| rescued_from_spam | Moved from spam folder |
|
||||||
|
| engaged_no_reply | Read/flagged, no reply sent |
|
||||||
|
| replied | Reply sent |
|
||||||
|
| complete | Thread closed |
|
||||||
|
|
||||||
|
## Reply direction
|
||||||
|
|
||||||
|
| Step | From | To |
|
||||||
|
|------|------|-----|
|
||||||
|
| 1. Warmup send | Satellite | Primary |
|
||||||
|
| 2. Reply (if sampled) | Primary | Satellite |
|
||||||
|
|
||||||
|
Maintains realistic thread: primary appears engaged with incoming mail.
|
||||||
|
|
||||||
|
## Daily reply budget
|
||||||
|
|
||||||
|
```
|
||||||
|
plannedReplies = ceil(plannedSends * replyRatePercent / 100)
|
||||||
|
```
|
||||||
|
|
||||||
|
Shown on dashboard: `replies today: 8/12 planned`
|
||||||
|
|
||||||
|
## Edge cases
|
||||||
|
|
||||||
|
| Case | Handling |
|
||||||
|
|------|----------|
|
||||||
|
| All messages arrive at 23:59 | Spread replies across rescuer cycles |
|
||||||
|
| Reply SMTP fails | Retry once; log error; don't count toward rate |
|
||||||
|
| Primary in error | Pause replies; satellites may still send |
|
||||||
|
| Satellite in error | Exclude from pool; rebalance |
|
||||||
|
|
||||||
|
## ESP rationale
|
||||||
|
|
||||||
|
100% reply rate on automated mail is a detection signal. Variable reply rate + rescue without reply on some messages mimics real mailbox behavior more closely.
|
||||||
129
docs/08-spam-rescue-and-placement.md
Normal file
129
docs/08-spam-rescue-and-placement.md
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
# Spam Rescue & Placement Tracking
|
||||||
|
|
||||||
|
## Objectives
|
||||||
|
|
||||||
|
1. Detect when warmup emails land in spam/junk
|
||||||
|
2. Rescue them (move to inbox, positive signals)
|
||||||
|
3. Track spam rate per primary for dashboard and auto-pause
|
||||||
|
4. Never leave warmup mail unhandled in spam folders
|
||||||
|
|
||||||
|
## Folder discovery
|
||||||
|
|
||||||
|
### Default folder list (try in order)
|
||||||
|
|
||||||
|
| Provider | Folders |
|
||||||
|
|----------|---------|
|
||||||
|
| Gmail | `[Gmail]/Spam` |
|
||||||
|
| Outlook | `Junk` |
|
||||||
|
| Yahoo | `Bulk` or `Spam` |
|
||||||
|
| Generic | `Spam`, `Junk` |
|
||||||
|
|
||||||
|
### Target enhancement
|
||||||
|
|
||||||
|
1. `LIST` IMAP folders on connect; match known spam patterns
|
||||||
|
2. Store per-mailbox `spamFolderPath` after first discovery
|
||||||
|
3. Provider enum shortcuts connection presets
|
||||||
|
|
||||||
|
## Rescue workflow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant R as Rescuer
|
||||||
|
participant IMAP as Primary IMAP
|
||||||
|
participant DB as Database
|
||||||
|
|
||||||
|
R->>IMAP: Open spam folder
|
||||||
|
R->>IMAP: SEARCH unseen FROM satellite
|
||||||
|
IMAP-->>R: UIDs
|
||||||
|
loop Each UID
|
||||||
|
R->>IMAP: MOVE to INBOX
|
||||||
|
R->>DB: Log placement=spam, status=rescued
|
||||||
|
R->>IMAP: FLAGS Seen, Flagged
|
||||||
|
R->>IMAP: FLAGS Important (best effort)
|
||||||
|
R->>R: shouldReply?
|
||||||
|
alt Reply sampled
|
||||||
|
R->>SMTP: Send reply
|
||||||
|
else No reply
|
||||||
|
R->>DB: status=engaged_no_reply
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Per-message matching (fix MVP weakness)
|
||||||
|
|
||||||
|
Current MVP marks all pending logs from sender when any spam found. Target:
|
||||||
|
|
||||||
|
| Priority | Match key |
|
||||||
|
|----------|-----------|
|
||||||
|
| 1 | `Message-ID` header exact match |
|
||||||
|
| 2 | `In-Reply-To` chain |
|
||||||
|
| 3 | From + normalized subject + 24h window |
|
||||||
|
|
||||||
|
One UID → one WarmupLog row.
|
||||||
|
|
||||||
|
## Placement tracking
|
||||||
|
|
||||||
|
Add to `WarmupLog` or separate `PlacementEvent`:
|
||||||
|
|
||||||
|
| Field | Values |
|
||||||
|
|-------|--------|
|
||||||
|
| placement | `inbox`, `spam`, `unknown` |
|
||||||
|
| detectedAt | timestamp |
|
||||||
|
| rescuedAt | timestamp nullable |
|
||||||
|
| folder | string |
|
||||||
|
|
||||||
|
### Inbox placement rate
|
||||||
|
|
||||||
|
```
|
||||||
|
inboxRate = 1 - (spamDetected / totalReceived)
|
||||||
|
```
|
||||||
|
|
||||||
|
Target: ≥95% inbox after week 3.
|
||||||
|
|
||||||
|
## INBOX processing (non-spam path)
|
||||||
|
|
||||||
|
Scan primary INBOX for unread warmup from assigned satellites:
|
||||||
|
|
||||||
|
- Same matching rules
|
||||||
|
- If already in inbox: flag + optional reply (no move)
|
||||||
|
- Log `placement=inbox`
|
||||||
|
|
||||||
|
## Auto-pause triggers
|
||||||
|
|
||||||
|
| Condition | Action |
|
||||||
|
|-----------|--------|
|
||||||
|
| Spam rate >30% for 3 consecutive days | Pause campaign; alert operator |
|
||||||
|
| Spam rate >50% in 24h | Immediate pause |
|
||||||
|
| Rescue failures >5 in row | Mark primary `error` |
|
||||||
|
| Auth failure | Mark `error`; stop all jobs |
|
||||||
|
|
||||||
|
## Provider-specific notes
|
||||||
|
|
||||||
|
### Gmail / Google Workspace
|
||||||
|
|
||||||
|
- `\Important` via labels (`useLabels: true`)
|
||||||
|
- Postmaster Tools integration (future): pull domain reputation
|
||||||
|
- App passwords required; OAuth preferred for SaaS
|
||||||
|
|
||||||
|
### Microsoft 365 / Outlook
|
||||||
|
|
||||||
|
- Junk Email folder naming varies
|
||||||
|
- Enable IMAP in admin settings
|
||||||
|
- SNDS integration (future)
|
||||||
|
|
||||||
|
### Yahoo
|
||||||
|
|
||||||
|
- App password required
|
||||||
|
- Bulk folder common
|
||||||
|
|
||||||
|
## Compliance considerations
|
||||||
|
|
||||||
|
Rescue actions simulate **user marking not spam** — legitimate when acting on mail the user owns. Warmbox must only access mailboxes the operator controls.
|
||||||
|
|
||||||
|
**Do not** rescue mail the operator does not own (peer network phase requires explicit consent).
|
||||||
|
|
||||||
|
## Future: seed placement tests
|
||||||
|
|
||||||
|
Optional inbox placement probes (like GlockApps) — out of MVP scope but document hook:
|
||||||
|
|
||||||
|
`POST /campaigns/:id/placement-test` → send test to probe addresses → report inbox/spam
|
||||||
111
docs/09-ai-content-policy.md
Normal file
111
docs/09-ai-content-policy.md
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
# AI Content Policy
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
All warmup email subjects and bodies must be:
|
||||||
|
|
||||||
|
- Professional B2B tone
|
||||||
|
- Contextually appropriate to `industry` (Childcare, SaaS, Finance, etc.)
|
||||||
|
- Free of spam trigger words (see blocklist)
|
||||||
|
- **Free of placeholders** — no `[Name]`, `[Your company]`, `{{variable}}`, etc.
|
||||||
|
- Plain text only (MVP) — no HTML, images, tracking pixels, or links in warmup mail
|
||||||
|
- Unique enough across sends — no identical consecutive bodies
|
||||||
|
|
||||||
|
## Forbidden patterns (auto-reject)
|
||||||
|
|
||||||
|
Regex / validation pass after generation:
|
||||||
|
|
||||||
|
```
|
||||||
|
\[.*?\] # bracket placeholders
|
||||||
|
\{\{.*?\}\} # mustache
|
||||||
|
<.*?@.*?> # angle email placeholders in body
|
||||||
|
\[Your .*?\]
|
||||||
|
\[Director .*?\]
|
||||||
|
\[Company .*?\]
|
||||||
|
INSERT .* HERE
|
||||||
|
TODO
|
||||||
|
lorem ipsum
|
||||||
|
click here
|
||||||
|
act now
|
||||||
|
limited time
|
||||||
|
free money
|
||||||
|
guarantee
|
||||||
|
```
|
||||||
|
|
||||||
|
## Spam trigger word tiers
|
||||||
|
|
||||||
|
### Tier 1 — hard block (regenerate)
|
||||||
|
|
||||||
|
`free`, `winner`, `cash`, `viagra`, `casino`, `lottery`, `act now`, `100% free`, `risk-free`
|
||||||
|
|
||||||
|
### Tier 2 — soft warning (regenerate once, then flag for review)
|
||||||
|
|
||||||
|
`urgent`, `limited`, `offer`, `discount`, `sale`, `promotion`, `buy now`
|
||||||
|
|
||||||
|
## Generation pipeline
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[AI Generate] --> B[JSON Parse]
|
||||||
|
B --> C[Placeholder scan]
|
||||||
|
C --> D[Spam word scan]
|
||||||
|
D --> E[Length check]
|
||||||
|
E --> F{Pass?}
|
||||||
|
F -->|No| G[Regenerate max 3]
|
||||||
|
F -->|Yes| H[Store + Send]
|
||||||
|
G --> A
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prompt guidelines (system)
|
||||||
|
|
||||||
|
### Initial email
|
||||||
|
|
||||||
|
- 80–150 words
|
||||||
|
- Specific but generic business topic (scheduling, follow-up, intro)
|
||||||
|
- Realistic subject ≤60 chars
|
||||||
|
- No signatures with fake phone numbers
|
||||||
|
- Sign off with first name only if name provided in mailbox profile (future)
|
||||||
|
|
||||||
|
### Reply
|
||||||
|
|
||||||
|
- 40–100 words
|
||||||
|
- Reference specific points from incoming body (quote or paraphrase)
|
||||||
|
- Natural continuation, not sales pitch
|
||||||
|
- `Re:` subject handling
|
||||||
|
|
||||||
|
## Content diversity
|
||||||
|
|
||||||
|
| Technique | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| Temperature 0.8–0.9 | Variation |
|
||||||
|
| Industry rotation | Context diversity |
|
||||||
|
| Thread memory (last 3 bodies) | Avoid repetition in replies |
|
||||||
|
| Optional spintax-free rewriting | Structural variation (phase 2) |
|
||||||
|
|
||||||
|
## Mailbox profile (future)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"senderName": "Jon",
|
||||||
|
"companyName": "FillCare",
|
||||||
|
"title": "Co-founder"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
AI uses real values instead of inventing or placeholdering.
|
||||||
|
|
||||||
|
## Failure handling
|
||||||
|
|
||||||
|
| Failure | Action |
|
||||||
|
|---------|--------|
|
||||||
|
| 3 validation failures | Skip send slot; log `ai_validation_failed` |
|
||||||
|
| API down | Retry with backoff; don't send template fallback |
|
||||||
|
| JSON parse error | Retry once with stricter prompt |
|
||||||
|
|
||||||
|
## Audit
|
||||||
|
|
||||||
|
Store `contentVersion` and `validationResult` on `WarmupLog` for debugging disputes.
|
||||||
|
|
||||||
|
## Why no links in warmup
|
||||||
|
|
||||||
|
Tracking links and URLs are strong spam signals during reputation building. Warmup content should be link-free until placement stabilizes (phase 2: optional benign links).
|
||||||
115
docs/10-statistics-and-reporting.md
Normal file
115
docs/10-statistics-and-reporting.md
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Statistics & Reporting
|
||||||
|
|
||||||
|
## Operator questions the dashboard must answer
|
||||||
|
|
||||||
|
1. How many emails are **scheduled** today for each primary?
|
||||||
|
2. How many were **sent** today?
|
||||||
|
3. How many were **replied** today?
|
||||||
|
4. How many landed in **spam** vs inbox?
|
||||||
|
5. Is warmup **on track** for the recipe?
|
||||||
|
6. Are any mailboxes in **error**?
|
||||||
|
|
||||||
|
## Per-primary campaign dashboard
|
||||||
|
|
||||||
|
### Summary cards
|
||||||
|
|
||||||
|
| Card | Source |
|
||||||
|
|------|--------|
|
||||||
|
| Campaign day | `currentDay / durationDays` |
|
||||||
|
| Today's sends | `actualSends / plannedSends` |
|
||||||
|
| Today's replies | `actualReplies / plannedReplies` |
|
||||||
|
| Inbox rate (7d) | `DailyStat` rollup |
|
||||||
|
| Spam rescues (7d) | Sum `spamRescued` |
|
||||||
|
| Status | active / paused / error |
|
||||||
|
|
||||||
|
### Time series charts (30–45 day window)
|
||||||
|
|
||||||
|
| Chart | Series |
|
||||||
|
|-------|--------|
|
||||||
|
| Volume | planned vs actual sends |
|
||||||
|
| Engagement | replies, rescues |
|
||||||
|
| Placement | inbox % vs spam % |
|
||||||
|
| Ramp progress | cumulative sends vs recipe curve |
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
|
||||||
|
| Table | Columns |
|
||||||
|
|-------|---------|
|
||||||
|
| Recent activity | time, satellite, subject, status, placement |
|
||||||
|
| Satellite health | email, status, last send, last error |
|
||||||
|
| Daily breakdown | date, sent, replied, spam, inbox rate |
|
||||||
|
|
||||||
|
## API endpoints (target)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/campaigns/:id/stats?range=7d|30d|all
|
||||||
|
GET /api/campaigns/:id/stats/today
|
||||||
|
GET /api/campaigns/:id/stats/daily?from=&to=
|
||||||
|
GET /api/mailboxes/:id/stats
|
||||||
|
GET /api/workspace/overview
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response example (`/stats/today`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"campaignId": "...",
|
||||||
|
"date": "2026-06-20",
|
||||||
|
"dayIndex": 12,
|
||||||
|
"planned": { "sends": 18, "replies": 6 },
|
||||||
|
"actual": { "sends": 15, "replies": 5, "spamDetected": 2, "spamRescued": 2 },
|
||||||
|
"rates": { "reply": 33.3, "inbox": 86.7 },
|
||||||
|
"recipe": "grow",
|
||||||
|
"onTrack": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Aggregation strategy
|
||||||
|
|
||||||
|
### Real-time counters
|
||||||
|
|
||||||
|
Update `DailySchedule.actualSends` on each successful send (transactional).
|
||||||
|
|
||||||
|
### Nightly rollup
|
||||||
|
|
||||||
|
Job at 00:05 campaign TZ:
|
||||||
|
|
||||||
|
- Finalize yesterday's `DailyStat`
|
||||||
|
- Compute 7d rolling averages
|
||||||
|
- Evaluate auto-pause rules
|
||||||
|
|
||||||
|
### Materialized vs computed
|
||||||
|
|
||||||
|
| Metric | Strategy |
|
||||||
|
|--------|----------|
|
||||||
|
| Today's counts | Real-time counters |
|
||||||
|
| Historical | `DailyStat` table |
|
||||||
|
| Inbox rate | `PlacementEvent` aggregation |
|
||||||
|
| Recipe curve overlay | Computed from `DailySchedule` |
|
||||||
|
|
||||||
|
## Export
|
||||||
|
|
||||||
|
- CSV export of daily stats
|
||||||
|
- JSON export for BI (future)
|
||||||
|
|
||||||
|
## Alerts (phase 2)
|
||||||
|
|
||||||
|
| Alert | Channel |
|
||||||
|
|-------|---------|
|
||||||
|
| Spam rate threshold | Email, webhook |
|
||||||
|
| Mailbox error | Email, in-app |
|
||||||
|
| Campaign completed | In-app |
|
||||||
|
| Ramp behind schedule | In-app |
|
||||||
|
|
||||||
|
## FillCare example view
|
||||||
|
|
||||||
|
**hello@fillcareapp.com — Day 12/30 — Grow**
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Scheduled today | 18 |
|
||||||
|
| Sent | 15 |
|
||||||
|
| Replied | 5 (33%) |
|
||||||
|
| Spam detected | 2 |
|
||||||
|
| Spam rescued | 2 |
|
||||||
|
| Inbox rate (7d) | 94% |
|
||||||
143
docs/11-esp-compliance-playbook.md
Normal file
143
docs/11-esp-compliance-playbook.md
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
# ESP Compliance Playbook
|
||||||
|
|
||||||
|
How to avoid Google, Microsoft, and Yahoo flagging Warmbox as an abusive or detectable warmup service.
|
||||||
|
|
||||||
|
## Threat model
|
||||||
|
|
||||||
|
ESPs detect warmup services via:
|
||||||
|
|
||||||
|
| Signal | Detection vector |
|
||||||
|
|--------|------------------|
|
||||||
|
| Volume spikes | Sudden send increases |
|
||||||
|
| Machine cadence | Sends every N minutes exactly |
|
||||||
|
| Identical content | Same body hash across mailboxes |
|
||||||
|
| Artificial pools | Known warmup IP/domain clusters |
|
||||||
|
| 100% engagement | Every message opened/replied |
|
||||||
|
| Bad pool hygiene | High bounce rate in network |
|
||||||
|
| Missing auth | SPF/DKIM/DMARC failures |
|
||||||
|
| Spam complaints | User marks as spam |
|
||||||
|
|
||||||
|
## Warmbox mitigations
|
||||||
|
|
||||||
|
### 1. Own satellite pool (phase 1)
|
||||||
|
|
||||||
|
User controls all mailboxes. No unknown third-party addresses. Lower pool-toxicity risk than public warmup networks.
|
||||||
|
|
||||||
|
### 2. Human-like scheduling
|
||||||
|
|
||||||
|
- Send windows (business hours)
|
||||||
|
- Random jitter 45–180 min between sends
|
||||||
|
- 48h volume cap +20%
|
||||||
|
- Weekend volume reduction (optional)
|
||||||
|
|
||||||
|
### 3. Realistic engagement
|
||||||
|
|
||||||
|
- Reply rate 30–45%, not 100%
|
||||||
|
- Some messages: rescue + read, no reply
|
||||||
|
- Varied AI content with validation
|
||||||
|
- Threaded replies with proper `In-Reply-To`
|
||||||
|
|
||||||
|
### 4. Content hygiene
|
||||||
|
|
||||||
|
- Plain text, no links/pixels in warmup
|
||||||
|
- No placeholder templates
|
||||||
|
- Industry-appropriate language
|
||||||
|
- No sales spam triggers
|
||||||
|
|
||||||
|
### 5. Authentication
|
||||||
|
|
||||||
|
Pre-flight checks before campaign start:
|
||||||
|
|
||||||
|
| Check | Required |
|
||||||
|
|-------|----------|
|
||||||
|
| SPF record | Pass or warn |
|
||||||
|
| DKIM signing | Pass or warn |
|
||||||
|
| DMARC policy | Present (p=none minimum) |
|
||||||
|
| Reverse DNS | Warn if missing |
|
||||||
|
|
||||||
|
`POST /mailboxes/:id/dns-check` (future)
|
||||||
|
|
||||||
|
### 6. Volume discipline
|
||||||
|
|
||||||
|
| Rule | Value |
|
||||||
|
|------|-------|
|
||||||
|
| Max warmup sends/day/mailbox | 50 hard cap |
|
||||||
|
| Recommended max | 40 |
|
||||||
|
| New domain age before warmup | 14 days recommended |
|
||||||
|
| Never stop warmup during cold sends | Maintenance mode 20% |
|
||||||
|
|
||||||
|
### 7. Auto-pause
|
||||||
|
|
||||||
|
| Trigger | Action |
|
||||||
|
|---------|--------|
|
||||||
|
| Bounce rate >5% | Pause campaign |
|
||||||
|
| Spam rate >30% (3 days) | Pause + alert |
|
||||||
|
| Auth errors | Mark error, stop |
|
||||||
|
| Complaint signal (future) | Immediate pause |
|
||||||
|
|
||||||
|
### 8. Peer network (phase 3 only)
|
||||||
|
|
||||||
|
If added:
|
||||||
|
|
||||||
|
- Verify DNS on every peer mailbox
|
||||||
|
- Remove bouncing addresses within 24h
|
||||||
|
- No cross-tenant data leakage
|
||||||
|
- Rate limit peer assignments
|
||||||
|
- Monitor pool spam rate aggregate
|
||||||
|
|
||||||
|
## Provider-specific guidance
|
||||||
|
|
||||||
|
### Google Workspace / Gmail
|
||||||
|
|
||||||
|
- Use app passwords or OAuth
|
||||||
|
- Enable [Postmaster Tools](https://postmaster.google.com)
|
||||||
|
- Target spam rate <0.1% (alert at 0.3%)
|
||||||
|
- Avoid connecting consumer Gmail accounts with suspicious bulk patterns
|
||||||
|
- `[Gmail]/Spam` rescue is user-owned mail — acceptable
|
||||||
|
|
||||||
|
### Microsoft 365 / Outlook
|
||||||
|
|
||||||
|
- Enable IMAP/SMTP auth
|
||||||
|
- Respect sending limits (~30/min, ~10k/day)
|
||||||
|
- Monitor SNDS (future)
|
||||||
|
- Junk folder rescue
|
||||||
|
|
||||||
|
### Yahoo
|
||||||
|
|
||||||
|
- App password required
|
||||||
|
- Lower daily limits than Google
|
||||||
|
- Watch Bulk folder
|
||||||
|
|
||||||
|
## What Warmbox is NOT
|
||||||
|
|
||||||
|
- Not a cold email sender — don't blast prospects through warmup
|
||||||
|
- Not a link tracker — no pixels in warmup phase
|
||||||
|
- Not a shared bot network — own satellites first
|
||||||
|
|
||||||
|
## Legal & ToS
|
||||||
|
|
||||||
|
Operators must:
|
||||||
|
|
||||||
|
- Own or control all connected mailboxes
|
||||||
|
- Comply with provider Terms of Service
|
||||||
|
- Not use warmup to deceive recipients (warmup is internal engagement only)
|
||||||
|
|
||||||
|
Document in Terms of Service before SaaS launch.
|
||||||
|
|
||||||
|
## Compliance checklist (pre go-live)
|
||||||
|
|
||||||
|
- [ ] SPF/DKIM/DMARC verified on primary domain
|
||||||
|
- [ ] Domain age ≥14 days
|
||||||
|
- [ ] Recipe = Grow, duration ≥30 days
|
||||||
|
- [ ] Reply rate ≤45%
|
||||||
|
- [ ] ≥4 satellites assigned
|
||||||
|
- [ ] AI validation enabled
|
||||||
|
- [ ] Send window configured
|
||||||
|
- [ ] Postmaster Tools connected (Gmail)
|
||||||
|
- [ ] Test campaign 48h with 2 sends/day before full ramp
|
||||||
|
|
||||||
|
## Rumor: "ESPs detect warmup services"
|
||||||
|
|
||||||
|
**Partially true.** ESPs detect **patterns**, not brand names. Mitigation is behavioral realism + own pool + auth + volume discipline — not hiding that warmup exists.
|
||||||
|
|
||||||
|
Warmbox should be undetectable as a **low-quality bot network**, not invisible as a product.
|
||||||
158
docs/12-architecture.md
Normal file
158
docs/12-architecture.md
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
# System Architecture
|
||||||
|
|
||||||
|
## Deployment model (hybrid)
|
||||||
|
|
||||||
|
**Phase 1–2:** Self-hosted single-tenant (FillCare) with web UI
|
||||||
|
**Phase 3+:** Multi-tenant SaaS on shared infrastructure
|
||||||
|
|
||||||
|
Design all entities with optional `workspaceId` from day one.
|
||||||
|
|
||||||
|
## High-level architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph client [Client Layer]
|
||||||
|
WebUI[Web Dashboard]
|
||||||
|
API_Consumers[API / CLI]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph app [Application Layer]
|
||||||
|
API[Express API]
|
||||||
|
Auth[Auth Middleware]
|
||||||
|
CampaignSvc[Campaign Service]
|
||||||
|
RecipeSvc[Recipe Engine]
|
||||||
|
StatsSvc[Statistics Service]
|
||||||
|
ContentSvc[AI Content Service]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph workers [Worker Layer]
|
||||||
|
Scheduler[Cron Scheduler]
|
||||||
|
SendWorker[Send Queue Worker]
|
||||||
|
RescueWorker[Rescuer Worker]
|
||||||
|
StatsWorker[Stats Rollup Worker]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph connectors [Connectors]
|
||||||
|
SMTP[nodemailer]
|
||||||
|
IMAP[imapflow]
|
||||||
|
AI[OpenRouter]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph data [Data Layer]
|
||||||
|
DB[(SQLite → Postgres)]
|
||||||
|
Queue[(Job Queue)]
|
||||||
|
end
|
||||||
|
|
||||||
|
WebUI --> API
|
||||||
|
API_Consumers --> API
|
||||||
|
API --> Auth
|
||||||
|
API --> CampaignSvc
|
||||||
|
API --> StatsSvc
|
||||||
|
Scheduler --> SendWorker
|
||||||
|
Scheduler --> RescueWorker
|
||||||
|
SendWorker --> SMTP
|
||||||
|
SendWorker --> ContentSvc
|
||||||
|
ContentSvc --> AI
|
||||||
|
RescueWorker --> IMAP
|
||||||
|
RescueWorker --> ContentSvc
|
||||||
|
workers --> DB
|
||||||
|
API --> DB
|
||||||
|
SendWorker --> Queue
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component responsibilities
|
||||||
|
|
||||||
|
| Component | Responsibility |
|
||||||
|
|-----------|----------------|
|
||||||
|
| **API** | CRUD mailboxes, campaigns, settings; stats queries |
|
||||||
|
| **Campaign Service** | Lifecycle: draft → active → paused → completed |
|
||||||
|
| **Recipe Engine** | Compute `DailySchedule` from recipe params |
|
||||||
|
| **Dispatcher / Send Worker** | Enqueue sends with jitter; role-aware pairing |
|
||||||
|
| **Rescuer Worker** | IMAP spam scan, rescue, reply sampling |
|
||||||
|
| **AI Content Service** | Generate + validate email bodies |
|
||||||
|
| **Stats Service** | Aggregations, rollups, on-track calculation |
|
||||||
|
| **Scheduler** | Cron triggers; reload on settings change |
|
||||||
|
|
||||||
|
## Data store evolution
|
||||||
|
|
||||||
|
| Phase | Database | Queue |
|
||||||
|
|-------|----------|-------|
|
||||||
|
| MVP (now) | SQLite | In-process cron |
|
||||||
|
| Phase 2 | SQLite | SQLite-backed queue or BullMQ |
|
||||||
|
| SaaS | PostgreSQL | Redis + BullMQ |
|
||||||
|
|
||||||
|
Use Prisma driver adapters; swap datasource URL for Postgres.
|
||||||
|
|
||||||
|
## Multi-tenant (future)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph tenantA [Workspace A]
|
||||||
|
A1[Primary]
|
||||||
|
A2[Satellites]
|
||||||
|
end
|
||||||
|
subgraph tenantB [Workspace B]
|
||||||
|
B1[Primary]
|
||||||
|
B2[Satellites]
|
||||||
|
end
|
||||||
|
API --> tenantA
|
||||||
|
API --> tenantB
|
||||||
|
```
|
||||||
|
|
||||||
|
- Row-level isolation via `workspaceId`
|
||||||
|
- JWT auth with workspace scope
|
||||||
|
- Encrypted credentials per workspace key (KMS in SaaS)
|
||||||
|
|
||||||
|
## Frontend architecture (recommended)
|
||||||
|
|
||||||
|
| Layer | Tech options |
|
||||||
|
|-------|----------------|
|
||||||
|
| Framework | Next.js or Vite + React |
|
||||||
|
| UI | shadcn/ui + Tailwind |
|
||||||
|
| Data | TanStack Query |
|
||||||
|
| Charts | Recharts |
|
||||||
|
| Auth | Clerk / Auth.js (SaaS) |
|
||||||
|
|
||||||
|
Serve as separate app or static build proxied by Express.
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
| Concern | Tool |
|
||||||
|
|---------|------|
|
||||||
|
| Logs | Pino (existing) |
|
||||||
|
| Errors | Sentry |
|
||||||
|
| Metrics | Prometheus / Grafana (SaaS) |
|
||||||
|
| Job tracing | Correlation ID per send |
|
||||||
|
|
||||||
|
## Security boundaries
|
||||||
|
|
||||||
|
- Credentials encrypted at rest (AES-256-GCM)
|
||||||
|
- API key (self-hosted) → JWT (SaaS)
|
||||||
|
- No plaintext passwords in logs
|
||||||
|
- IMAP/SMTP connections TLS-only
|
||||||
|
|
||||||
|
## Integration points (external)
|
||||||
|
|
||||||
|
| System | Integration | Phase |
|
||||||
|
|--------|-------------|-------|
|
||||||
|
| Instantly / Smartlead | Read-only: pause warmup when they send | 3 |
|
||||||
|
| Google Postmaster | Pull domain stats | 3 |
|
||||||
|
| Webhooks | Campaign events | 2 |
|
||||||
|
| Stripe | Billing per primary | SaaS |
|
||||||
|
|
||||||
|
## Directory structure (target)
|
||||||
|
|
||||||
|
```
|
||||||
|
warmbox/
|
||||||
|
├── apps/
|
||||||
|
│ ├── api/ # Express (current src/)
|
||||||
|
│ └── web/ # Dashboard
|
||||||
|
├── packages/
|
||||||
|
│ ├── database/ # Prisma schema
|
||||||
|
│ ├── recipe-engine/
|
||||||
|
│ └── content-validator/
|
||||||
|
├── docs/
|
||||||
|
└── prisma/
|
||||||
|
```
|
||||||
|
|
||||||
|
Monorepo optional; single package acceptable through phase 2.
|
||||||
122
docs/13-api-evolution.md
Normal file
122
docs/13-api-evolution.md
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
# API Evolution
|
||||||
|
|
||||||
|
## Current endpoints (v0)
|
||||||
|
|
||||||
|
| Method | Path |
|
||||||
|
|--------|------|
|
||||||
|
| GET | `/health` |
|
||||||
|
| POST/GET/PATCH/DELETE | `/api/inboxes` |
|
||||||
|
| POST | `/api/inboxes/:id/test` |
|
||||||
|
| GET | `/api/logs` |
|
||||||
|
| GET/PATCH | `/api/settings` |
|
||||||
|
|
||||||
|
## Target REST API (v1)
|
||||||
|
|
||||||
|
### Mailboxes
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| POST | `/api/mailboxes` | Create (role: primary \| satellite) |
|
||||||
|
| GET | `/api/mailboxes` | List with filters |
|
||||||
|
| GET | `/api/mailboxes/:id` | Detail |
|
||||||
|
| PATCH | `/api/mailboxes/:id` | Update |
|
||||||
|
| DELETE | `/api/mailboxes/:id` | Remove |
|
||||||
|
| POST | `/api/mailboxes/:id/test` | SMTP + IMAP test |
|
||||||
|
| POST | `/api/mailboxes/:id/dns-check` | SPF/DKIM/DMARC (future) |
|
||||||
|
|
||||||
|
### Campaigns
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| POST | `/api/campaigns` | Create warmup campaign |
|
||||||
|
| GET | `/api/campaigns` | List |
|
||||||
|
| GET | `/api/campaigns/:id` | Detail + stats summary |
|
||||||
|
| PATCH | `/api/campaigns/:id` | Update recipe/limits |
|
||||||
|
| POST | `/api/campaigns/:id/start` | Activate |
|
||||||
|
| POST | `/api/campaigns/:id/pause` | Pause |
|
||||||
|
| POST | `/api/campaigns/:id/complete` | End campaign |
|
||||||
|
| DELETE | `/api/campaigns/:id` | Delete draft only |
|
||||||
|
|
||||||
|
### Campaign satellites
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| GET | `/api/campaigns/:id/satellites` | List assigned |
|
||||||
|
| POST | `/api/campaigns/:id/satellites` | Assign `{ mailboxId }` |
|
||||||
|
| DELETE | `/api/campaigns/:id/satellites/:mailboxId` | Unassign |
|
||||||
|
|
||||||
|
### Statistics
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| GET | `/api/campaigns/:id/stats/today` | Today's metrics |
|
||||||
|
| GET | `/api/campaigns/:id/stats/daily` | Time series |
|
||||||
|
| GET | `/api/campaigns/:id/stats` | Summary + range |
|
||||||
|
| GET | `/api/workspace/overview` | All primaries at a glance |
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| GET | `/api/logs` | Paginated (existing, add campaignId filter) |
|
||||||
|
|
||||||
|
### Recipes
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| POST | `/api/recipes/preview` | Preview schedule without saving |
|
||||||
|
| GET | `/api/recipes/templates` | Default params per recipe type |
|
||||||
|
|
||||||
|
### Jobs (operator)
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| POST | `/api/jobs/dispatcher/run` | Manual trigger (dev) |
|
||||||
|
| POST | `/api/jobs/rescuer/run` | Manual trigger (dev) |
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| GET/PATCH | `/api/settings` | Global + AI config |
|
||||||
|
|
||||||
|
## Create campaign example
|
||||||
|
|
||||||
|
```json
|
||||||
|
POST /api/campaigns
|
||||||
|
{
|
||||||
|
"primaryMailboxId": "clx...",
|
||||||
|
"name": "FillCare hello@",
|
||||||
|
"recipe": "grow",
|
||||||
|
"durationDays": 30,
|
||||||
|
"minEmailsPerDay": 1,
|
||||||
|
"maxEmailsPerDay": 40,
|
||||||
|
"replyRatePercent": 30,
|
||||||
|
"satelliteMailboxIds": ["clx...", "clx...", "clx..."],
|
||||||
|
"timezone": "America/Vancouver",
|
||||||
|
"sendWindowStart": "08:00",
|
||||||
|
"sendWindowEnd": "18:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backward compatibility
|
||||||
|
|
||||||
|
- Keep `/api/inboxes` as alias to `/api/mailboxes` for one release
|
||||||
|
- Deprecation header on v0 routes
|
||||||
|
|
||||||
|
## Error format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Human message",
|
||||||
|
"code": "CAMPAIGN_SATELLITE_MIN",
|
||||||
|
"details": { "minRequired": 2 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auth evolution
|
||||||
|
|
||||||
|
| Phase | Auth |
|
||||||
|
|-------|------|
|
||||||
|
| Self-hosted | Optional `x-api-key` |
|
||||||
|
| SaaS | JWT + workspace header |
|
||||||
144
docs/14-frontend-ux-spec.md
Normal file
144
docs/14-frontend-ux-spec.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Frontend UX Specification
|
||||||
|
|
||||||
|
## Design principles
|
||||||
|
|
||||||
|
1. **Confidence-first** — show health, spam rate, and on-track status prominently
|
||||||
|
2. **Progressive disclosure** — simple defaults; expert options collapsed
|
||||||
|
3. **Safe defaults** — Grow, 30 days, 30% reply, max 40/day pre-selected
|
||||||
|
4. **Mobile-readable** — dashboard works on tablet; setup flows desktop-first
|
||||||
|
|
||||||
|
## Tech recommendation
|
||||||
|
|
||||||
|
- **Vite + React + TypeScript**
|
||||||
|
- **shadcn/ui** + Tailwind
|
||||||
|
- **TanStack Query** for API state
|
||||||
|
- **Recharts** for time series
|
||||||
|
|
||||||
|
## Information architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Dashboard (overview)
|
||||||
|
├── Campaigns
|
||||||
|
│ ├── [Campaign detail]
|
||||||
|
│ │ ├── Overview tab
|
||||||
|
│ │ ├── Activity log tab
|
||||||
|
│ │ ├── Satellites tab
|
||||||
|
│ │ └── Settings tab
|
||||||
|
│ └── New campaign wizard
|
||||||
|
├── Mailboxes
|
||||||
|
│ ├── All mailboxes
|
||||||
|
│ ├── Add primary
|
||||||
|
│ └── Add satellite
|
||||||
|
├── Activity (global log)
|
||||||
|
└── Settings
|
||||||
|
├── AI provider
|
||||||
|
├── Schedule defaults
|
||||||
|
└── API keys
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key screens
|
||||||
|
|
||||||
|
### 1. Dashboard (home)
|
||||||
|
|
||||||
|
**Purpose:** At-a-glance health of all warming primaries.
|
||||||
|
|
||||||
|
| Section | Content |
|
||||||
|
|---------|---------|
|
||||||
|
| Hero stats | Active campaigns, total sends today, avg inbox rate |
|
||||||
|
| Campaign cards | One card per primary: day X/Y, sends, spam %, status badge |
|
||||||
|
| Alerts | Error mailboxes, high spam rate, paused campaigns |
|
||||||
|
| Quick actions | Add primary, Add satellite, New campaign |
|
||||||
|
|
||||||
|
### 2. New campaign wizard (4 steps)
|
||||||
|
|
||||||
|
**Step 1 — Primary**
|
||||||
|
Select existing primary mailbox or add new.
|
||||||
|
|
||||||
|
**Step 2 — Recipe**
|
||||||
|
Radio cards: Grow (recommended), Flat, Randomized, Custom.
|
||||||
|
Sliders: duration 30–45, min/max sends, reply rate 30%.
|
||||||
|
|
||||||
|
**Step 3 — Satellites**
|
||||||
|
Multi-select from satellite pool; minimum 2 warning; recommend 4+.
|
||||||
|
|
||||||
|
**Step 4 — Review**
|
||||||
|
Preview ramp chart; confirm; Start campaign.
|
||||||
|
|
||||||
|
### 3. Campaign detail — Overview
|
||||||
|
|
||||||
|
| Widget | Data |
|
||||||
|
|--------|------|
|
||||||
|
| Progress ring | Day 12/30 |
|
||||||
|
| Sends today | 15/18 bar |
|
||||||
|
| Replies today | 5/6 bar |
|
||||||
|
| Inbox rate 7d | 94% with trend arrow |
|
||||||
|
| Ramp chart | Planned vs actual line chart |
|
||||||
|
| Placement | Inbox vs spam stacked bar |
|
||||||
|
|
||||||
|
Actions: Pause, Edit settings, Export CSV.
|
||||||
|
|
||||||
|
### 4. Campaign detail — Satellites
|
||||||
|
|
||||||
|
Table: email, provider, status, last activity, remove button.
|
||||||
|
Add satellite dropdown.
|
||||||
|
|
||||||
|
### 5. Campaign detail — Activity
|
||||||
|
|
||||||
|
Filterable log table (reuse `/api/logs`).
|
||||||
|
|
||||||
|
### 6. Add mailbox modal
|
||||||
|
|
||||||
|
Tabs: Primary | Satellite
|
||||||
|
Fields: email, provider preset (Gmail/Yahoo/Outlook/Custom), SMTP/IMAP auto-fill, app password, industry, test connection button.
|
||||||
|
|
||||||
|
Provider presets fill host/port from [05-primary-satellite-topology.md](./05-primary-satellite-topology.md).
|
||||||
|
|
||||||
|
### 7. Mailbox detail
|
||||||
|
|
||||||
|
Connection status, DNS check results (future), assigned campaigns, error history.
|
||||||
|
|
||||||
|
### 8. Settings
|
||||||
|
|
||||||
|
- AI: API key, model, test generation button
|
||||||
|
- Defaults: recipe, duration, reply rate
|
||||||
|
- Danger zone: reset campaign, delete mailbox
|
||||||
|
|
||||||
|
## UX patterns
|
||||||
|
|
||||||
|
| Pattern | Use |
|
||||||
|
|---------|-----|
|
||||||
|
| Status badges | active=green, paused=yellow, error=red, completed=blue |
|
||||||
|
| Empty states | Guided CTA to add first primary |
|
||||||
|
| Loading skeletons | Stats cards |
|
||||||
|
| Optimistic updates | Pause/resume |
|
||||||
|
| Toast notifications | Test connection result, save settings |
|
||||||
|
|
||||||
|
## Recipe selector UI
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ ● Grow (Recommended) │
|
||||||
|
│ Progressive ramp over 30 days │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ ○ Flat │
|
||||||
|
│ Steady volume every day │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ ○ Randomized │
|
||||||
|
│ Variable volume for testing │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ ○ Custom │
|
||||||
|
│ Expert day-by-day schedule │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
- WCAG 2.1 AA contrast on charts
|
||||||
|
- Keyboard navigation for wizards
|
||||||
|
- Screen reader labels on status badges
|
||||||
|
|
||||||
|
## MVP frontend scope (phase 2)
|
||||||
|
|
||||||
|
**In scope:** Dashboard, campaign wizard, campaign detail, mailbox CRUD, connection test, activity log.
|
||||||
|
|
||||||
|
**Out of scope:** Billing, team members, OAuth connect, mobile app.
|
||||||
73
docs/15-security-and-compliance.md
Normal file
73
docs/15-security-and-compliance.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Security & Compliance
|
||||||
|
|
||||||
|
## Credential handling
|
||||||
|
|
||||||
|
| Data | Storage | Access |
|
||||||
|
|------|---------|--------|
|
||||||
|
| Mailbox passwords | AES-256-GCM encrypted | Decrypt only in send/IMAP workers |
|
||||||
|
| AI API keys | Env or encrypted settings | Never returned in GET settings |
|
||||||
|
| API keys | Env | Header auth self-hosted |
|
||||||
|
|
||||||
|
### SaaS evolution
|
||||||
|
|
||||||
|
- Per-tenant encryption keys (KMS)
|
||||||
|
- Secrets rotation without re-entering passwords where OAuth applies
|
||||||
|
- Audit log for credential access
|
||||||
|
|
||||||
|
## Authentication roadmap
|
||||||
|
|
||||||
|
| Phase | Model |
|
||||||
|
|-------|-------|
|
||||||
|
| 1 | Optional single API key |
|
||||||
|
| 2 | Session auth for web UI |
|
||||||
|
| 3 | Multi-tenant JWT + RBAC |
|
||||||
|
|
||||||
|
### Roles (SaaS)
|
||||||
|
|
||||||
|
| Role | Permissions |
|
||||||
|
|------|-------------|
|
||||||
|
| Owner | All + billing |
|
||||||
|
| Admin | Campaigns, mailboxes |
|
||||||
|
| Viewer | Read-only stats |
|
||||||
|
|
||||||
|
## Data privacy
|
||||||
|
|
||||||
|
- Warmup email bodies may contain AI-generated business text — not PII by default
|
||||||
|
- Store minimum content needed for thread continuity
|
||||||
|
- Retention policy: 90 days for logs (configurable)
|
||||||
|
- GDPR: export + delete workspace data
|
||||||
|
|
||||||
|
## Transport security
|
||||||
|
|
||||||
|
- TLS required for SMTP (587/465) and IMAP (993)
|
||||||
|
- HTTPS for API and UI
|
||||||
|
- No credential logging in Pino output
|
||||||
|
|
||||||
|
## Abuse prevention (SaaS)
|
||||||
|
|
||||||
|
- Rate limit API
|
||||||
|
- Verify email domain ownership before warming (DNS TXT challenge)
|
||||||
|
- Block disposable email domains for signup
|
||||||
|
- Monitor outbound volume per tenant
|
||||||
|
|
||||||
|
## Legal
|
||||||
|
|
||||||
|
Before public SaaS:
|
||||||
|
|
||||||
|
- Terms of Service (user owns mailboxes, compliant use)
|
||||||
|
- Privacy Policy
|
||||||
|
- Acceptable Use Policy (no spamming prospects via warmup)
|
||||||
|
- ESP ToS disclaimer
|
||||||
|
|
||||||
|
## Incident response
|
||||||
|
|
||||||
|
| Event | Response |
|
||||||
|
|-------|----------|
|
||||||
|
| Credential leak | Rotate ENCRYPTION_KEY, force password re-entry |
|
||||||
|
| ESP suspension | Pause all campaigns for affected mailbox |
|
||||||
|
| AI generating harmful content | Block send, alert operator |
|
||||||
|
|
||||||
|
## Compliance frameworks (future)
|
||||||
|
|
||||||
|
- SOC 2 Type II (SaaS scale)
|
||||||
|
- Not required for self-hosted FillCare deployment
|
||||||
172
docs/16-roadmap.md
Normal file
172
docs/16-roadmap.md
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
# Product Roadmap
|
||||||
|
|
||||||
|
## Phase 0 — Foundation (complete)
|
||||||
|
|
||||||
|
**Status:** Done
|
||||||
|
|
||||||
|
- Barebone MVP: flat inbox pool, dispatcher, rescuer, AI, REST API
|
||||||
|
- Prisma 7 + SQLite
|
||||||
|
- OpenRouter AI integration
|
||||||
|
- Documentation scaffold (this folder)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Production warmup core (8–10 weeks)
|
||||||
|
|
||||||
|
**Goal:** Reliable multi-primary warmup with recipes, roles, and stats. Self-hosted backend only.
|
||||||
|
|
||||||
|
### Sprint 1.1 — Domain model & roles (2 weeks)
|
||||||
|
|
||||||
|
- Add `role` to mailbox (primary / satellite)
|
||||||
|
- Add `WarmupCampaign` entity
|
||||||
|
- Campaign ↔ satellite assignments
|
||||||
|
- Migrate existing inboxes
|
||||||
|
- Deprecate flat random pairing
|
||||||
|
|
||||||
|
**Deliverable:** API to create campaign with satellites; no sends yet.
|
||||||
|
|
||||||
|
### Sprint 1.2 — Recipe engine (2 weeks)
|
||||||
|
|
||||||
|
- Implement Grow, Flat, Randomized, Custom
|
||||||
|
- `DailySchedule` generation
|
||||||
|
- Duration 30–45 days, min/max validation
|
||||||
|
- 48h volume cap
|
||||||
|
- `POST /recipes/preview`
|
||||||
|
|
||||||
|
**Deliverable:** Unit-tested schedule for all 4 recipes.
|
||||||
|
|
||||||
|
### Sprint 1.3 — Campaign dispatcher (2 weeks)
|
||||||
|
|
||||||
|
- Role-aware send: satellite → primary only
|
||||||
|
- Reply path: primary → satellite
|
||||||
|
- Jitter + send windows
|
||||||
|
- Campaign day rollover
|
||||||
|
- Idempotency keys
|
||||||
|
|
||||||
|
**Deliverable:** End-to-end warmup with one primary + 4 satellites.
|
||||||
|
|
||||||
|
### Sprint 1.4 — Reply rate engine (1 week)
|
||||||
|
|
||||||
|
- Probabilistic 30% default replies
|
||||||
|
- `engaged_no_reply` status
|
||||||
|
- Daily reply budget tracking
|
||||||
|
|
||||||
|
**Deliverable:** Reply rate within ±5% of target over 7 days.
|
||||||
|
|
||||||
|
### Sprint 1.5 — Stats API (1 week)
|
||||||
|
|
||||||
|
- `DailyStat` rollups
|
||||||
|
- `/campaigns/:id/stats/today` and `/stats/daily`
|
||||||
|
- On-track calculation
|
||||||
|
|
||||||
|
**Deliverable:** JSON stats matching manual log counts.
|
||||||
|
|
||||||
|
### Sprint 1.6 — AI content validator (1 week)
|
||||||
|
|
||||||
|
- Placeholder regex rejection
|
||||||
|
- Spam word tiers
|
||||||
|
- Regenerate loop (max 3)
|
||||||
|
- Mailbox profile (name, company)
|
||||||
|
|
||||||
|
**Deliverable:** Zero placeholder emails in 100-test run.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Operator experience (6–8 weeks)
|
||||||
|
|
||||||
|
**Goal:** Web UI + hardening for FillCare daily use.
|
||||||
|
|
||||||
|
### Sprint 2.1 — Web dashboard MVP (3 weeks)
|
||||||
|
|
||||||
|
- Vite + React + shadcn
|
||||||
|
- Dashboard, campaign wizard, campaign detail
|
||||||
|
- Mailbox add/test flows
|
||||||
|
|
||||||
|
### Sprint 2.2 — Spam rescue hardening (2 weeks)
|
||||||
|
|
||||||
|
- Per-UID log matching
|
||||||
|
- Placement tracking
|
||||||
|
- Folder auto-discovery
|
||||||
|
- Auto-pause on spam thresholds
|
||||||
|
|
||||||
|
### Sprint 2.3 — DNS & health checks (1 week)
|
||||||
|
|
||||||
|
- SPF/DKIM/DMARC preflight
|
||||||
|
- Provider presets (Gmail/Yahoo/Outlook)
|
||||||
|
|
||||||
|
### Sprint 2.4 — Job queue (2 weeks)
|
||||||
|
|
||||||
|
- Replace batch cron with queued sends
|
||||||
|
- Manual job triggers for dev
|
||||||
|
- Better overlap protection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — SaaS readiness (8–12 weeks)
|
||||||
|
|
||||||
|
**Goal:** Multi-tenant, billing, optional integrations.
|
||||||
|
|
||||||
|
- Postgres migration path
|
||||||
|
- Workspace + JWT auth
|
||||||
|
- Stripe billing per primary
|
||||||
|
- Webhooks (campaign events)
|
||||||
|
- Google Postmaster integration
|
||||||
|
- Session-based web auth
|
||||||
|
- Optional peer network (beta)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — Scale & ecosystem (ongoing)
|
||||||
|
|
||||||
|
- Instantly / Smartlead sync
|
||||||
|
- Inbox placement tests
|
||||||
|
- Team roles
|
||||||
|
- White-label
|
||||||
|
- Mobile notifications
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Milestone timeline (visual)
|
||||||
|
|
||||||
|
```
|
||||||
|
2026 Q2 [Phase 1: Core backend ████████░░]
|
||||||
|
2026 Q3 [Phase 2: Web UI + hardening ░░░░████████]
|
||||||
|
2026 Q4 [Phase 3: SaaS beta ░░░░░░░░████]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FillCare-specific milestone
|
||||||
|
|
||||||
|
| Week | Milestone |
|
||||||
|
|------|-----------|
|
||||||
|
| 1 | hello@fillcareapp.com campaign created, 4 satellites assigned |
|
||||||
|
| 2 | Grow recipe running, stats visible via API |
|
||||||
|
| 4 | Web dashboard live |
|
||||||
|
| 6 | 30-day warmup complete, inbox rate ≥95% |
|
||||||
|
| 8 | Maintenance mode + cold email tool connected |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Definition of done (per phase)
|
||||||
|
|
||||||
|
### Phase 1 done when
|
||||||
|
|
||||||
|
- [ ] 2+ primaries warming concurrently
|
||||||
|
- [ ] All 4 recipes pass integration tests
|
||||||
|
- [ ] Stats API accurate
|
||||||
|
- [ ] Reply rate configurable and honored
|
||||||
|
- [ ] AI validator blocks placeholders
|
||||||
|
- [ ] ESP compliance checklist passable
|
||||||
|
|
||||||
|
### Phase 2 done when
|
||||||
|
|
||||||
|
- [ ] Operator never needs curl for daily use
|
||||||
|
- [ ] Auto-pause tested on simulated spam spike
|
||||||
|
- [ ] FillCare running 30-day campaign via UI
|
||||||
|
|
||||||
|
### Phase 3 done when
|
||||||
|
|
||||||
|
- [ ] Second customer onboarded in isolated workspace
|
||||||
|
- [ ] Billing active
|
||||||
|
- [ ] Security review complete
|
||||||
181
docs/17-agile-epics-backlog.md
Normal file
181
docs/17-agile-epics-backlog.md
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
# Agile Epics & Backlog
|
||||||
|
|
||||||
|
## Epic map
|
||||||
|
|
||||||
|
| Epic ID | Name | Phase | Priority |
|
||||||
|
|---------|------|-------|----------|
|
||||||
|
| E1 | Campaign & role model | 1 | P0 |
|
||||||
|
| E2 | Recipe engine | 1 | P0 |
|
||||||
|
| E3 | Campaign dispatcher | 1 | P0 |
|
||||||
|
| E4 | Reply rate engine | 1 | P0 |
|
||||||
|
| E5 | Statistics & reporting | 1 | P0 |
|
||||||
|
| E6 | AI content safety | 1 | P0 |
|
||||||
|
| E7 | Spam rescue v2 | 2 | P0 |
|
||||||
|
| E8 | Web dashboard | 2 | P1 |
|
||||||
|
| E9 | DNS & deliverability checks | 2 | P1 |
|
||||||
|
| E10 | Job queue | 2 | P1 |
|
||||||
|
| E11 | Multi-tenant SaaS | 3 | P2 |
|
||||||
|
| E12 | Peer network (optional) | 3 | P3 |
|
||||||
|
| E13 | External integrations | 3–4 | P3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E1 — Campaign & role model
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E1-1 | As an operator, I can mark a mailbox as primary or satellite | 3 |
|
||||||
|
| E1-2 | As an operator, I can create a warmup campaign for a primary | 5 |
|
||||||
|
| E1-3 | As an operator, I can assign/remove satellites from a campaign | 3 |
|
||||||
|
| E1-4 | As an operator, I can run multiple campaigns for different primaries | 5 |
|
||||||
|
| E1-5 | As a developer, migrate existing Inbox rows to new schema | 3 |
|
||||||
|
|
||||||
|
**Integration:** Prisma migration + `/api/campaigns` routes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E2 — Recipe engine
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E2-1 | As an operator, I can select Grow recipe with 30-day duration | 5 |
|
||||||
|
| E2-2 | As an operator, I can select Flat recipe | 3 |
|
||||||
|
| E2-3 | As an operator, I can select Randomized recipe | 5 |
|
||||||
|
| E2-4 | As an expert, I can define Custom per-day schedule | 8 |
|
||||||
|
| E2-5 | As an operator, I can preview ramp chart before starting | 3 |
|
||||||
|
| E2-6 | System enforces min 1, max 50 emails/day | 2 |
|
||||||
|
| E2-7 | System enforces 48h +20% volume cap | 5 |
|
||||||
|
|
||||||
|
**Integration:** `packages/recipe-engine` or `src/services/recipe.service.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E3 — Campaign dispatcher
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E3-1 | Satellites send only to assigned primary | 5 |
|
||||||
|
| E3-2 | Primary replies only to assigned satellites | 5 |
|
||||||
|
| E3-3 | Sends respect daily schedule from recipe | 5 |
|
||||||
|
| E3-4 | Sends distributed with jitter in send window | 5 |
|
||||||
|
| E3-5 | Campaign auto-completes after durationDays | 3 |
|
||||||
|
| E3-6 | Maintenance mode at 20% volume post-campaign | 5 |
|
||||||
|
|
||||||
|
**Integration:** Replace `dispatcher.job.ts` with campaign-aware version
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E4 — Reply rate engine
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E4-1 | Operator sets reply rate 30% (default) | 2 |
|
||||||
|
| E4-2 | System caps reply rate at 45% | 1 |
|
||||||
|
| E4-3 | Rescuer samples replies probabilistically | 8 |
|
||||||
|
| E4-4 | Non-replied messages still rescued and flagged | 3 |
|
||||||
|
| E4-5 | Dashboard shows reply rate actual vs target | 3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E5 — Statistics & reporting
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E5-1 | API returns today's planned vs actual sends | 5 |
|
||||||
|
| E5-2 | API returns daily time series for 30 days | 5 |
|
||||||
|
| E5-3 | API returns spam detected/rescued counts | 3 |
|
||||||
|
| E5-4 | API returns inbox placement rate | 5 |
|
||||||
|
| E5-5 | Nightly rollup job materializes DailyStat | 5 |
|
||||||
|
| E5-6 | Workspace overview endpoint for all primaries | 5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E6 — AI content safety
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E6-1 | Validator rejects bracket placeholders | 3 |
|
||||||
|
| E6-2 | Validator rejects tier-1 spam words | 3 |
|
||||||
|
| E6-3 | Auto-regenerate up to 3 times on failure | 3 |
|
||||||
|
| E6-4 | Mailbox profile supplies real name/company | 3 |
|
||||||
|
| E6-5 | Plain text only, no links in warmup body | 2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E7 — Spam rescue v2
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E7-1 | Match spam UID to single WarmupLog | 5 |
|
||||||
|
| E7-2 | Track placement inbox vs spam | 3 |
|
||||||
|
| E7-3 | Auto-discover spam folder per provider | 5 |
|
||||||
|
| E7-4 | Auto-pause campaign on high spam rate | 5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E8 — Web dashboard
|
||||||
|
|
||||||
|
### Stories
|
||||||
|
|
||||||
|
| ID | Story | Points |
|
||||||
|
|----|-------|--------|
|
||||||
|
| E8-1 | Dashboard shows all active campaigns | 5 |
|
||||||
|
| E8-2 | 4-step campaign wizard | 8 |
|
||||||
|
| E8-3 | Campaign detail with charts | 8 |
|
||||||
|
| E8-4 | Add mailbox with provider presets | 5 |
|
||||||
|
| E8-5 | Connection test from UI | 3 |
|
||||||
|
| E8-6 | Global activity log view | 3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration breakdown (build order)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
E1[E1 Campaign model] --> E2[E2 Recipe engine]
|
||||||
|
E2 --> E3[E3 Dispatcher]
|
||||||
|
E1 --> E4[E4 Reply rate]
|
||||||
|
E3 --> E5[E5 Statistics]
|
||||||
|
E3 --> E6[E6 AI validator]
|
||||||
|
E4 --> E7[E7 Spam rescue v2]
|
||||||
|
E5 --> E8[E8 Web UI]
|
||||||
|
E6 --> E8
|
||||||
|
E7 --> E8
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suggested first sprint (2 weeks)
|
||||||
|
|
||||||
|
1. E1-1, E1-2, E1-3 (campaign CRUD + roles)
|
||||||
|
2. E2-1, E2-6 (Grow recipe + validation)
|
||||||
|
3. E6-1 (placeholder validator)
|
||||||
|
|
||||||
|
**Sprint goal:** Create FillCare campaign via API; preview Grow schedule; no sends yet.
|
||||||
|
|
||||||
|
## Story point scale
|
||||||
|
|
||||||
|
| Points | Meaning |
|
||||||
|
|--------|---------|
|
||||||
|
| 1–2 | Few hours |
|
||||||
|
| 3 | Half day |
|
||||||
|
| 5 | 1 day |
|
||||||
|
| 8 | 2–3 days |
|
||||||
|
|
||||||
|
## Backlog hygiene
|
||||||
|
|
||||||
|
- Every story links to spec doc section
|
||||||
|
- Acceptance criteria required before sprint commit
|
||||||
|
- Demo: FillCare primary visible in stats after each epic
|
||||||
61
docs/18-open-questions.md
Normal file
61
docs/18-open-questions.md
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Open Questions
|
||||||
|
|
||||||
|
Decisions needed before or during implementation. Update this doc as answers are confirmed.
|
||||||
|
|
||||||
|
## Confirmed
|
||||||
|
|
||||||
|
| Question | Answer | Date |
|
||||||
|
|----------|--------|------|
|
||||||
|
| Deployment model | Hybrid: self-hosted first, SaaS-ready | 2026-06 |
|
||||||
|
| Satellite pool | Own satellites first; peer network optional later | 2026-06 |
|
||||||
|
| Warmup duration | 30–45 days recommended | 2026-06 |
|
||||||
|
| Reply rate default | 30%, max 45% | 2026-06 |
|
||||||
|
| Daily volume | Min 1, default max 40, cap 50 | 2026-06 |
|
||||||
|
|
||||||
|
## Product
|
||||||
|
|
||||||
|
| # | Question | Options | Impact |
|
||||||
|
|---|----------|---------|--------|
|
||||||
|
| Q1 | Can one satellite serve multiple primaries? | A) No (dedicated) B) Yes with toggle | Dispatcher pairing |
|
||||||
|
| Q2 | Should primary ever send unsolicited to satellites (not replies)? | A) No B) Yes for variety | Traffic rules |
|
||||||
|
| Q3 | Post-campaign behavior default? | A) Stop B) Maintenance 20% C) Ask user | Campaign lifecycle |
|
||||||
|
| Q4 | Weekend sending? | A) Same volume B) 50% C) Pause | Scheduling |
|
||||||
|
| Q5 | FillCare: is hello@ on Google Workspace? | Confirm SMTP/IMAP hosts | Onboarding docs |
|
||||||
|
|
||||||
|
## Technical
|
||||||
|
|
||||||
|
| # | Question | Options | Impact |
|
||||||
|
|---|----------|---------|--------|
|
||||||
|
| Q6 | Job queue tech for phase 2? | A) BullMQ+Redis B) pg-boss C) SQLite queue | Infra |
|
||||||
|
| Q7 | When to migrate SQLite → Postgres? | A) Phase 3 B) Phase 2 if UI heavy | Schema hosting |
|
||||||
|
| Q8 | OAuth for Gmail/Outlook in SaaS? | A) App passwords only B) OAuth phase 3 | Auth complexity |
|
||||||
|
| Q9 | Monorepo split api/web? | A) Yes phase 2 B) Stay single package | Repo structure |
|
||||||
|
| Q10 | Warmup content: store full body forever? | A) 90d retention B) Forever | Storage/privacy |
|
||||||
|
|
||||||
|
## Business (SaaS phase)
|
||||||
|
|
||||||
|
| # | Question | Options | Impact |
|
||||||
|
|---|----------|---------|--------|
|
||||||
|
| Q11 | Pricing model? | Per primary / per satellite / flat | Billing |
|
||||||
|
| Q12 | Free tier? | Yes limited primaries / No | GTM |
|
||||||
|
| Q13 | Brand name: Warmbox or FillCare-internal? | | Marketing |
|
||||||
|
| Q14 | Open source core? | A) Private B) OSS + hosted | License |
|
||||||
|
|
||||||
|
## Compliance
|
||||||
|
|
||||||
|
| # | Question | Options | Impact |
|
||||||
|
|---|----------|---------|--------|
|
||||||
|
| Q15 | Require DNS verification before campaign start? | A) Warn B) Block | Onboarding friction |
|
||||||
|
| Q16 | Canada (PIPEDA) data residency for SaaS? | | Hosting region |
|
||||||
|
|
||||||
|
## How to resolve
|
||||||
|
|
||||||
|
1. Add answer to **Confirmed** table
|
||||||
|
2. Update affected spec docs
|
||||||
|
3. Create implementation story in [17-agile-epics-backlog.md](./17-agile-epics-backlog.md)
|
||||||
|
|
||||||
|
## Questions for you (next conversation)
|
||||||
|
|
||||||
|
1. **Q5** — Is `hello@fillcareapp.com` on Google Workspace, Microsoft 365, or cPanel?
|
||||||
|
2. **Q1** — Should satellites be dedicated per primary, or shared across your primaries?
|
||||||
|
3. **Q3** — After 30-day warmup completes, auto-switch to maintenance mode or stop?
|
||||||
55
docs/19-risk-register.md
Normal file
55
docs/19-risk-register.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Risk Register
|
||||||
|
|
||||||
|
| ID | Risk | Likelihood | Impact | Mitigation | Owner |
|
||||||
|
|----|------|------------|--------|------------|-------|
|
||||||
|
| R1 | ESP suspends satellite Gmail account | Medium | High | Own pool, volume caps, app passwords, monitor errors | Operator |
|
||||||
|
| R2 | ESP detects warmup patterns | Medium | High | Jitter, reply sampling, content diversity, no links | Engineering |
|
||||||
|
| R3 | AI generates placeholder/spam content | Medium | High | Post-validation, regen loop, blocklists | Engineering |
|
||||||
|
| R4 | Shared peer pool toxicity (phase 3) | High | Critical | Delay peer network; bounce monitoring | Product |
|
||||||
|
| R5 | SQLite corruption / lock under load | Low | Medium | Migrate to Postgres before SaaS; queue writes | Engineering |
|
||||||
|
| R6 | Credential breach | Low | Critical | Encryption, no log leaks, KMS for SaaS | Security |
|
||||||
|
| R7 | IMAP spam folder mismatch | Medium | Medium | Auto-discovery, provider presets | Engineering |
|
||||||
|
| R8 | 100% reply rate detection | High (now) | Medium | Reply rate engine (E4) | Engineering |
|
||||||
|
| R9 | Operator sets max 50 on day 1 | Medium | High | Grow recipe default; UI warnings | Product |
|
||||||
|
| R10 | Google Postmaster spam rate >0.3% | Medium | High | Auto-pause, reduce volume, rescue | Operations |
|
||||||
|
| R11 | Legal/ToS violation (warmup as deception) | Low | High | ToS: only owned mailboxes; internal engagement | Legal |
|
||||||
|
| R12 | OpenRouter free model quality | Medium | Low | Model fallback list; paid tier option | Engineering |
|
||||||
|
| R13 | Cron overlap double-sends | Medium | Medium | Idempotency keys, job queue | Engineering |
|
||||||
|
| R14 | Primary warms too fast vs domain age | Medium | High | Domain age check; min 14-day warning | Product |
|
||||||
|
| R15 | Competitor ESP crackdown on warmup industry | Low | High | Differentiate on own-pool + self-hosted control | Product |
|
||||||
|
|
||||||
|
## Risk heat map
|
||||||
|
|
||||||
|
```
|
||||||
|
Impact
|
||||||
|
High | R4 R6 R9 R10 R11 R15
|
||||||
|
| R1 R2 R3 R8 R14
|
||||||
|
Medium | R5 R7 R13
|
||||||
|
Low | R12
|
||||||
|
+------------------
|
||||||
|
Low Med High
|
||||||
|
Likelihood
|
||||||
|
```
|
||||||
|
|
||||||
|
## Top 5 actions (before scaling FillCare)
|
||||||
|
|
||||||
|
1. **Implement E4** — reply rate sampling (addresses R8, R2)
|
||||||
|
2. **Implement E6** — AI validator (addresses R3)
|
||||||
|
3. **Implement E2** — Grow recipe enforced (addresses R9, R14)
|
||||||
|
4. **Run DNS checklist** on fillcareapp.com (addresses R10, R14)
|
||||||
|
5. **Add auto-pause** on spam rate (addresses R10, R1)
|
||||||
|
|
||||||
|
## Monitoring triggers
|
||||||
|
|
||||||
|
| Metric | Warning | Critical |
|
||||||
|
|--------|---------|----------|
|
||||||
|
| Spam placement rate | >15% 7d avg | >30% 3d avg |
|
||||||
|
| Bounce rate | >2% | >5% |
|
||||||
|
| Auth failures | 1 per mailbox | 3 consecutive |
|
||||||
|
| AI validation fail rate | >5% | >15% |
|
||||||
|
| Reply rate deviation | >±10% from target | >±20% |
|
||||||
|
|
||||||
|
## Review cadence
|
||||||
|
|
||||||
|
- Weekly during active warmup campaigns
|
||||||
|
- Update register after each sprint retro
|
||||||
52
docs/README.md
Normal file
52
docs/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Warmbox Documentation
|
||||||
|
|
||||||
|
Planning artifacts for evolving Warmbox from a barebone MVP into a production-grade cold email warmup platform and eventual SaaS.
|
||||||
|
|
||||||
|
## Decisions (confirmed)
|
||||||
|
|
||||||
|
| Decision | Choice |
|
||||||
|
|----------|--------|
|
||||||
|
| Deployment | **Hybrid** — self-hosted + web UI first; architecture ready for multi-tenant SaaS later |
|
||||||
|
| Satellite pool | **Own satellites first**; optional shared peer network in a later phase |
|
||||||
|
| Warmup duration | **30–45 days** recommended default |
|
||||||
|
| Reply rate default | **30%** (max recommended **45%**) |
|
||||||
|
| Daily volume | Min **1**, default max **40**, hard cap **50** |
|
||||||
|
|
||||||
|
## Document index
|
||||||
|
|
||||||
|
| # | Document | Purpose |
|
||||||
|
|---|----------|---------|
|
||||||
|
| 00 | [Product Brief](./00-product-brief.md) | Vision, personas, success metrics |
|
||||||
|
| 01 | [Current State & Gaps](./01-current-state-and-gaps.md) | MVP inventory vs target |
|
||||||
|
| 02 | [Glossary](./02-glossary.md) | Domain language |
|
||||||
|
| 03 | [Domain Model](./03-domain-model.md) | Future entities & relationships |
|
||||||
|
| 04 | [Warmup Recipes](./04-warmup-recipes.md) | Grow, Flat, Randomized, Custom specs |
|
||||||
|
| 05 | [Primary & Satellite Topology](./05-primary-satellite-topology.md) | Multi-primary warmup design |
|
||||||
|
| 06 | [Scheduling & Timing](./06-scheduling-and-timing.md) | Duration, windows, jitter |
|
||||||
|
| 07 | [Reply Rate Engine](./07-reply-rate-engine.md) | Probabilistic replies |
|
||||||
|
| 08 | [Spam Rescue](./08-spam-rescue-and-placement.md) | Rescue, placement tracking |
|
||||||
|
| 09 | [AI Content Policy](./09-ai-content-policy.md) | Safe B2B content rules |
|
||||||
|
| 10 | [Statistics & Reporting](./10-statistics-and-reporting.md) | Metrics & dashboards |
|
||||||
|
| 11 | [ESP Compliance Playbook](./11-esp-compliance-playbook.md) | Anti-detection, DNS, policies |
|
||||||
|
| 12 | [Architecture](./12-architecture.md) | System design (hybrid → SaaS) |
|
||||||
|
| 13 | [API Evolution](./13-api-evolution.md) | REST roadmap |
|
||||||
|
| 14 | [Frontend UX Spec](./14-frontend-ux-spec.md) | Web UI screens & flows |
|
||||||
|
| 15 | [Security & Compliance](./15-security-and-compliance.md) | Credentials, privacy, legal |
|
||||||
|
| 16 | [Roadmap](./16-roadmap.md) | Phased delivery plan |
|
||||||
|
| 17 | [Agile Epics & Backlog](./17-agile-epics-backlog.md) | Epics, stories, integrations |
|
||||||
|
| 18 | [Open Questions](./18-open-questions.md) | Unresolved decisions |
|
||||||
|
| 19 | [Risk Register](./19-risk-register.md) | Risks & mitigations |
|
||||||
|
|
||||||
|
## How to use these docs
|
||||||
|
|
||||||
|
1. **Product** — Start with `00`, `16`, `17` for prioritization.
|
||||||
|
2. **Engineering** — Use `03`–`13` as implementation specs per integration.
|
||||||
|
3. **Each sprint** — Pick one epic from `17`, cross-reference the relevant spec doc, update `18` when decisions are made.
|
||||||
|
|
||||||
|
## Agile workflow (recommended)
|
||||||
|
|
||||||
|
```
|
||||||
|
Epic (17) → Technical spec (03–13) → Implementation PR → Stats/metrics validation (10)
|
||||||
|
```
|
||||||
|
|
||||||
|
Sprints should ship **vertical slices** (e.g. "Primary inbox + Grow recipe + daily stats") rather than horizontal layers only.
|
||||||
Loading…
Reference in a new issue