Campaign Cancellation Races: Define What Stop Sending Can Still Stop
Define campaign cancellation around the actual dispatch boundary, with worker coordination and honest counts for pending, submitted and uncertain messages.
TL;DR
- Define “stop sending” concretely and make product language match runtime behavior: state the handoff boundary the system can prevent, then enforce that promise in the backend so cancellations are honest.
- Model and coordinate the sending states and recheck permission at the external handoff to avoid large batches continuing after cancel: distinguish prepared, in‑flight, provider‑accepted and uncertain outcomes.
- Verify the cancellation boundary with targeted tests and retain an audit trail: test races at each handoff and show the documented boundary is enforced while recording request time, actor and state counts.
Define what stop sending means before adding the button
A campaign can be canceled while workers are preparing messages, waiting for a provider or recording results. “Stop sending” must describe what the system can still prevent at that moment.
An honest contract might stop new application dispatches while acknowledging that messages already accepted by the delivery provider can still arrive. The exact boundary depends on the architecture and provider capabilities. Do not promise recall unless the system genuinely supports it for the relevant state.
Write the boundary in product language first, then make the backend enforce that promise.
Model the states that matter
Distinguish candidates not yet processed, messages prepared but not dispatched, requests in flight, provider-accepted messages and uncertain outcomes. A single “sending” status hides the differences that cancellation needs to handle.
For an illustrative campaign of 1,000 recipients, cancellation may occur after 300 provider acknowledgments, with several requests in flight and the rest still pending. The system should not label all 700 remaining recipients as definitely canceled if some requests may already have crossed the handoff boundary.
The useful model separates what is known from what needs reconciliation.
Put the cancellation check near the handoff
A worker that checks campaign status only when it starts can continue sending a large batch after an operator cancels. Define where it rechecks permission to dispatch and how that check coordinates with competing workers.
A database flag by itself is not a complete concurrency design. There can be a gap between reading “active” and making the external request. Use appropriate claiming, versioning or coordination mechanisms for the architecture and document the remaining in-flight boundary.
The PostgreSQL transaction-isolation guidance is a useful reference for concurrent state handling, but an external provider call is not automatically rolled back with a database transaction.
Related reading: Email Domain Warmup: Build a Measured Sending Plan.
Handle in-flight uncertainty explicitly
If a network interruption occurs during cancellation, the application may not immediately know whether the provider accepted the message. Do not mark it safely canceled just because the local request failed to return a response.
Reconcile using the provider's supported identifiers or status mechanisms where available. Keep the uncertain state visible to operators until the evidence supports a final classification.
Avoid retrying an uncertain message blindly after cancellation. The retry policy must respect both the campaign's stopped state and the possibility that the original request already succeeded.
Make the operator message match the boundary
The interface should say whether cancellation was requested, is being applied or has completed for all preventable work. Explain that already submitted messages may still be delivered when that is the actual behavior.
For the 1,000-recipient example, a useful summary can distinguish accepted messages, prevented pending messages and unresolved in-flight requests. The counts may change as outstanding acknowledgments are reconciled.
Keep the original cancellation request visible while that reconciliation runs. An operator refreshing the page should see that the stop decision is still in force, even if a delayed provider acknowledgment updates the submitted count. A late response must not accidentally reactivate pending work.
Do not hide this transition behind a cheerful “all emails stopped” banner. Clear wording helps the operator decide whether additional customer communication or incident handling is needed.
Decide how resuming differs from restarting
If the product allows resumption, define which recipients remain eligible and whether the approved content is still valid. A stopped campaign should not automatically resend to recipients already acknowledged by the provider.
If the operator creates a replacement campaign, preserve the relationship to the original and make duplicate-risk review part of the workflow. The new campaign may have changed content or audience scope, so it is not necessarily a continuation of the old job.
Keep current preference checks in either path. Cancellation and resumption should not freeze permission indefinitely or bypass later opt-outs.
Test the exact race, not just the button
Create controlled tests where cancellation occurs before a worker claims a recipient, after preparation, immediately before dispatch and while a request is awaiting acknowledgment. Include multiple workers and a delayed response.
For each case, state the expected outcome and what evidence the system retains. Verify that canceled work is not silently reclaimed by a retry loop or another scheduled process.
A successful test should demonstrate the documented boundary. It should not merely assert that the campaign status field changed to canceled while messages continued through an unobserved path.
Keep a cancellation audit trail
Record the request time, actor, campaign revision and the resulting counts by state. Avoid placing full message bodies or unnecessary recipient data in operational logs.
When using SendDart, verify the actual campaign and delivery behavior in the deployed integration. If the application supplies additional workers or queues, those components must honor cancellation too. The complete feature is a coordinated state transition with an honest handoff limit, not just a frontend control that changes a label.
Related reading: Scheduled Email Delivery: Store the Time, Content and Cancellation State.