Short answer: for a marketplace's weekly digest, let the scheduler identify due active customers, publish small queue messages, and let separate email and SMS workers enforce concurrency and provider pacing. A batch publisher should create work quickly; it should not wait for the whole delivery backlog. This keeps latency measurable without turning a provider limit into a duplicate-send incident.
I've been paged by missed jobs and duplicate deliveries. The uncomfortable part is that both can come from the same rushed fix: a retry that publishes again, or a worker pool enlarged until the queue looks healthy while the downstream channel starts refusing work. The useful boundary is simple: claim once, publish once per logical digest, and acknowledge only after delivery is accepted.
The failure was in the handoff, not the cron expression
Imagine Monday's digest run for active marketplace customers. The scheduler finds records whose due time has passed and claims a bounded page. Each record becomes a message with a stable digest ID, customer ID, channel, and template ID. The publisher returns. Email and SMS workers then take over. The important detail is what happens when the boundary is unclear: a database claim can succeed while the publish request times out, or the queue can accept a message while the publisher sees no response. In the first case, a record stuck in claimed must be visible to reconciliation; in the second, the retry must carry the same digest ID so a consumer can suppress the duplicate. A worker that receives the same message twice should check delivery state before sending, and a provider response such as HTTP 429 should affect pacing rather than mark the customer as permanently failed. I keep those transitions in separate fields because an on-call engineer needs to answer three different questions quickly: did we select the customer, did we enqueue the work, and did the channel accept it? Combining them into one sent boolean hides the recovery path and turns a routine timeout into an argument about whether to run the whole batch again.






