---
title: "When One System Does Two Jobs: Verifying the Fast Path"
canonical: https://dxdev.com/blog/2026-08-22_unified-system-multiple-patterns/
datePublished: 2026-06-29
---
At 11:36 AM on June 29, one click on **Send Domain Instructions Email** was taking the long way through our mail system.

The button sends one transactional message. A customer needs setup instructions for a domain, they click the button, and the email should be on its way. Instead, that one send was entering the same campaign mailer we use for batch work. The result was a slow button with no useful feedback while the request sat inside machinery built for a different job.

We had built one unified mailer because the common parts are genuinely common. Both campaigns and transactional emails need templates, recipient data, delivery rules, and a record of what was sent. Sharing those pieces was the right instinct. The mistake was assuming that a shared system meant every send should travel through the same route.

## The symptom was small. The route was not.

The first report was not an outage or a delivery failure. It was a slow admin action. The email eventually went out, which made the problem easy to discount. But the user experience was still wrong. A single operational message was behaving like a batch send, and the interface gave no indication that it was working.

I traced the button through the send path and found the mismatch. The action was not sending the domain-instructions template directly. It was handing a one-recipient request to the full campaign mailer.

That explained the delay without needing a more exotic theory. The campaign route exists to manage campaigns. It is the right place for a process that sends to a group. It is not the right place to make one person wait for a template they requested through an admin screen.

The useful comparison was already in the codebase. The sibling domain emails did not make this detour. They sent their templates directly. That gave me both the expected behavior and the narrower implementation. I did not need to redesign the mailer or invent a new subsystem. I needed to stop treating one message as a campaign.

## I kept the shared pieces and changed the boundary

There were three plausible fixes.

The first was to leave the transactional send in the campaign mailer and optimize that path. I rejected it because it would make the batch system carry an exception for a job it was never meant to own. The second was a priority lane inside the campaign flow. That would have been faster, but it would still preserve the wrong abstraction: one recipient would remain a special kind of campaign.

The third option was the one the sibling domain emails already used. Send the domain-instructions template directly. That is the path I implemented.

The change was deliberately small. The button now invokes the transactional template send rather than routing through the full campaign mailer. Templates and the surrounding email behavior remain shared where they should be shared. The execution path is no longer shared merely because the payload happens to be an email.

I also added a **Sending** state to the button. That did not solve the routing problem, but it solved the second problem exposed by it. Before the fix, a person could click and see no immediate evidence that the request had been accepted. Once an action has real work behind it, the interface needs to say so.

We shipped it as a hotfix and confirmed that production was serving the corrected path.

## A unified interface can hide a split in the work

The failure here was not that we reused code. It was that we merged two operating patterns under one label, then stopped checking whether the label concealed different performance requirements.

A campaign send and a transactional send may both be described as “send email.” That description is too coarse to choose an execution path. One is batch oriented. The other is an immediate response to a specific action. The user does not care that they share templates or delivery infrastructure. They care that the instructions arrive when the button says they will.

This kind of problem is easy to create in a mature system because the wrong route can still be correct enough. The message sends. The logs are not necessarily full of errors. Nothing obvious crashes. The cost shows up as latency on a path that should feel direct.

I am now treating each shared subsystem as two things: the shared contract, and the routes that implement it. Sharing a contract is valuable. Sharing every route is not. When one component supports both batch and immediate work, I want tests and production checks that exercise both patterns independently.

The code did not need a smarter campaign mailer. It needed one transactional email to stop pretending it was a campaign.
