Picture a bank that must send customers certain notices and statements. A background job is supposed to generate and send them. One day it quietly stops for some of them, and nobody notices. When someone finally does, thousands of letters have to be recreated and sent in batches, and the regulator is involved. It isn't a coding chore anymore. It's a compliance problem.
Nobody clicked the wrong button. The work simply never happened, and nothing showed that it was still owed. That is the work nobody clicks. Insurance has the same kind of work, and that's where my example comes from.
In "One slice, end to end" I traced a state change: a policyholder cancels a life policy during its cooling-off period. In Read models are cheap. Coupling isn't. I showed the state views that carry those facts to a screen, one read model per use case. Both are about things a person does or looks at. This time it's about the work that happens after the click, when nobody is clicking at all.
Work nobody clicks
Once PolicyCancelledInCoolingOff is recorded (the cooling-off cancellation from One slice, end to end), the premium the policyholder paid is owed back. Nobody has to press a button for that. The system has an open task.
In an Event Model, that task is an automation. It's the third of the four building blocks, and it's built from parts we already know. The open work is a state view, a todo list. A processor, drawn as a gear, picks up a row and sends a command. The command produces an event, and that event takes the row off the list.

A todo list drives the processor: the cancellation puts a refund on the list, and the issued refund takes it off.
The list drives the work
On the left is the todo list, RefundsToIssue. PolicyCancelledInCoolingOff adds a row. CoolingOffRefundIssued removes it. Every field on the row comes from an event, so the amount to refund is the premium that was actually collected, not something the processor works out.
At the top is the automation. The processor takes an open row and sends IssueCoolingOffRefund. That's all it does. The rules, such as "a refund can only be issued once", live in the command handler, which decides only from past events. The scenarios read just like the ones for a state change: given the policy was cancelled and the premium collected, when the refund is issued, then CoolingOffRefundIssued. Given it was already refunded, the same command is rejected.
So the processor doesn't react to the cancellation. It works through the list. If it was down for an hour, the list still shows exactly what is owed.
One shape, different triggers
What changes from one automation to the next is only what makes a row due.
- A deadline. When a policy is issued, it gets a row on a list of cooling-off windows that are still open, each with its own end date. A processor picks up the rows whose date has passed and records
CoolingOffEnded. A cancellation closes the row before that happens. - A schedule. On the first of the month, a scheduled job records
BillingRunStartedfor that period. That's a fact on the timeline, so later anyone can see that December was billed. A todo list then gets one row per policy to bill, and the processor works through them one by one. - An outside system. Issuing the refund puts a row on a second list, payouts to send, and its processor calls the bank. That call sits only in the processor, never in the command handler. The processor records what happened as data: submitted, failed, or rejected. The bank's later answer comes in as an outside event and is translated into one of ours.
Each one is a list, a processor and a command with its event. Once you've seen one, you can read them all.
When things fail
My first question when we modelled this was: what about a poison item, one payout that always fails? Are the others stuck behind it?
They aren't, because a todo list isn't a queue. Every row stands on its own, so the processor skips the one that fails and keeps going with the rest.
The failure itself becomes a fact. A failed attempt records an event with the reason and the attempt number. The row stays on the list and waits a while before the next try. After the third failed attempt, the automation records an escalation instead. That closes the row on the automated list and opens one on a second list, the payouts needing attention, which feeds a back-office screen. A person fixes the cause, for example a closed bank account, and retries.

A failed bank call is an event: the list retries it, and the third failure escalates to a person.
A business rejection is different. If the bank rejects the payment, for example because the IBAN is invalid, retrying won't help. That outcome is final for the automation, so it never retries it on its own. The payout goes straight to the needs-attention list, where a person can correct the account and send it again.
Nothing is hidden in a retry setting somewhere in the infrastructure. Every attempt, every escalation and every manual fix is on the timeline, and a business expert can read the whole story of one payout.
Explicit and simple
Two things guide how I model this. First: "I like to be explicit for many reasons, not the least for auditing and 'explainability' purposes". That's why the billing run is an event and why every failed attempt is one. Years later, someone can ask why a premium was collected, or why a refund was paid when it was, and the answer is on the timeline.
Second: "I like 'simple' as it results in less (logical) errors". An automation that does one thing is easy to check. Its scenarios can cover it completely. I'd rather have several small slices, each with its own list, than one clever process that does everything.
That's also why every automation in my models shows its todo list. If you can see the list, you can see what's owed, what's stuck and what a person has to look at.
How do you find out today what your system still owes your customers?
Originally published on LinkedIn on 1 Nov 2026: https://www.linkedin.com/pulse/work-nobody-clicks-gabriel-n-schenker-yhzge/