Skip to main content
Version: Next

3. Introduction

3.1 What is the Notification Framework?

The Shumoul Notification Framework is the platform-wide system responsible for composing, rendering, dispatching, retrying, and tracking every notification sent by Shumoul Cloud ERP — across six delivery channels: Email, SMS, WhatsApp, Push (mobile), SignalR (real-time web), and In-App.

It answers one question for every business event in the ERP ("a purchase invoice was posted", "a sales order was approved", "a subscription is about to expire"): who needs to know, on which channel, in which language, with what content, and what happens if delivery fails?

It is not a single project — it is a multi-repository framework:

ConcernWhere it lives
Channel-agnostic contracts, enums, DTOsShumoul.Notification.Contracts (NuGet package)
Interfaces / ports the ERP host must implementShumoul.Notification.Abstractions (NuGet package)
Dispatch engine, retry strategies, provider resolutionShumoul.Notification.Core (NuGet package)
Entities and EF Core configurationShumoul.Notification.Persistence (NuGet package)
Real-time hub support (user-ID provider, constants)Shumoul.Notification.SignalR (NuGet package)
ERP-specific entities, controllers, campaign engine, WhatsApp senderShumoul.Saas.Api (this repository)
Legacy SignalR hub + legacy CRUD controllers (pre-framework)Shumoul.Saas.MultiTenancyApi

3.2 Why was it built?

Before this framework existed, notification logic was scattered: a SignalR hub and a set of CRUD controllers in the MultiTenancyApi host, ad-hoc IMailService/ISendSMSService calls sprinkled through ERP business services, and no unified retry, dead-letter, or delivery-tracking story. Every new channel (WhatsApp, Push) was bolted on separately, with its own guard conditions and its own failure handling.

The framework consolidates this into one dispatch pipeline with:

  • A single request shape (DispatchNotificationRequest) that every ERP business service uses regardless of channel.
  • One template system (event key + channel + language → rendered subject/body).
  • One retry/backoff/dead-letter engine shared by every channel.
  • One analytics surface answering "what got delivered, what failed, and why."

3.3 Problems it solves

  • Channel fragmentation — Email, SMS, WhatsApp, Push, SignalR, and In-App previously had independent, inconsistent failure handling. Now every channel flows through the same INotificationChannelProvider dispatch contract and the same delivery-policy/retry/dead-letter engine.
  • No visibility into failuresNotificationAnalyticsController and NotificationDeliveryReceiptController give operators a queryable view of delivery rates, failure reasons, and per-provider health.
  • No administrator control without a deployNotificationEventConfigurationsController lets an administrator enable/disable a channel for a specific business event from the database, with zero code changes or redeploys.
  • Provider lock-in and outages — the provider-abstraction layer (§ Framework Packages) supports multiple providers per channel with automatic health-based failover.
  • One-off/manual bulk sendsNotificationCampaignController turns "send this to a segment of users, on a schedule, possibly multi-step" into a first-class, auditable entity instead of a one-off script.

3.4 Goals

  1. A single, typed dispatch contract for every ERP business service, regardless of destination channel.
  2. Database-driven configuration for which channels are active per event, per tenant, without a deployment.
  3. A durable retry/backoff/dead-letter pipeline shared by all channels.
  4. Real-time delivery to connected web clients via SignalR, with the same dispatch path as every other channel.
  5. First-class delivery visibility: attempts, receipts, analytics, and recent-failure surfaces.
  6. Reusable, host-independent packages (Shumoul.Notification.*) that could in principle be consumed by a different ERP host, not just Shumoul's.

3.5 Architecture philosophy

The framework follows the platform's Framework First principle (see docs/ARCHITECTURE/STANDARDS/ in this repository): generic, reusable notification infrastructure belongs in the standalone Shumoul.Saas.NotificationFramework repository and is consumed as versioned NuGet packages. The ERP host (Shumoul.Saas.Api) owns only what is genuinely ERP-specific: the campaign engine, the WhatsApp business-template sender, and a small number of adapter classes that let the framework's interfaces (IDeliveryPolicyRepository, INotificationRepository, etc.) run against the ERP's shared IRepositoryAsync.

A deliberate, documented deviation exists: the framework's own entities (AppNotification, NotificationDeliveryPolicy, NotificationRetryQueue, NotificationDeadLetter, campaign entities, etc.) physically live in Shumoul.Notification.Persistence but declare namespace Shumoul.Domain.Entities.Notifications (and DTOs declare namespace Shumoul.Application.DTOs.Notifications) so that existing ERP code can consume them without an import-path change. This is recorded as ADR-002 in docs/ARCHITECTURE/adr/ADR-002-notification-entity-base-class-deviation.md — an accepted, permanent deviation, not a transitional hack.

3.6 Golden Reference status

As of this writing, the Notification Framework has completed a Golden Reference Module extraction (2026-06-27 → 2026-06-29): all notification entities were moved out of Shumoul.Domain into the standalone Shumoul.Notification.Persistence package, a unified INotificationChannelProvider dispatch layer was introduced, and the ERP's own dispatch service was reduced to a thin pass-through adapter. Separately, every Hangfire recurring job in the notification pipeline (retry processor, retry-expire, dead-letter cleanup, campaign scheduler/executor/cleanup) was re-wrapped to run through the platform's Background Jobs Framework (job IDs now carry a -pipeline suffix; the underlying business logic classes are unchanged).

This means: treat any older phase report under docs/NOTIFICATION_FRAMEWORK_PHASE*.md as historically accurate but structurally superseded on the two points above. This documentation reflects the current, post-extraction state; where an older report and current source disagree, this documentation follows source.