Skip to main content
Notification preferences give users control over what they hear from your app, on which channel, and how often. Get them right and users stay engaged - they can mute what’s noisy without losing what matters. Skip them and users will unsubscribe from everything. This guide covers the preference patterns that come up most in real products - from simple channel opt-in/opt-outs to multi-tenant setups - and ends with a worked example from a real project-management app. Each pattern explains what it is, when to use it, how to configure it in SuprSend, and the code to implement it in your app. One rule runs through every pattern: a preference value resolves as user choice → tenant default → category default. The most specific setting wins. See Preference Evaluation for how and when SuprSend applies this.
New to preferences in SuprSend? Read the User Preferences overview first - it covers how categories, channels, and the preference API response are structured. For multi-tenant setups, see Tenant Preferences.Client-side code examples use the @suprsend/react SDK. The same user.preferences.* methods exist in the JavaScript web SDK, with equivalents for Angular, iOS, and Expo. Each pattern also shows the matching REST API call - use it from your backend (it needs your workspace secret) or from platforms without an SDK. Digest schedules and condition properties (Sections 3-6) are REST-only today. If you want a ready-made preference UI without building from scratch, see the Embedded Preference Centre.

1. Channel opt-in / opt-out

Users control what they receive on each channel. For every channel your app uses (Email, SMS, WhatsApp, Push, etc.), the user picks one of two options:
  • All - receive all notifications on this channel, except categories they’ve explicitly turned off
  • Required - receive only notifications from categories marked as mandatory (Can’t Unsubscribe)
Separately, users can also turn individual channels on or off per preference category - for example, keeping Email on for “Product Updates” but turning off WhatsApp for the same category. These are the per-category channel checkboxes shown in the CHANNELS BY TOPIC section of the screenshot. The channel_preferences array in the API response drives the global All / Required control. Each item has a channel name and an is_restricted boolean: false means All, true means Required. The per-category channel preferences live under sections[].subcategories[].channels.
Channel-level preferences

Example app UI - global channel controls (mockup, not the SuprSend dashboard)

Configure in SuprSend
  1. Go to Developers → Preference categories and select a root-category (System, Transactional, or Promotional)
  2. Optionally, click +Section to create a section (for example “Product”, “Billing”). Sections are used to group sub-categories on the preference page. Section doesn’t have it’s own preference.
  3. Click +Sub-Category, fill in the name, select the section (if set), and set a default preference (On, Off, or Can’t Unsubscribe)
  4. Click Publish Changes
Build in your app Call getPreferences() in your parent component on page load and pass the result down as preferenceData. The response gives you two independent parts:
  • channel_preferences - render one row per channel with All / Required radio buttons. Read is_restricted to know which is currently selected: false = All, true = Required. Call updateOverallChannelPreference with ChannelLevelPreferenceOptions.ALL or ChannelLevelPreferenceOptions.REQUIRED when the user changes their selection.
  • sections[].subcategories[].channels - per-category channel checkboxes that let users turn individual channels on or off within a specific notification type. This is covered in detail in Section 2.
When to use: Your app sends on multiple channels and users want coarse control over each one. Examples: “Email me everything, but push only for required notifications”, “I only want SMS for critical alerts.” Common in consumer apps, marketplaces, and collaboration tools.

2. Preference category toggles

Users turn each notification type on or off individually - for example, keeping “Invoices & receipts” on while turning off “Sales Newsletter” - and optionally choose which channels receive each type. This is the standard notification settings page that most apps need. The response gives you sections (top-level groupings like Product, Billing, Sales) containing subcategories (the individual notification types). Each subcategory has a toggle and a list of channel checkboxes.
Category-level preferences

Example app UI - category toggles with per-category channels (mockup)

Configure in SuprSend
  1. Go to Developers → Preference categories and select a root-category
  2. Click +Section to create sections (for example “Product”, “Billing”, “Sales”). Sections are used to group sub-categories on the preference page. Section doesn’t have it’s own preference.
  3. Click +Sub-Category for each notification type, select its section (if set), and set a default preference (On, Off, or Can’t Unsubscribe). You can also choose which channels should be opt_in by default in case of On preference.
  4. Click Publish Changes
Build in your app Call getPreferences() in your parent component and pass preferenceData as a prop. Render sections as headings and subcategories as rows, each with a toggle and channel checkboxes below it. Three things to handle carefully:
  • is_editable: false on a subcategory means the category is set to Can’t Unsubscribe in SuprSend. Disable the toggle - users cannot opt out.
  • is_editable: false on a channel inside a subcategory means that specific channel is mandatory for that category and cannot be turned off. Disable the channel checkbox independently of the subcategory toggle. A category can have is_editable: true (user can toggle it off) while individual channels within it have is_editable: false (those channels cannot be removed).
  • channels may be empty or absent (for example when show_opt_out_channels is false for opted-out categories). Use optional chaining (subcategory?.channels?.map(...)) to handle this safely.
All preference updates (updateCategoryPreference, updateChannelPreferenceInCategory, updateOverallChannelPreference) are optimistic - the UI updates immediately and the actual API call happens in the background with a 1-second debounce. Set up these emitter listeners once when loading preferences to handle the confirmed result and catch server-side errors:
React
When to use: The default for most apps - the standard notification settings page. Examples: e-commerce (order updates on, promotions off), SaaS (product updates via email, billing via email + SMS), social apps (comments on, likes off).

3. Digest schedules and condition properties

The first two patterns only need a category and its channels. The next three use two more settings that live on a sub-category, next to its default preference: Both are configured under Developers → Preference categories on the sub-category and go live when you click Publish Changes.
Preference categories in the SuprSend dashboard with a condition property on the Project Assigned sub-category

Sub-categories in the SuprSend dashboard - the Conditions column shows condition properties configured on each

Three things make them behave like preferences, not workflow settings:
  • They resolve through the same three layers. At trigger time, SuprSend uses the recipient’s own value if set, else the tenant default, else the category default. Tenant admins can override the defaults for their users.
  • They travel in the preference API. Each sub-category in the get preference response carries digest_schedule_options, digest_schedule, and a properties array. Users change them through the update category preference API - only fields marked editable accept overrides; locked fields stay fixed.
  • You control the input type. A condition property is typed: integer, string, string_choice (single-select), list_choice (multi-select), or dynamic list variants - up to 25 per sub-category.
Use Preview on the Preference categories page before publishing - it renders the full preference experience, including digest selectors and condition inputs, exactly as users will see them.

4. Digest frequency

Instead of one notification per event, users choose how often they want a summary - Instant, Daily, or Weekly. A user who picks Daily gets one email at 09:00 summarising everything that happened that day instead of a notification for every single event. This is the digest schedule from Section 3: you define the cadence options on the sub-category, the user picks one on the preference page, and the workflow’s Digest node batches each recipient’s notifications on whatever schedule they resolve to - in the recipient’s timezone.
Digest preferences

Example app UI - digest frequency selector (mockup)

Configure in SuprSend
1

Enable a digest schedule on the sub-category

Go to Developers → Preference categories, select the sub-category (for example “Activity Summary”) and enable Digest schedule. For each option, set:
  • Label - what the user sees (“Daily”).
  • Frequency and interval - instantly, hourly, daily, weekly, or monthly; interval multiplies it (interval 2 + weekly = every 2 weeks).
  • Time / weekdays / month days - when the batch fires. Each field has an edit policy: editable (the user can change it) or locked.
Mark exactly one option as the default, then click Publish Changes.
Digest schedule options configured on a sub-category

Digest schedule settings on a sub-category (SuprSend dashboard)

2

Point the workflow's Digest node at the category

In the workflow linked to this category, add a Digest node and set its schedule type to Preference Category (category_digest_schedule in the management API). SuprSend batches notifications on the option each recipient selected - or the default, if they haven’t picked one.
If the digest schedule isn’t enabled on the category, a Digest node set to Preference Category sends notifications immediately, without batching.
Build in your app The preference response gives you everything to render: digest_schedule_options lists the options, digest_schedule is the recipient’s resolved choice. Show the options as radio buttons, add a time picker where time.edit_policy is "editable", and save with the update category preference API. This API uses your workspace secret, so call it from your backend.
Only pass id and the fields whose edit policy is editable. Pass "digest_schedule": null to clear the user’s choice and fall back to the tenant or category default.
When to use: Users get too many individual notifications and want a summary instead. Examples: daily activity digest (“Here’s what happened in your workspace today”), weekly analytics reports, aggregated comment or mention notifications.

5. User-defined alert threshold

Some notifications are only useful past a user-defined threshold. A storage alert at 1% full is noise; at 80% it’s urgent. Users set their own trigger point, and the workflow only fires when the incoming metric crosses it. The threshold is a condition property (Section 3) on the sub-category. The category preference still controls whether the alert is on and which channels it uses; the condition property controls when the workflow fires.
Threshold alerts

Example app UI - threshold input (mockup)

Configure in SuprSend
1

Add a condition property to the sub-category

Go to Developers → Preference categories. Click +Sub-Category, name it (for example “Usage alerts”), and add a condition property: key threshold, value type integer, a default value (say 80), and edit policy editable so users can change it. Click Publish Changes.
Condition property configuration with key, value type, default value, and edit policy

Condition property settings on a sub-category (SuprSend dashboard)

2

Reference it in the workflow

Compare the value from the trigger payload against $category.properties.threshold. Two places to put the check, depending on what you want to skip:
  • Skip the whole workflow - add it as a trigger condition: usage_percent >= $category.properties.threshold. If it fails, nothing runs for that recipient.
  • Skip one step - put the same expression on a Branch node. The branch decides that step; the rest of the workflow still runs.
At trigger time, SuprSend resolves the value per recipient: their own setting, else the tenant default, else the category default.
A property marked optional with no value resolves to null at runtime - handle that case in your branch logic.
Build in your app The sub-category’s properties array in the preference response gives you the input to render and the user’s current value. Save changes with the update category preference API from your backend. The category’s toggle and channel checkboxes work exactly as in Section 2.
When to use: Users need to set their own trigger point for an alert, not just whether to receive it. Examples: cloud storage (“alert me at 80% full”), API rate limits (“notify at 90% of quota”), budget tracking (“warn me when spend hits 75%“).

6. Target recipients by role

In B2B apps, who receives a notification is a setting too. An admin decides that budget alerts go to project managers, adds one stakeholder who must always get them, and excludes a contractor who never should. The workflow triggers once for the whole team; each member is kept or dropped by rules the admin controls.
Different from tags (Section 7). Tags only filter which categories appear on the preference page - they never affect delivery. The condition properties here decide who actually receives the notification.
Model the rules as condition properties on the sub-category:
  • role (list_choice) - the roles that receive this notification.
  • include (list_dynamic) - users who always receive it, whatever their role.
  • except (list_dynamic) - users who never do.
Trigger the workflow on the group - typically an object whose subscribers are the team, so SuprSend fans out to every member - and filter recipients with trigger conditions:
Combine them as OR groups: (role matches AND not excluded) OR (in the always-notify list). A recipient passes if either group holds. $recipient.subscription.role reads the role stored on the user’s object subscription.
When trigger conditions reference recipient properties and you trigger on an object, add $recipient.$type == "object" as its own OR group. Without it, the object itself fails the condition and fan-out to its subscribers never happens.
Because these are preference-layer values, they resolve like any other preference: an admin sets the category defaults, a tenant admin can override them per tenant, and whether individual users can edit them depends on each property’s edit policy. Most apps expose them on admin screens, not the end-user preference page.
Account-level notification defaults with recipient roles per category

Example admin UI (mockup from Keystone) - default recipients per notification, backed by role, include, and except

When to use: Notifications aimed at a team where only some members should hear about each event. Examples: budget alerts to project managers and sponsors only; deployment failures to on-call engineers plus the team lead; compliance alerts to all admins with legal always copied.

7. Filter categories with modules, roles, or departments

Not every user needs to see every preference category. A sales user only cares about Sales Newsletter, Cart & checkout, and New Leads - they don’t need Budget Alerts or Expense Reports. A finance user is the opposite. And in a multi-module product, each module’s alerts should live on its own tab. This uses tags. You assign tags to preference categories or sections in SuprSend, then pass the relevant tag to getPreferences() - SuprSend returns only the matching categories. When a tag is assigned at the section level, it automatically applies to all subcategories under that section via effective_tags. Tags are flexible - they can drive two common patterns:
  • Automatic filtering by role or department: Your app reads the user’s department/role/team from their profile or session and passes the tag silently. No filter UI is needed.
  • User-selected filtering by module: Your app shows a visible tab selector and re-fetches preferences with the selected tag.
Configure in SuprSend Go to Developers → Preference categories and tag your sections or sub-categories. Two ways to structure this:
  • Tag per section: Click +Section to create a section (for example “Sales”, “Finance”). Add a tag to the section (for example sales, finance). All sub-categories under that section inherit the tag automatically via effective_tags. Click Publish Changes.
  • Tag per sub-category: Create a section with all sub-categories under it. Select each sub-category and add its own tag (for example sales, finance, product). Filtering by tag returns that section with only the matching sub-categories.
Build in your app Pass the tag to getPreferences() via the tags parameter. Where the tag comes from depends on your use case - either derived from the user’s profile (automatic) or from a tab selector (user-selected). The tags parameter also supports logical filtering for more complex cases - see Filter categories with tags for supported operators (or, and, not, exists).
Read the user’s department from their profile or session and pass it as the tag. No filter UI is needed - a sales user automatically sees only sales categories, a finance user sees only finance categories.
Sales department preferences

Example app UI (mockup) - Jane D. (Sales team) sees Sales Newsletter, Cart & checkout, and New Leads

Finance department preferences

Example app UI (mockup) - M. Kim (Finance team) sees Budget Alerts, Expense Reports, Invoice Reminders, and more

React
The API equivalent is the tags query parameter on the get user preference API - a single tag, or a JSON expression with or / and / not / exists:
cURL (backend)
When to use: Users in different roles, departments, or product modules should see only the preference categories relevant to them. Examples: sales users see Sales Newsletter and Cart & checkout; finance users see Budget Alerts and Expense Reports; a multi-module product where users switch between modules to manage alerts separately.

8. Tenant admin controls

Different from Section 9 (platform admin restricting tenant access). This section is about tenant admins configuring defaults for their own users. For per-project independent preferences, see Section 10.
In a B2B product, the person who configures notifications isn’t the end user - it’s generally pre-configured by account admin. This pattern lets tenant admins control which categories are available to their users, what the defaults are, and which channels are enabled - all scoped to their account. End users then see only what their admin has left enabled. The admin view shows a table of categories with a default preference selector (Opt-in / Opt-out / Mandatory) and a toggle per category. The toggle maps directly to visible_to_subscriber in the API.
Tenant admin preferences

Example admin UI (mockup) - tenant admin sets defaults and visibility for their organization

Configure in SuprSend
  1. First ensure your preference categories are set up under Developers → Preference categories and published
  2. Provide tenant admins access to configure categories via the Tenant Preference API. They can enable/disable categories, set default On/Off, and control channel availability - all scoped to their tenant
Build in your app Build an admin settings page that calls the Tenant Preference API. Tenant admins control the following per category:
  • Default preference (preference: opt_in / opt_out / cant_unsubscribe) - sets whether the category is on, off, or mandatory by default for all users in this tenant. SuprSend evaluates preferences in the order: user preference → tenant default → category default. If a user hasn’t set their own preference, the tenant default applies.
  • Visibility (visible_to_subscriber: true / false) - controls whether the category appears on the end user’s preference page at all. Set to false to hide and turn off categories that aren’t relevant to this tenant’s users.
  • Channel controls - mandatory_channels makes specific channels non-unsubscribable (when preference is cant_unsubscribe), opt_in_channels sets which channels are on by default, and blocked_channels disables channels across all categories for the tenant regardless of individual user preferences.
For the end-user side, pass tenantId to getPreferences() - the rendering is the same as Section 2.
When to use: B2B or white-label products where each customer organisation’s admin controls the notification experience for their users. Examples: one enterprise customer enables billing + security + product alerts while another only enables billing alerts; a white-label SaaS where each reseller configures the notification defaults for their own users.

9. Hide categories by tenant plan

This guide has three tenant-related patterns. This section is for platform admins restricting what a tenant can access. Section 8 is for tenant admins setting defaults for their own users. Section 10 is for modelling each project as an independent tenant.
If you’re building a multi-tenant app (for example a SaaS platform with free and paid plans), you probably don’t want every tenant to see the same preference categories. A free-plan tenant shouldn’t see categories for features they haven’t subscribed to. SuprSend lets you handle this with global tenant preferences. As a platform admin, you can opt out specific categories or channels for any tenant. Once you do:
  • The opted-out categories and channels are removed from getPreferences() responses for all users in that tenant - they won’t see them on the preference page.
  • Notifications for those categories are not delivered to the tenant’s users.
  • These are hard overrides - neither the tenant admin’s default preferences nor individual user preferences can re-enable them. See Preference Evaluation for the full resolution order.
Tenant-restricted categories - free vs paid plan

Example app UI (mockup) - free-plan users see only core categories; paid-plan users see the full set

Configure in SuprSend Go to Tenants → select your tenant → Tenant Preference in the SuprSend dashboard. The Set Global Preference for Tenant section gives you three controls:
  • Global Channel Opt-out — block one or more channels (for example Email, In-App Inbox) across all categories for the tenant. Notifications on blocked channels are never delivered to any user in the tenant.
  • Category Opt-out — toggle a category off for the tenant. The category is removed from the tenant’s preference API responses and notifications for that category are not delivered.
  • Opt-out Channels in the Category — block specific channels within a single category (for example disable SMS for “Newsletter” but keep it enabled for other categories).
Set Global Preference for Tenant - category and channel opt-outs

Global tenant preference page in the SuprSend dashboard

Build in your app Pass tenantId to getPreferences(). Categories opted out at the tenant level are excluded from the response server-side - your rendering code stays the same regardless of tenant plan.
When to use: Multi-tenant SaaS where each tenant’s plan determines which notifications are available. Examples: free-plan tenants see only core alert categories, paid tenants see advanced analytics alerts and SLA breach alerts they’ve subscribed to.

10. Per-project preferences

Different from Section 9 (platform admin restricting tenant access) and Section 8 (tenant admin setting defaults). This section models each project as a separate tenant so preferences are fully independent per project.
Each project needs a completely independent set of preferences - its own category toggles, its own channel settings, its own defaults. Tags won’t work here because tags only filter categories; they don’t give each project its own independent channel-level preferences or defaults. The solution is to model each project as a tenant in SuprSend. Preferences are then scoped per tenant. When the user switches projects, you switch the tenantId passed to getPreferences(), and all reads and writes happen against that project’s tenant.
Project-level preferences

Example app UI (mockup) - independent preferences per project

Configure in SuprSend
  1. Create one tenant per project in SuprSend (via Tenants in the dashboard or the Tenants API). You can also create tenants via API
  2. For each tenant, configure which categories are enabled, what the defaults are, and which channels are available via the Tenant Default Preference API
  3. Ensure your preference categories are published under Developers → Preference categories
Build in your app Show a project selector. When the user picks a project, pass its tenant ID to getPreferences(). Render the full preference UI - channel-level (All / Required per channel) and category-level (toggles + channel checkboxes).
Client SDK update methods take no tenantId - they apply to the active tenant: the one set at identify, or overridden by the most recent getPreferences({ tenantId }) call. So fetch a project’s preferences before writing to it, and keep one active project at a time. The tenantId must also match the scope.tenant_id in the user’s JWT token, or the call fails. On the REST API, scope every call explicitly with the tenant_id query parameter instead.
When to use: Each project or sub-account needs completely independent preferences - its own defaults, its own channel settings, everything separate. Examples: an ad platform where each ad account has independent budget alert settings; a cloud hosting provider where production, staging, and dev each have separate alert preferences.

Worked example: Keystone

Keystone is the project-management app from the multi-tenant B2B guide: each customer gets a workspace, and each project is both a tenant (for settings and preferences) and an object (whose subscribers are the project team). Its notification settings combine most of the patterns above, so two of its categories are worth reading end to end. Budget Alert - conditions. The sub-category carries four condition properties: role, cost (the budget threshold), include, and except. The workflow triggers on the project object, and its trigger conditions keep a recipient if any of these OR groups pass:
  1. Their role is in role, the project’s cost exceeds cost, and they’re not in except
  2. They’re in include
  3. The recipient is the project object itself - the $recipient.$type == "object" fan-out guard from Section 6
Group 2 is why “always notify” beats every other rule, and group 3 is easy to forget - without it, no one gets notified at all. Comment Added - digest. The sub-category offers three digest options: Instant, Hourly with a user-editable time, and Daily at a locked time - the default. The workflow is a Digest node with schedule type Preference Category, followed by a send step using a template that renders the batched comments. A user who picks Hourly and a user who left the default get different cadences from the same workflow, with no branching.
Keystone per-project preference screen with a digest frequency selector on Comment Added

Keystone's project notification settings - category toggle, digest frequency, and channel toggles, all from one preference API response

Every screen in the app sits on the preference APIs: For the full data model - workspaces, tenants, objects, and how one workflow serves every project - see Designing notifications for multi-tenant B2B applications.

User Preferences

How categories, channels, and the preference API response fit together.

Preference Evaluation

When SuprSend checks preferences, and how conflicts resolve.

Digest

Everything the Digest node can do, beyond preference-driven schedules.

Multi-tenant B2B modelling

The full Keystone data model this guide’s example comes from.