warmbox/docs/06-scheduling-and-timing.md

91 lines
2.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Scheduling & Timing
## Campaign duration
| Setting | Value |
|---------|-------|
| Recommended | 3045 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