How a templated WhatsApp carousel reaches tens of thousands of recipients: audience resolved from the warehouse, media uploaded once and cached, then sent in parallel fan-out groups - roughly a 10x throughput gain over the original loop.
Filters + selection -> recipient list, resolved from the analytics store. Users with no phone are excluded.
Each card image converted to JPEG and uploaded once; media_id cached 6h with in-flight coalescing.
One GET /message_templates per campaign instead of one per recipient.
Batches sent in groups of WA_SEND_CONCURRENCY (10) with a 100 ms pause between groups.
In-memory status map drives the banner; warehouse rows feed the historical report.
Cloud Tasks persists future sends; recipients re-resolved at fire time.
The dominant cost was redundant uploads: the same four images were re-uploaded for every recipient.
Caching the media_id removed ~95% of the runtime; parallel fan-out removed most of the rest.
| Control | Default | Purpose |
|---|---|---|
WA_SEND_CONCURRENCY | 10 | Parallel sends per fan-out group |
WA_INTER_FAN_OUT_MS | 100 | Pause between groups (headroom under Meta's limit) |
| Media cache TTL | 6h | Spans a full campaign run |
| Max download / image | 8 MB | Guards against OOM from a mis-pasted URL |
| Scheduled explicit ids | 1,500 | Cloud Tasks 100 KB body cap; larger audiences use a filter spec |
Theoretical ceiling at the defaults is 100 msg/sec - comfortably under Meta's 250 msg/sec Cloud API limit; sustained rate (~5.9/sec) is bounded by Meta's per-message latency, not the client.