Bulk send architecture
- Validate campaign status (DRAFT or SCHEDULED).
- Resolve recipients: combine
EmailListmembers +segmentQuerytags. - Chunk recipients by
MAILGUN_BATCH_SIZE(default 500). - For each chunk, call Mailgun API; on 200, mark each
CampaignLogrowsent=true, sentAt=now(). - Between chunks,
setTimeout(batchDelayMs)(default 1500ms) — keeps under 300 msgs/sec. - Update
Campaign.totalRecipientsafter enumeration; transitionCampaign.statusto SENT when done. - Failures (4xx/5xx): log
CampaignLog.errorMessage, leave rowsent=false; retried by next scheduled run.
Per Mailgun docs, 300 msgs/sec is the safe ceiling for shared IPs; 500/batch @ 1.5s spacing (~333/s) sits just under that.