Vulnerability Management

Notification Rules and Channel Preferences That Scale

By PMAP Security Team 19 min read

A vulnerability management platform never lacks for events. Findings get created, assigned and reopened. Scans finish. SLA deadlines pass. Reports complete. Approvals wait for a decision. Each of these moments matters to someone, and almost none of them matter to everyone. The hard part of alerting is not generating the signal. It is delivering the right signal to the right person on the channel they actually watch, without burying that person under everything else.

This is where most teams quietly fail. They wire a single firehose to a single channel, then watch their people learn to ignore it. The fix is not more alerts. It is a routing model that treats delivery as a first-class concern, with rules that decide what fires, filters that decide when it fires, and preferences that decide where it lands for each individual.

This article walks through how PMAP solves that problem. We will cover the five delivery channels, the admin rule engine that drives routing, the severity filter that acts as a minimum threshold, the per-user preference matrix, the safeguards around chat and webhook destinations, and the failure isolation that keeps a bad delivery from ever breaking a finding. Everything here reflects the behavior of the PMAP notification domain, which sits downstream of the platform event bus and acts purely as a fan-out delivery engine.

For the broader picture of how alerting, live updates and timelines come together in day-to-day operations, this article is part of the real-time security operations pillar.

Why Sending Every Alert to Everyone Fails

The instinct when you stand up a new platform is to turn everything on. Every event to every channel for every user. It feels safe. Nobody can complain they were not told. In practice it produces the opposite of safety. When a notification stream contains a hundred items a day and three of them are urgent, the human reading that stream cannot reliably find the three. Volume is not visibility. A channel that is always loud is a channel that is never read.

Targeted routing inverts that logic. Instead of asking who might possibly want this, it asks who needs to act on this, and how do they prefer to hear about it. PMAP’s notification domain is built around that question. It is a fan-out delivery engine that receives events from the platform event bus and routes each one to a specific set of recipients on a specific set of channels. It does not originate events. It does not drive any other part of the system. It is a leaf domain whose only job is delivery, which means routing decisions can be reasoned about in isolation rather than tangled into the logic that produced the event.

That separation matters for scale. As the number of findings, scans and reports grows, the routing layer absorbs the growth by getting more precise, not louder. Rules narrow what fires. Severity filters raise the bar. Per-user preferences let each person tune their own inbox. The result is a stream where presence means relevance.

Five Delivery Channels in One Engine

PMAP delivers to up to five channels from one engine. Each channel suits a different rhythm of work, and the value comes from being able to mix them per event type and per person rather than picking one for the whole organization.

The five channels are:

  • In-app bell. A persistent, per-user inbox rendered as the notification bell in the top bar. Every qualifying event can land here, and it survives across sessions so nothing is lost if a tab is closed.
  • Email. HTML and plaintext messages sent over SMTP, with the template selected by event type so an SLA breach reads differently from a new finding or an approval request.
  • Webhook. A server-to-server HTTP POST carrying a JSON envelope to an admin-configured URL, for feeding downstream systems, ticketing glue or custom automation.
  • Slack. A Block Kit message posted to a Slack Incoming Webhook, delivered at the team level to the channel a squad already lives in.
  • Microsoft Teams. An Adaptive Card posted to a Teams Incoming Webhook, again team-scoped, for organizations standardized on Teams.

These channels are not mutually exclusive. A single event can light up several of them at once. An urgent finding assigned to a person might post to their team’s Slack channel, drop into their in-app bell, and send an email, while a routine scan completion lands only in the inbox. The engine evaluates each channel independently against the rules and preferences that govern it, then dispatches in parallel. The channels share one delivery pipeline but reach people where they actually pay attention.

To see how channel routing fits a working operations stack, the notifications and real-time operations datasheet lays out the engine alongside live push and the operations calendar.

The In-App Inbox as the Default Channel

The in-app bell is the channel that needs no configuration. It is the default for every event. On any qualifying event, the engine persists a notification row for each recipient and emits a real-time event so the browser updates the bell badge without polling. That live behavior is handled by the server-sent events layer, which is worth a read on its own in real-time push with server-sent events.

The inbox is more than a list. It carries read and unread state per user, supports cursor-based pagination for long histories, and can be filtered by read status and by event type so an analyst can pull up only the SLA notifications or only the approval requests. A summary endpoint returns the total and unread counts that drive the bell badge, so the count a user sees is the count of items genuinely waiting for them.

Read state is owned by the user. Marking a single item read is ownership-checked, meaning only the target user can clear their own notification, never someone else’s. A mark-all action clears every unread flag for a user in one operation. Each of these state changes also emits a real-time event, so an inbox open in two browser tabs stays consistent. Because the inbox is persistent and per-user, it is the safety net beneath every other channel. Even if an email bounces or a Slack hook is misconfigured, the in-app record is still there.

Admin Rules: Event Type, Channel and Severity Filter

The routing brain lives in the admin rule engine. Platform administrators define rules in a dedicated rules table, each one a combination of an event type, a channel, and an optional severity filter. A rule decides which channels activate for a given class of event, and it supplies the static configuration those channels need, such as a list of email recipients or a webhook URL.

This is what makes routing declarative rather than hard-coded. An administrator who wants SLA breaches emailed to a distribution list and posted to a webhook simply creates two rules, one per channel, both keyed to the SLA breach event type. Email and webhook channels are rule-driven. They activate when a matching, enabled rule exists. The email channel turns on when a rule with the email channel matches the event, and the webhook channel turns on when a rule with the webhook channel matches and carries a non-empty URL. Without a rule, those channels stay quiet for that event type. This is the lever that prevents the firehose. Nothing reaches email or webhook unless an administrator deliberately routed it there.

Rules are also kept clean by a uniqueness constraint. The rules table enforces uniqueness on the combination of event type, channel and the normalized severity filter. Attempting to create a duplicate returns a conflict response rather than silently stacking redundant rules. That constraint keeps the rule set legible. There is exactly one rule per meaningful combination, so an administrator reviewing the table sees a routing map, not a pile of overlapping entries.

Severity as a Minimum Threshold

The severity filter is the dial that controls noise. Each rule can carry an optional severity filter, and that filter is evaluated as a minimum threshold rather than an exact match. The severity order runs info < low < medium < high < critical < urgent. A rule with a severity filter set to high activates for high, critical and urgent events, and suppresses anything below high for that rule.

This is exactly the behavior a tired on-call engineer needs. They can be routed every critical and urgent finding to their phone through a webhook while never being paged for an informational item. A manager can take medium and above by email while leaving low and informational findings to live only in the in-app inbox. Because the threshold is a minimum rather than a list, the rule stays correct as new findings arrive at any severity. There is nothing to maintain when a critical comes in. It simply clears the bar.

Why Scan and Report Events Bypass the Filter

Not every event has a severity. A scan finishing or a report becoming ready is not high or low. It just happened. PMAP handles this cleanly. Events with an empty severity string bypass the severity filter and always match. The filter only suppresses events that actually carry a severity below the threshold.

This is the right default. You never want a severity filter meant for findings to accidentally swallow a scan completion or a report-ready notice, because those events are operational milestones rather than risk signals. By treating an empty severity as an automatic match, the engine keeps process notifications flowing while still letting severity thresholds govern the finding events where they belong. The filter narrows what it should, and stays out of the way where severity is not a meaningful concept.

Per-User Preference Matrix

Rules decide what the platform is willing to route. Preferences decide what each person is willing to receive. Every user has a preference matrix that lets them opt in or out of specific event and channel combinations, stored as a JSON structure on their profile. This is what turns a one-size routing policy into something each individual can tune.

The matrix is consulted on dispatch. Before a personal channel delivers, the engine checks whether the user wants that channel for that event type. If they have opted out, the delivery is suppressed for that user even when a rule would otherwise have fired. The in-app channel is the default for every event and can be suppressed by a user’s preference as well, which means even the safety-net inbox respects an individual’s explicit choices.

The matrix supports two key forms so it can evolve without breaking. There is a newer namespaced key form that encodes the event and channel together, and a set of legacy keys for established combinations such as the SLA breach email, the new finding email and the scan complete email. The engine resolves both forms, so older preferences keep working while new ones use the cleaner structure. For the team, the practical effect is that a person can shape their own stream. A remediation owner who wants every assignment by email but nothing in Slack sets it once, and the engine honors it on every dispatch from then on.

Team and Personal Chat Delivery

Chat delivery comes in two flavors, and the distinction matters. Team-channel delivery and personal delivery use the same chat platforms but answer different needs.

Team-channel Slack and Teams delivery is keyed to assignee teams. When an event carries assignee teams, the engine posts to the Slack or Teams Incoming Webhook URL stored on each of those teams, one post per assignee team per event. This runs independently of the admin rules. If a team has a chat webhook configured and is an assignee on the event, it gets the post. That independence is deliberate. Team channels are where a squad coordinates, so the engine treats reaching the responsible team as a baseline rather than something an administrator has to remember to route.

Personal chat delivery is the opposite. It is strictly opt-in, per user. A user supplies their own Slack or Teams Incoming Webhook URL in their profile, and the preference matrix gates whether each event and channel combination actually delivers. Personal delivery reaches the individual on their own terms, which is why it sits behind explicit opt-in and the same preference checks as every other personal channel. Together, the two modes cover both shapes of chat alerting. The team hears about its work in its shared channel, and an individual can additionally route specific events to their personal space.

SSRF Guarding Webhook Destinations

Any feature that posts to a user-supplied URL is a server-side request forgery risk if it is not constrained. PMAP constrains it tightly. Chat webhook destinations are validated against a host allowlist before any HTTP call is made. Microsoft Teams webhook URLs must match the Teams webhook host patterns, and Slack personal-chat URLs must point at the canonical Slack hooks host. A URL that does not match an allowed host is rejected up front, so the engine never makes an outbound request to an attacker-controlled destination.

The same discipline applies on both the team side and the personal side, with HTTPS required and the host allowlist enforced before dispatch. This matters because the webhook URL is data that flows in from a profile or a team configuration, and treating that data as trusted would be a classic SSRF mistake. By validating the host before the call, PMAP keeps the convenience of user-supplied chat hooks without opening the door to internal network probing. The OWASP SSRF Prevention Cheat Sheet lays out why an allowlist of permitted hosts is the strong control here, and PMAP applies exactly that pattern. For the webhook destinations themselves, both Slack and Microsoft Teams document their Incoming Webhook formats, which is what the engine builds its Block Kit and Adaptive Card payloads against.

Multi-Assignee and Team Fan-Out With Dedup

Real findings rarely belong to exactly one person. They get assigned to several people, or to teams, or to both. The notification engine handles this with explicit fan-out. Events that carry a list of assignee user identifiers are fanned out to every user in the list for the in-app and email channels. Events that carry assignee team identifiers are fanned out to each team’s Slack or Teams webhook.

The subtle part is deduplication. When fan-out happens across multiple assignees, the engine prevents a single user from being notified twice for the same event. Without that guard, a person who appears in an assignee list more than once, or who is reachable through overlapping paths, would get duplicate notifications and learn to trust the stream less. Deduplication keeps the promise that one event produces one notification per person per channel. Fan-out gives you breadth, reaching everyone responsible, and dedup keeps that breadth from turning into noise. The two work together so that adding more assignees scales the reach without scaling the clutter.

Approval Workflow Notifications

The approval workflow has its own notification path, because approval is a conversation with directional roles rather than a single broadcast. When an approval is requested, the engine notifies all of the approver candidates, the people who can act on the request. When the request reaches a terminal state, whether approved, rejected, cancelled or expired, it notifies the requester, the person waiting on the answer.

This split mirrors how approvals actually feel to the people in them. An approver needs to know there is a decision waiting. A requester needs to know the decision was made. Routing approval-requested events to the approver pool and routing the resolution events back to the requester means each side hears the part that concerns them, and only that part. It is a small example of the broader philosophy at work throughout the domain. Delivery is shaped to the role, not blasted to a list, so that even a workflow as branchy as approvals produces a clean, role-appropriate notification on each side.

Failure Isolation: Why a Bad Webhook Never Breaks a Finding

Notification delivery touches external systems, and external systems fail. SMTP relays time out. Slack hooks get revoked. A customer webhook endpoint returns a server error. The question that separates a robust platform from a fragile one is what happens to the original work when a delivery fails. In PMAP, the answer is nothing. The finding still saves. The scan still imports. The work that triggered the event is never held hostage by a delivery problem.

This is enforced by design. Notification handling runs in a background goroutine off the event bus rather than inline with the request that produced the event, and it uses a background context rather than the originating request context so that completing the original request never cancels the delivery. Email dispatch launches a goroutine per recipient so a single slow mailbox does not stall the others. Webhook dispatch launches a goroutine per matching rule URL. Personal and team chat each dispatch on their own goroutines with their own timeouts. The real-time bell push is fire-and-forget, so even a hiccup in the live channel never blocks the persisted notification write.

Crucially, chat and email failures are logged and swallowed. A delivery failure is recorded for operators to investigate, then absorbed so it cannot propagate back into the finding or scan flow that triggered the event. The reasoning is sound. A notification is a side effect of work, not the work itself. If a webhook is down, the right outcome is a logged failure and a working platform, not a failed finding save. This isolation is what lets the engine fan out to five channels and many recipients without ever putting the core workflow at risk. Guidance such as NIST SP 800-61 on incident communications underscores why notification has to be reliable without being load-bearing for the underlying process.

How PMAP Routes the Right Alert to the Right Person

Put the pieces together and a clear model emerges. An event leaves the bus carrying its type, its severity if it has one, and the users and teams it concerns. The notification engine evaluates that event against the admin rules to decide which channels are in play, applies the severity filter as a minimum threshold to decide whether the event clears the bar, fans out to every assignee and team with deduplication, and consults each user’s preference matrix to honor their personal choices. The in-app inbox catches everything by default, email and webhook fire where rules direct them, team chat reaches the responsible squads, and personal chat reaches individuals who opted in. SSRF guards protect every outbound chat call, and failure isolation guarantees that none of this can break the work that produced the event.

The outcome is alerting that gets sharper as the platform grows busier, because precision lives in the routing layer rather than in the volume. Rules narrow, filters raise the bar, preferences tune, and dedup cleans up. The right alert reaches the right person on the channel they watch, and everything else stays in the inbox where it belongs.

If you want to see this in a fuller operations context, the notifications and real-time operations datasheet shows the routing engine alongside live push, and the security operations calendar covers how the same deadlines and milestones surface on a shared timeline.

Frequently Asked Questions

How many notification channels does PMAP support?

PMAP delivers to up to five channels from a single engine: the in-app bell inbox, email over SMTP, server-to-server webhooks, Slack via Incoming Webhook, and Microsoft Teams via Incoming Webhook. A single event can fire to several channels at once, evaluated independently per channel against the rules and preferences that govern it.

How does the severity filter on a notification rule work?

The severity filter is a minimum threshold, not an exact match. Severity is ordered as info, low, medium, high, critical and urgent. A rule with a severity filter of high activates for high, critical and urgent events and suppresses anything below. Events with no severity, such as a scan finishing or a report becoming ready, bypass the filter and always match, so process milestones are never accidentally suppressed.

Can each user control which notifications they receive?

Yes. Every user has a preference matrix that opts them in or out of specific event and channel combinations, stored on their profile. The engine consults it on dispatch, so a personal channel is suppressed for a user who opted out even when an admin rule would otherwise have fired. The matrix supports both a newer namespaced key form and established legacy keys, so existing preferences keep working.

What is the difference between team chat delivery and personal chat delivery?

Team-channel Slack and Teams delivery posts to the webhook URL stored on each assignee team and runs independently of the admin rules, so the responsible squad is reached by default. Personal chat delivery is strictly opt-in per user, uses a webhook URL the user supplies in their own profile, and is gated by the preference matrix so it only fires for the events the individual chose.

How does PMAP prevent webhook abuse and SSRF?

Chat webhook destinations are validated against a host allowlist before any HTTP request is made. Teams URLs must match the Teams webhook host patterns and Slack personal-chat URLs must point at the canonical Slack hooks host, with HTTPS required. A URL that does not match an allowed host is rejected up front, so the engine never sends an outbound request to an attacker-controlled destination.

What happens if an email or webhook delivery fails?

Nothing happens to the underlying work. Notification handling runs in a background goroutine with its own context, separate from the request that produced the event. Email, webhook and chat dispatch each run on their own goroutines with their own timeouts, and any chat or email failure is logged and swallowed. A delivery failure can never propagate back into the finding or scan flow that triggered the event.

Will the same person ever get notified twice for one event?

No. When an event fans out across multiple assignees or overlapping paths, deduplication prevents a single user from being notified more than once for that event per channel. Fan-out provides the breadth of reaching everyone responsible, and dedup keeps that breadth from turning into duplicate noise.

author avatar
PMAP Security Team

Newsletter

Get the next writeup in your inbox

One short email when a new case writeup or detection deep dive ships. No marketing drip, no third-party tracking.