Tell the User the Work Is Pending
The two pieces before this one dealt with what queuing does inside the system: the work becomes less visible, and the failures that were loud become quiet. There’s a third consequence that lives outside the system entirely, in the interface. When the expensive part moved to a background worker, the response that comes back is no longer “this happened” — it’s “this has been accepted and will happen.” Most products keep displaying the first message anyway, because that’s what the screen said before the refactor and nobody revisited the copy.
That gap is where a specific kind of support ticket comes from. The user clicks export and sees “your export is ready” — except it isn’t, it’s queued, and for the next four minutes they’re looking at a page that promises a file that doesn’t exist. Or they change a setting, the UI confirms instantly, and the change takes effect on the next processing cycle, which is why the thing they were trying to prevent happens one more time. In each case the software is behaving as designed. What’s wrong is that the interface described a completed action when what actually occurred was an accepted request.
Fixing this is mostly a matter of saying the true thing, which is usually just as easy to say. “We’re generating your export — we’ll email you when it’s ready” sets an accurate expectation and costs nothing extra. A status that reads processing rather than complete, with a timestamp, tells someone whether to wait or to worry. And for work that can fail after the response was sent, there needs to be some path by which the user finds out — a notification, a status that transitions to failed, an email. Otherwise “accepted” is the last thing they ever hear, and a silent failure downstream becomes their problem to discover.
There’s a design decision hiding in here about which work belongs in the background at all. Anything the user is actively waiting on the result of is a poor candidate, because deferring it doesn’t remove the wait — it just moves the wait somewhere the user can’t see and can’t reason about. Deferral works best for work whose completion the user doesn’t need to observe immediately: sending a receipt, generating a report they’ll come back for, syncing something downstream. The question isn’t only “can this be async” but “will the person on the other end be able to tell what state their request is in” — and if the answer is no, the honest option is either to keep it synchronous or to build the visibility.
Which closes the thread’s arc. Queuing work is a trade, not a free win: it buys latency at the cost of observability, and the cost is paid three times over — in the metrics you now have to build, in the failures that no longer announce themselves, and in the interface that has to stop claiming things are done when they’re merely scheduled. The engineering side of that bill gets paid because incidents force it. The interface side often doesn’t, because nobody files a bug titled “the confirmation message is subtly a lie.”