Usecases it solves
- Build hierarchical admin preferences. Take a project management tool with Company → Project → Cycle. An admin turns email off at the Company and no user gets email in any Project or Cycle; turn it back on for one Cycle and only that Cycle sends email.
- Users can set different preferences along the tenant tree. A user picks “don’t email me” at the Company and it holds for every Project and Cycle; turn email on for one Project and they get email for that Project and its Cycles, still opted out everywhere else.
- Globally turn off a channel or category for a tenant. Turn off email at the company level to keep costs down, block SMS at a customer’s tenant when their plan doesn’t include it, or turn off a specific notification category at the project level so it stays off for every tenant below.
- Inherit templates, vendors, and branding — override where you need to. A white-label agency can let each client brand have its own vendors (Twilio, SendGrid, FCM) and templates. A notification SaaS can give each customer a default template that their sites, projects, or downstream customers tweak.
- Users can have a different profile per tenant. The same person can be a team member on one Project and the project manager on another, each with the role, channels, and properties that fit that tenant. If sub-tenants represent different apps of the same customer, each app tenant stores that user’s push token and channel identity for that app, while their name, email, and other shared properties come from the tenant above.
Key concepts
How it works
Create a sub-tenant by passingparent_id in the tenant upsert API — the tenant becomes a child of that parent. Skip parent_id (or send null) to create a root tenant.
parent_id is the only field you send. SuprSend maintains and returns two more fields on every tenant response to describe its place in the tree:
ancestors— the chain of parent IDs from the root down to the tenant’s immediate parent, root first. Does not include the tenant itself.level— the tenant’s position in the tree.0= root,4= deepest allowed.
How inheritance resolves
At trigger time, SuprSend walks the ancestor chain to figure out which value applies. Almost everything resolves bottom up — the closest tenant to where the notification fired wins. The one exception is channel and category blocks set at the tenant level, which cascade the other way and can’t be undone.How inheritance flows across a sub-tenant tree
Tenant properties
Tenant’s custom and reserved properties (logo, colors, preference_page_url, social_links) are shallow merged from parent to child.
- Inheritance is per key. Overriding
support_emailon a child still letscompany_name,region, etc. inherit from above. idandnameare never inherited; each tenant has its own.
Tenant category-level preferences
For each notification category, a tenant can either set default preferences that descendants override key by key, or hard-block the category — or specific channels within it — so no descendant can re-enable them. Update a category via the Update Tenant Category Preference endpoint:Tenant blocked channels
Setblocked_channels on the tenant itself (not scoped to any category) to globally turn a channel off for every user of that tenant, across every category — including root categories and ones marked cant_unsubscribe. Update it via the same Create / Update Tenants endpoint:
Per-tenant user preference
A user can set their own preferences on any tenant they belong to. Every user preference — category or channel — is always saved against a tenant; there is no workspace-wide user preference. At trigger time, walk up from where the notification fired; the first value the user has explicitly set wins. Update via the Update User Category Preference or Update User Channel Preference endpoint — thetenant_id query param scopes each write to a specific tenant and is required:
- User Preference supports 2 granularities — category preferences (opt in/out per notification category, and per channel within a category) and channel preferences (restrict or allow a channel across every category the user is subscribed to).
- Category preferences fall back to the tenant’s resolved default — if the user hasn’t set one anywhere in the chain, the tenant’s default for that category applies.
- Channel preferences have no tenant default — set
is_restricted: trueon a channel to restrict,falseto allow. If the user has never set it anywhere in the chain, the channel is treated as unrestricted. Mandatory channels oncant_unsubscribecategories still fire when restricted. - Channel preferences apply as a group — once a child sets its own
channel_preferences, it replaces the parent’s list entirely; the parent’s restrictions on other channels no longer apply at that child. - Within a category, each key inherits independently — changing
preferencedoesn’t detachdigest_scheduleor conditionproperties; each keeps inheriting from the closest ancestor until the user sets their own. cant_unsubscribeat the triggered tenant beats a user’s opt-out — when the tenant’s resolved preference for a category iscant_unsubscribeat the triggered tenant, the notification still sends on that tenant’s effectivemandatory_channelseven if the user opted out anywhere in the ancestor chain. Only the triggered tenant’s mandatory channels apply — ancestors’ mandatory channel lists don’t stack.
Tenant vendors
For each channel (email, SMS, push, WhatsApp, …), the closest tenant in the chain that configured a vendor wins. Vendors cascade through the full tenant tree today.- Per channel, per vendor — overriding the SMS vendor on a child still lets it inherit the parent’s email vendor.
- Default tenant vendor as final fallback — if no tenant in the chain configures a vendor for a channel, the default tenant vendor for that channel applies.
Tenant templates (coming soon)
For a workflow triggered at any tenant, SuprSend will walk the ancestor chain and pick the closest tenant that has a matching template variant — the same rule as vendors.- Per channel — a child that overrides only the email template keeps inheriting the parent’s SMS template.
- Default tenant fallback — if no tenant in the chain has a variant for a channel, the default tenant’s template applies.
- Locale fallback within each tenant — at every tenant along the chain, SuprSend will try the recipient’s locale from most specific to least specific (e.g.
es-MX→es→ template default likeen) before moving to the next tenant. - Tenant proximity beats locale specificity — a closer tenant’s
envariant wins over a farther tenant’s exact-locale variant. The selector picks the closest tenant that has any matching variant for the channel, then applies locale fallback within it.
Per-tenant user profile (coming soon)
A user’s effective profile at trigger time will be their global profile merged with each subscribed tenant’s per-tenant profile, layered from root down. The lowest tenant in the chain wins on shared fields. Users won’t need a subscription at every level — skipped levels contribute nothing. 📘 User-Tenant Mapping → | Upsert User Tenant Profile → Trigger a workflow atsprint-42 and SuprSend takes logo and primary_color from acme, support_email from project-1, and the milestone property from sprint-42 itself. SMS stays blocked on every send because acme locks it at the root — no descendant can turn it back on.
Adding a sub-tenant
- Dashboard
- API
- Go to Tenants in the side nav and open the tenant you want to add a sub-tenant under.
- Switch to the Sub-tenants tab and click Add Sub-tenant.
Sub-tenants tab — empty state
- In the modal, enter a Tenant ID (unique across the workspace, ≤ 64 chars, characters
[a-z0-9_.-]) and a Name. The parent is prefilled from the tenant you opened; if that tenant already has sub-tenants of its own, you can pick any tenant in its subtree as the parent instead. Click on Create Tenant.
Add Sub-tenant modal
Sub-tenants tab on the tenant details page
Re-parent or delink a tenant
Change theparent_id on an existing tenant to move it (and its entire subtree) somewhere else in the tree, or delink it from its parent so it becomes a root again. On the API it’s a single field — set parent_id to a new tenant to re-parent, or send null (or an empty string) to delink. On the dashboard the two flows are surfaced as separate actions.
- Dashboard
- API
Change Parent and Delink Parent actions on a sub-tenant row
Change Parent modal
Delink Parent modal
Delete a tenant
Deleting a tenant is permanent — its settings, preferences, and per-tenant profiles are gone, and creating a new tenant with the same ID doesn’t restore any previous settings. Notifications already in flight are not affected — they finish with the settings that were in effect when they were triggered. When the tenant has children, you decide what happens to them withchildren_action.
- Dashboard
- API
Delete option on a sub-tenant row
Delete modal with children handling options
Navigate the tenant listing
Once sub-tenants exist, the Tenants listing adds two columns to every row — Parent and Level — so you can see each tenant’s place in the tree at a glance. On each row, a small dot next tologo, brand colors, or social_links means that value is inherited from an ancestor; no dot means it’s set locally on this tenant.
Tenant listing page after sub-tenants are added
- Ancestor — pick a tenant and the listing shows every tenant in its subtree, at any depth.
- Level — restrict to specific depths (
0= roots,4= deepest).
acme + level 1 returns every project directly under acme.
Key behaviors and constraints
- 5 levels max. Root = level 0, deepest allowed = level 4. Every create and re-parent is checked — moves that would go past the limit are rejected with a 400.
- One parent per tenant. Every non-root tenant has exactly one
parent_id. There’s no multi-parent or DAG model. idis unique across the workspace. A tenant can’t share itsidwith its parent, sibling, or any other tenant. IDs are ≤ 64 chars and accept letters, digits,.,_, and-.- Live inheritance. Editing an ancestor’s field takes effect right away for every tenant below that hasn’t overridden it. There’s no snapshot at create time.
- Properties are merged per inner key. Override
social_links.websiteon a child and other inner keys (twitter,linkedin, …) keep inheriting from the parent. - Category preferences merge per setting. Within a category,
preference,digest_schedule, andconditionseach inherit independently — overriding one doesn’t mean that other settings will also stop inheriting. - Tenant Preference — blocked channels and categories. Tenant
blocked_channels, per-categoryblocked_channels, and categoryenabled_for_tenant: falsecascade to every descendant and can’t be reversed. - Re-parenting is safe. Local values on the moved tenant stay put; inherited values resolve from the new chain at the next send. Notifications in flight finish with the settings that were in effect at trigger time.
- The
defaulttenant is unchanged. Every workspace still ships with thedefaulttenant. It behaves as a root tenant with no children until you give it any.
FAQ
How deep can the tree go?
How deep can the tree go?
What happens if I edit a parent's primary_color?
What happens if I edit a parent's primary_color?
primary_color picks up the new value right away — the next workflow triggered at any of those tenants uses the new value. Tenants that set their own primary_color aren’t affected.Can I mix hierarchical and flat tenants in the same workspace?
Can I mix hierarchical and flat tenants in the same workspace?
parent_id: null is a root and behaves exactly like flat tenants do today. You can use sub-tenants for one part of your customer base and leave the rest flat.Do preferences, vendors, template variants, and user-tenant mapping cascade?
Do preferences, vendors, template variants, and user-tenant mapping cascade?
properties, brand fields, tenant-level blocked_channels, and the category-level block settings (enabled_for_tenant, visible_to_subscriber, per-category blocked_channels) cascade. Everything else applies to the triggered tenant only. Hierarchy support for the rest ships in a later phase — reach out to support@suprsend.com if you have a use case that needs it sooner.Can I re-parent a tenant that has children?
Can I re-parent a tenant that has children?
Can I reuse a tenant id after deleting it?
Can I reuse a tenant id after deleting it?
id. Creating a new tenant with the same id starts a brand-new record; it does not bring back the deleted tenant’s users, subscriptions, or history.Is there a performance cost to deep hierarchies?
Is there a performance cost to deep hierarchies?
My child tenant isn't picking up a parent update at send time.
My child tenant isn't picking up a parent update at send time.
parent_id and ancestors match what you expect), and (c) the field isn’t overridden on the child — a local value always wins over an inherited one.My blocked_channels change didn't take effect for a descendant.
My blocked_channels change didn't take effect for a descendant.
blocked_channels block at any level flows down and can’t be undone. The descendant’s effective list at send time is the union across the chain — every ancestor’s blocks plus its own. If you were trying to unblock a channel by removing it from a child’s blocked_channels, that isn’t possible — an ancestor’s block always wins. Remove it at the level where it was originally set.Can users have a global preference in a workspace?
Can users have a global preference in a workspace?
tenant_id query param; without one, the API rejects the request. If your workspace uses just the built-in default tenant, all writes land there and behave workspace-wide in practice, but there is no global-across-all-tenants preference on a user.If you need a workspace-wide default preference that individual tenants can then override per user, model your tenants as sub-tenants of the default tenant. For example, keep the Company at default, nest Projects under it, and nest Cycles under each Project. The user’s preference at default acts as the global default; a preference set on a Project (or a Cycle) wins for triggers scoped to that subtree.My customers are mapped as tenants and users need a global preference plus a per-tenant override. How do I model this?
My customers are mapped as tenants and users need a global preference plus a per-tenant override. How do I model this?
default tenant. default then acts as the global layer, and each customer stays its own tenant where users can override.- Re-parent every customer tenant to
default(or create new ones withparent_id: "default"). Existing users, subscriptions, and history stay intact. - Save the user’s global preference by writing to
tenant_id=default. - Save a customer-specific override by writing to
tenant_id=<customer_tenant_id>. - At trigger time, pass the customer’s
tenant_id. SuprSend walks up the chain — the customer’s preference wins if the user set one there; otherwise thedefaultpreference applies; if neither is set, the customer tenant’s own default preference for the category applies.
Which plans include sub-tenants?
Which plans include sub-tenants?
Next steps
Tenant preferences
Tenant templates
$tenant.* variables.Trigger a tenant workflow
id at trigger time and let SuprSend resolve the merged profile.