Home / admin / custom-notification-type-design
custom-notification-type-design
Use when designing Custom Notification Types that fire via Flow, Apex, or Process Builder to Lightning bell, desktop, mobile (push), and Slack. Covers channel enablement, targeting, deliverability, consent, and anti-spam discipline. NOT for actually sending one from Flow or Apex, or the recipient limit — use admin/custom-notification-types. NOT for email templates and alerts — use admin/email-templates-and-alerts.
Open the skill folder on GitHub
Custom Notification Type Design
A Custom Notification Type is the definition of a notification: its label, its API name, and which delivery channels it is allowed to use. It is not the send. Designing one badly is cheap, invisible, and permanent — the notification still "works" in the sense that send() returns without an exception, and the user still never sees it, or sees it forty times a day and mutes the whole channel.
Two failure shapes dominate, and they need opposite fixes:
| Symptom | Actual cause | Where it is designed away |
|---|---|---|
| "The notification never arrives" | One of four independent delivery gates is closed | The delivery chain, below |
| "Users muted the bell / uninstalled the app" | Every event became a notification | Targeting, throttling, and the registry |
Scope. This skill owns the design and governance decisions: which channels a type should declare, who should receive it, how often, what the deep link opens, and how the org keeps the set of notification types from sprawling. The mechanics of the send — Messaging.CustomNotification, the Flow customNotificationAction, the 500-value recipient cap, bulk send patterns — belong to admin/custom-notification-types. Those appear below only where they constrain a design choice. Email alerts are admin/email-templates-and-alerts.
Before Starting
- Name the action the recipient will take. If the honest answer is "be
aware", this is a report subscription, a dashboard, or a Chatter post — not a notification. Write the action down; it becomes the review criterion later.
- Decide who cannot act. Notifications to people without permission to do
the thing are the fastest route to mute. The recipient set is the design, not an afterthought.
- Find out whether the org already has a type that covers this. A custom
notification type is org-wide and cheap to create, which is exactly why orgs accumulate dozens of near-duplicates that fragment the user's mental model.
- Establish the channel budget. Bell and desktop are one checkbox
(
desktop); mobile push is a separate one (mobile). Slack is a separate notification type entirely — see below. Every channel you enable is a channel you have to justify at review.
Core Concepts
The delivery chain has four independent gates
A notification reaches a human only if all four of these are open. Three of them are outside the sending code, which is why "the Apex ran and nobody got anything" is the single most common support ticket in this area.
1. Type definition CustomNotificationType.desktop / .mobile
(Setup → Notification Builder → Notification Types)
↓
2. Org delivery settings NotificationTypeConfig → notificationChannels
{ desktopEnabled, mobileEnabled, slackEnabled }
and appSettings → { connectedAppName, enabled }
(Setup → Notification Delivery Settings)
↓
3. User preference The recipient's own notification settings
↓
4. Device / browser Mobile app installed with push permission granted;
browser notification permission granted
Gate 2 is the one nobody remembers. NotificationTypeConfig carries an appSettings array of { connectedAppName, enabled } — per connected app. Mobile push does not reach a device because "mobile is checked on the type"; it reaches a device because a connected app (the Salesforce mobile app, a Field Service app, a branded app built with the Mobile SDK) is enabled for that notification type in Notification Delivery Settings. A new notification type plus an untouched delivery setting is a notification that silently goes nowhere on mobile.
The two metadata types, and why deployment surprises people
| Metadata type | Suffix | Directory | Since | Carries |
|---|---|---|---|---|
CustomNotificationType | .notiftype | notificationtypes | API 46.0 | The type: label, API name, description, desktop, mobile, action groups |
NotificationTypeConfig | .config | notificationTypeConfig | API 48.0 | Org delivery settings: which channels and which connected apps are live |
They deploy separately. A change set or package that carries the type but not the config moves the definition and leaves the delivery settings at whatever the target org already had — which for a brand-new type is "nothing enabled for any connected app". Treat the pair as one deployable unit.
slack on the type is not the Slack feature
CustomNotificationType has a slack field. The Metadata API Developer Guide describes it as "Reserved for future use." Do not check it, do not reverse-engineer meaning from it, and do not conclude from it that Salesforce cannot notify Slack — it can.
The real Slack path is a separate notification type, created through Notification Builder's Slack flow ("Create a Slack Notification"), which binds the notification to a Slack app and a Slack message configuration and is sent by a Send Notification action. Independently, NotificationTypeConfig → notificationChannels does carry a real slackEnabled boolean, so Slack is a first-class delivery channel at the org-settings layer. Design Slack as its own type with its own audience and its own review — not as a fourth checkbox on the desktop/mobile type.
Recipient resolution is polymorphic, and that is a design lever
The Flow action's recipientIds input is documented as accepting several ID kinds, each of which fans out differently:
| ID you pass | Who receives it | Prerequisite |
|---|---|---|
UserId | That user | User must be active |
GroupId | All active group members | — |
QueueId | All active queue members | — |
AccountId | All active Account Team members | Account teams enabled |
OpportunityId | All active Opportunity Team members | Team selling enabled |
"Values can be combined in a list up to 500 values." That is a cap on the number of IDs you pass, not on the number of humans reached — one GroupId can be hundreds of people. Design the recipient expression at the ID level: passing one queue ID is both cheaper and more maintainable than resolving 200 user IDs in Flow, and it stays correct when queue membership changes.
The design consequence: prefer a group or queue ID over an enumerated user list, because membership then has an owner and a maintenance path. An explicit user list is a snapshot that starts rotting the day it ships.
Deep links: targetId versus targetPageRef
Exactly one of the two is required. targetId is a record ID and is the right answer for anything record-shaped — the platform resolves it to the record home on every surface, so the same notification works on desktop and in the mobile app without you writing a URL.
targetPageRef takes a serialized PageReference and is the escape hatch for non-record destinations (a tab, a custom component page, an external URL). It is strictly more work and strictly more fragile: you are now maintaining a navigation contract by hand.
Hand-built /lightning/r/... URLs are the worst of both. They are Lightning-only strings that bypass the platform's own navigation resolution, and they hardcode an assumption about which app the recipient is in.
Actionable mobile notifications (action groups, Beta)
CustomNotificationType supports an actionGroups array — labelled Beta in the Metadata API Developer Guide — which lets a mobile push carry buttons the user can press without opening the record. Each action has an actionLabel, actionName, an actionType of either NotificationApiAction or Share, and for NotificationApiAction an actionTarget naming the Apex class that implements it. Apex selects the group at send time with setActionGroupId(...).
This is the one lever that genuinely raises mobile notification value rather than just its volume: "Approve / Reject" in the notification shade is a different product from "tap to open a record and hunt for the button". Being Beta, it is a design option to evaluate, not to assume, and it is worth confirming current status before committing a roadmap to it.
Common Patterns
Pattern A — one type per audience contract, not per feature
Create a notification type for a class of interruption a user can reason about ("Case escalation", "Approval needed"), not for each Flow that wants to send something. Users tune preferences per type, so a type is the unit of consent. Ten types with clear names beat forty named after the projects that shipped them.
Pattern B — the throttle lives on the record, not in the sender
A Last_Notified_At__c datetime plus a Notification_Count_Today__c on the subject record makes suppression a data question rather than a code question, survives across Flow and Apex senders, and is queryable when someone asks "why did this fire six times". Detail in