Back to Engineering Notes

Building a Campaign Delivery Pipeline

July 2026·11 min read

Creating a campaign is one action.

Delivering it is another.

From a user's perspective, clicking Send Campaign should take only a few seconds.

Behind the scenes, however, the platform may need to process hundreds or even thousands of recipients, validate delivery channels, communicate with external providers, record delivery history, and recover from transient failures.

Those responsibilities should never extend the lifetime of an HTTP request.

Instead, campaign creation became the beginning of a long-running workflow rather than the workflow itself.

The Problem

The most straightforward implementation would process every recipient immediately after the campaign request arrives.

For a small recipient list, that approach appears reasonable.

As campaigns grow, the limitations quickly become apparent.

A single request now depends on:

  • recipient validation
  • provider availability
  • network latency
  • delivery failures
  • database writes

The request remains open until every recipient has been processed.

One slow provider delays the entire operation.

One unexpected failure may require repeating work that has already completed.

Most importantly, the API response time becomes proportional to campaign size.

That relationship does not scale.

Decision 01
Accept work immediately, execute it asynchronously

Creating a campaign records the intent to send messages rather than performing message delivery inside the request lifecycle.

Background workers become responsible for completing the remaining work.


Separating Acceptance from Execution

The API has one responsibility.

Accept the campaign.

Once the request has been validated, the platform persists the campaign, creates the required background jobs, and immediately returns control to the user.

Everything that follows occurs independently.

Conceptually, the flow becomes:

Campaign Request
Persist Campaign
Queue Background Job
HTTP Response

The request completes in a predictable amount of time regardless of whether the campaign targets ten recipients or ten thousand.


Fan-Out Processing

A campaign represents a collection of deliveries.

Each recipient should progress independently.

Rather than treating a campaign as one large task, background workers expand the campaign into many smaller delivery operations.

Campaign
Recipient List
Job
Job
Job
Job
Campaign Progress

Each delivery becomes isolated.

A failure affecting one recipient no longer prevents every other message from completing successfully.

The campaign advances as individual deliveries finish.


Isolating External Providers

Message delivery depends on systems outside the platform.

Providers may throttle requests.

Temporary outages may occur.

Network communication may fail.

Rather than allowing provider behavior to influence request handling, integrations remain entirely inside background workers.

The API only records what should happen.

Workers determine how and when that work is completed.

This separation keeps user-facing performance independent from provider performance.

Decision 02
External systems should not determine API responsiveness

Third-party providers inevitably introduce unpredictable latency.

Running delivery operations in background workers prevents external dependencies from affecting request performance or user experience.


Tracking Progress

Returning immediately creates another challenge.

Users still need to know what happened.

Instead of waiting for completion, the platform continuously records campaign progress as background jobs execute.

Messages move through their own delivery lifecycle while the campaign reflects overall progress.

This allows administrators to monitor campaigns in real time without coupling progress reporting to request execution.

A campaign therefore becomes a living process rather than a completed API request.


Recovering from Failures

Large campaigns rarely complete without encountering some form of failure.

Recipients may contain invalid contact information.

Providers may temporarily reject requests.

Network interruptions may prevent delivery confirmation.

Because deliveries execute independently, failures remain localized.

Successful recipients remain successful.

Only failed deliveries require additional work.

This significantly reduces operational overhead while improving overall delivery reliability.


Scaling with Campaign Size

One of the most valuable characteristics of the pipeline is that campaign size no longer affects request complexity.

Whether a campaign targets fifty recipients or several thousand, the API performs the same work.

Validate the request.

Persist the campaign.

Queue background processing.

Return a response.

Everything else scales horizontally through workers rather than vertically through longer request lifecycles.


Lessons Learned

Campaign creation and campaign delivery are different business events.

Treating them as the same operation tightly couples user experience to infrastructure performance.

Separating acceptance from execution created a much more resilient architecture.

Users receive immediate confirmation.

Background workers process deliveries independently.

Failures remain isolated.

The platform gains visibility into long-running work without sacrificing responsiveness.


Future Improvements

The current delivery pipeline focuses on reliable asynchronous processing.

Future enhancements could include:

  • Priority queues for time-sensitive campaigns
  • Provider-aware load balancing
  • Dynamic worker scaling
  • Scheduled campaign execution
  • Multi-provider routing based on delivery health

Because delivery execution is already separated from campaign creation, these improvements can be introduced without changing the public API.


The most important lesson from this implementation was recognizing that sending a campaign is not an API request.

It is a workflow.

Once the platform modeled it that way, scalability, reliability, and operational visibility all became significantly easier to achieve.

ArchitectureDistributed SystemsBackendSystem Design