
The Umbraco Notification Pipeline & Clean Domain Events: Architecting Decoupled Solutions
At some point every Umbraco developer hits the problem where you have business logic that needs to run when a page is published, when a product is published or when a form is submitted. The perfect place for that logic is in a notification handler. It works. It ships. And six months later, that handler has turned into a dumping ground for email sends, cache invalidations, search index updates, and three "quick" if-statements nobody wants to touch.
The notification pipeline itself isn't the problem. It's one of the most useful things Umbraco gives you - a clean, event-driven hook into the content lifecycle. The problem is what happens when business logic gets welded directly to it, instead of being kept a step removed.
This post walks through how to use Umbraco's notification system the way it's meant to be used, and how to layer clean domain events on top of it so your business logic doesn't live and die with your CMS internals.
What the Notification Pipeline Actually Gives You
Umbraco triggers notifications at nearly every significant step of the content lifecycle: save, publish, move, copy, delete, and more. Handlers can choose which notifications they want to subscribe to and execute code when they are triggered.
- Most events are paired: a -ing event before it happens and a -ed event after it has occurred.
- The "-ing" notifications are cancelable, meaning a handler can stop the operation before it happens
- The "-ed" notifications are purely informational, the action already happened, and handlers just react to it
- Handlers can be registered to run synchronously or asynchronously, depending on whether the work is quick or I/O-heavy
- Everything gets wired up in one central place, so you always know where to look for what's listening
That last point is the real strength here: a single, predictable extension point instead of scattered event hooks across the codebase.
The Coupling Trap Most Teams Fall Into
The trouble starts when a handler stops being a notification handler and starts being a business logic engine.
- The handler reaches directly into content properties and Umbraco-specific types to make decisions
- Business rules end up buried inside infrastructure code, instead of living somewhere they can be found and reasoned about
- Testing means spinning up Umbraco's content services just to verify a simple rule
- The same logic can't be reused anywhere outside the CMS context, even when the business rule has nothing to do with Umbraco
- One handler quietly grows five responsibilities, and nobody notices until it breaks
None of this is Umbraco's fault. It's what happens when a framework's extension point becomes the home for logic that should live somewhere else entirely.
Translating CMS Notifications into Domain Events
The fix is a thin translation layer. Instead of letting a notification handler make business decisions, its only job becomes converting a CMS-specific notification into a domain event your application actually understands.
- A ContentPublishedNotification for a product page becomes something like ProductPageWentLive
- The domain event carries plain, simple data - IDs, names, values - never Umbraco types like IPublishedContent or IContent
- The notification handler's entire responsibility is translation: catch the notification, build the domain event, hand it off
- No decisions, no side effects, no business rules inside the handler itself
- If Umbraco changes how a notification is structured in a future version, only the translation layer needs to change, not your business logic
This one shift is what separates "code that happens to run inside a CMS" from "a real application that happens to use a CMS for content."
Where the Real Business Logic Should Live
Once a domain event exists, it needs somewhere to go. That's where a dispatcher sometimes called a mediator, comes in, sitting completely outside Umbraco-specific code.
- The dispatcher receives the domain event and routes it to whichever handlers care about it
- Each domain event handler does exactly one thing: send a notification email, update a search index, invalidate a cache, ping a Slack channel
- Handlers can be tested in complete isolation, with no Umbraco context required at all
- Adding a new reaction to an event means adding a new handler, not editing an existing one
- Removing a feature means deleting a handler, not untangling it from three others
This is the payoff: business logic that's organized around what the business actually cares about, not around which Umbraco notification happened to trigger it.
Keeping It Async Without Breaking the Save
Content editors notice when saving or publishing suddenly feels slow. That's usually a sign that something I/O-heavy, an API call, a search reindex, an email send is sitting inside a synchronous handler and blocking the save pipeline.
- Use asynchronous handlers for anything that involves network calls or external services
- Push genuinely slow work, full reindexing, third-party syncs onto a background job instead of running it inline
- Reserve cancelable notifications for cases where you truly need to stop an action, not as a place to run cleanup logic
- Keep synchronous handlers fast and boring; if a handler is doing real work, it probably shouldn't be synchronous
- Log failures at the boundary rather than letting a failed side effect silently swallow an editor's save
Best Practices
- Keep notification handlers thin, they translate, they don't decide
- Name domain events after what happened in the business, not what happened in Umbraco
- Never let IPublishedContent or IContent leak into your domain or application layer
- One handler, one responsibility resists the urge to combine reactions "since you're already in there"
- Test domain event handlers on their own, with the dispatcher mocked out
- Revisit old handlers periodically, they tend to accumulate responsibilities quietly over time
Conclusion
Umbraco's notification pipeline is genuinely well designed. The issue was never the tool, it's how easy it is to let business logic quietly move in and take up permanent residence inside it.
Separating CMS notifications from domain events isn't extra ceremony for its own sake. It's what maintains a decoupled codebase, enables you to reuse your business rules outside of the CMS, and prevents upgrades to future versions of Umbraco from turning into a safari hunt of hall-of-mirrors handlers. The translation layer is a modest investment that pays for itself every single time a new requirement land.
If you're maintaining an Umbraco solution where handlers have started to sprawl, this is usually one of the highest leverage refactors available and one that gets easier to justify the longer it's put off.
Related Blogs

Most enterprises don't run one website. They have portfolios, regional stores,…
Read More
Umbraco makes it easy to ship fast. It also makes it easy to end up with business logic…
Read More
Most agencies won't give you a straight answer. They'll say, "it depends" and leave you…
Read More