# 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