warmbox/docs/sprints/sprint-1.2-1.3.md

3.1 KiB

Sprint 1.2 + 1.3 — Recipes Complete + Campaign Dispatcher

Combined sprint delivering all four warmup recipes and live campaign-aware sending.

What shipped

Sprint 1.2 — Recipe engine (complete)

  • Grow, Flat, Randomized, and Custom recipes via generateSchedule() in recipe.service.ts
  • Randomized uses a seeded PRNG (campaignId at start) plus the 48h volume cap
  • Custom validates per-day sends in [min, max] and fills gaps with hold-last
  • POST /api/recipes/preview accepts all four recipes (optional customSchedule for custom)
  • GET /api/recipes/templates returns templates for all four recipes
  • POST /api/campaigns/:id/start persists the correct schedule per recipe

Sprint 1.3 — Campaign dispatcher (complete)

  • campaign-dispatcher.job.ts — satellite → primary sends for active/maintenance campaigns
  • campaign-rollover.job.ts + syncCampaignDay() — day rollover and maintenance transition
  • send-window.ts — timezone-aware send windows (luxon)
  • lastSendAt on WarmupCampaign — 45 min minimum jitter between sends
  • Campaign-aware rescuer — primary → satellite replies with actualReplies tracking
  • Manual job endpoints: POST /api/jobs/dispatcher/run, POST /api/jobs/rollover/run
  • Legacy flat dispatcher still runs when zero campaigns exist

Traffic rules

Direction Job
Satellite → Primary Dispatcher
Primary → Satellite Rescuer (reply only)

Scheduler

Job Schedule
Campaign dispatcher dispatcher_cron setting
Legacy dispatcher Same cron (gated)
Rescuer rescuer_cron setting
Campaign rollover Hourly (0 * * * *)

Manual QA

# 1. Create primary + 2+ satellites (see sprint-1.1.md)
# 2. Create campaign (recipe: grow), assign satellites, start
# 3. Trigger dispatch:
curl -X POST http://localhost:3000/api/jobs/dispatcher/run -H "x-api-key: $API_KEY"
# 4. Check logs:
curl http://localhost:3000/api/logs?inboxId=<primary_id> -H "x-api-key: $API_KEY"
# 5. Wait for rescuer cron or trigger rescuer manually via cron interval
# 6. Repeat with recipe:flat on a second primary to verify recipe router

Preview examples

# Flat 30-day preview
curl -X POST http://localhost:3000/api/recipes/preview \
  -H "Content-Type: application/json" -H "x-api-key: $API_KEY" \
  -d '{"recipe":"flat","durationDays":30,"maxEmailsPerDay":40}'

# Custom schedule
curl -X POST http://localhost:3000/api/recipes/preview \
  -H "Content-Type: application/json" -H "x-api-key: $API_KEY" \
  -d '{"recipe":"custom","durationDays":14,"customSchedule":[{"day":1,"sends":2},{"day":2,"sends":5}]}'

Deferred to Sprint 1.4

  • Probabilistic reply rate (~30% sampling)
  • engaged_no_reply status

Key files

  • src/services/recipe.service.ts
  • src/services/campaign.service.ts
  • src/jobs/campaign-dispatcher.job.ts
  • src/jobs/campaign-rollover.job.ts
  • src/jobs/rescuer.job.ts
  • src/lib/send-window.ts