Skip to main content

Overview

This page provides comprehensive technical details for implementing complex tenant architectures. For basic tenant concepts, see the Tenants Overview.

Tenant Object Structure

Tenants store defaults (preferences, profile) that take effect when users don’t have specific configurations, plus custom properties for template usage:
{
  "id": "acme-corp-engineering",
  "name": "Acme Corp Engineering Team",
  "parent_tenant_id": "acme-corp",
  "default_preferences": {
    "items": [
      {
        "status": "OPTED_IN",
        "has_custom_routing": true,
        "custom_routing": [
          "email",
          "slack"
        ],
        "id": "production-alerts"
      },
      {
        "status": "OPTED_OUT",
        "has_custom_routing": false,
        "id": "marketing-updates"
      }
    ]
  },
  "properties": {
    "department": "engineering",
    "timezone": "America/Los_Angeles",
    "budget_code": "ENG-2024",
    "slack_workspace": "acme-engineering"
  },
  "user_profile": {
    "company_name": "Acme Corp",
    "team_name": "Engineering",
    "environment": "production"
  },
  "brand_id": "acme-engineering-brand"
}
For complete tenant structure details and all available fields, see the Tenants API Reference.

Hierarchical Inheritance

Four-Layer Hierarchy Limit

Courier supports up to four layers of tenant hierarchy. Here’s how the system handles deeper hierarchies:
FOUR LAYERS OF HIERARCHYCurrently, Courier only supports four layers of tenant hierarchy. For example if you have a hierarchy like the following.
tenant0
└── tenantQ
    └── tenantP
        ├── tenantR1
        │   └── tenantR1D1
        └── tenantR2
            ├── tenantR2D1
            └── tenantR2D2
Loading a tenant context of tenantR2 will load in tenant0, tenantQ, tenantP, tenantR2.However, loading a tenant context of tenantR2D2 will start at tenantQ, and also load tenantP, tenantR2, then finally tenantR2D2

Metadata Merging Behavior

When data is loaded through a hierarchy, the topmost parent is loaded first, then the next, and next, overwriting keys within the metadata properties as it goes. Parent Tenant:
{
  "brand_id": "brandX",
  "user_profile": {
    "key1": "value1",
    "key3": "valueA"
  }
}
Child Tenant:
{
  "brand_id": "brandY",
  "user_profile": {
    "key2": "value2",
    "key3": "valueB"
  }
}
Merged Result:
{
  "brand_id": "brandY",
  "user_profile": {
    "key1": "value1",
    "key2": "value2",
    "key3": "valueB"
  }
}

User Profile Merging

Tenant user profile data merges with user data following a specific precedence order:
  1. Tenant hierarchy merge (parent → child)
  2. User profile data from Courier
  3. Send API call to/profile context (highest priority)
Resolved Tenant Context:
{
  "user_profile": {
    "key1": "value1",
    "key2": "valueA",
    "key3": "X",
    "foo": "bar"
  }
}
Courier User Profile:
{
  "key2": "valueB",
  "key4": "Y",
  "foo": "baz"
}
Send Context:
{
  "to": {
    "key2": "valueC",
    "key5": true
  }
}
Final Merged Profile Context:
{
  "foo": "baz",
  "key1": "value1",
  "key2": "valueC",
  "key3": "X",
  "key4": "Y",
  "key5": "true"
}

Provider Credentials Storage

Tenant user profiles commonly store per-tenant provider credentials:
{
  "user_profile": {
    "slack": {
      "access_token": "xoxb-..."
    },
    "microsoft_teams": {
      "webhook_url": "https://outlook.office.com/webhook/..."
    }
  }
}

Default Preferences Management

Setting Tenant Defaults

Configure tenant-specific default preferences for subscription topics: Via Courier Studio:
{
  "items": [
    {
      "id": "72YE3TJK7S40KWN427Y69FD4D4FH",
      "status": "OPTED_IN",
      "type": "subscription_topic"
    }
  ]
}
Via REST API:
// PUT /tenants/:tenant_id
{
  "name": "default-preferences-test",
  "default_preferences": {
    "items": [
      {
        "id": "72YE3TJK7S40KWN427Y69FD4D4FH",
        "status": "OPTED_IN"
      }
    ]
  }
}

Custom Routing Defaults

Set default channel routing per tenant:
{
  "items": [
    {
      "id": "72YE3TJK7S40KWN427Y69FD4D4FH",
      "status": "OPTED_IN",
      "type": "subscription_topic",
      "has_custom_routing": true,
      "custom_routing": ["inbox", "push", "sms"]
    }
  ]
}
USER PREFERENCE PRECEDENCEDefault Tenant Preferences will change the default behavior for any user/tenant combination who has not updated their preferences for the subscription_topic_id yet. Users who have already made a selection for this tenant will keep their current preference.

Branding Integration

Specify a brand as the default for a tenant. This allows inbox, inbox messages, and email messages to be applied based on tenant context instead of global level. Providing the brand in the Notification Send can still override this setting. To learn more about creating and managing brands, see Brands in Email Notifications.

Customer Data Platform Integrations

For both Rudderstack and Segment integrations, we support the group event, which will create a user tenant membership between the user_id in the group call and the groupId.

Auto-Infer Tenant Context

By default, if a user has a single User Tenant Membership and a Send request does not specify the tenant in the context, Courier will load the tenant information into the context by default. Auto-infer is excellent for studio, list, or audience messages because required provider information may be stored at the tenant level. You can control this setting under your general workspace settings.
Workspace settings showing auto-infer tenant context toggle

Workspace Settings - Auto-Infer Tenant Context