3.1 KiB
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()inrecipe.service.ts - Randomized uses a seeded PRNG (
campaignIdat start) plus the 48h volume cap - Custom validates per-day sends in
[min, max]and fills gaps with hold-last POST /api/recipes/previewaccepts all four recipes (optionalcustomSchedulefor custom)GET /api/recipes/templatesreturns templates for all four recipesPOST /api/campaigns/:id/startpersists the correct schedule per recipe
Sprint 1.3 — Campaign dispatcher (complete)
campaign-dispatcher.job.ts— satellite → primary sends for active/maintenance campaignscampaign-rollover.job.ts+syncCampaignDay()— day rollover and maintenance transitionsend-window.ts— timezone-aware send windows (luxon)lastSendAtonWarmupCampaign— 45 min minimum jitter between sends- Campaign-aware rescuer — primary → satellite replies with
actualRepliestracking - 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_replystatus
Key files
src/services/recipe.service.tssrc/services/campaign.service.tssrc/jobs/campaign-dispatcher.job.tssrc/jobs/campaign-rollover.job.tssrc/jobs/rescuer.job.tssrc/lib/send-window.ts