Partner White-Labeling¶
What it does¶
Allows partners (e.g., Rillet) to run Stacksync under their own brand. Users see the partner's logo, name, and colors across the login page, app UI, emails, and error notifications.
Why it exists¶
Partners resell Stacksync to their customers. Those customers should see the partner's brand, not Stacksync's.
How it works¶
Partner Detection (workflows-frontend)¶
Single source of truth: detectPartnerFromUrl() in src/api/partners/get-config.ts.
| Detection Method | Example |
|---|---|
| Hostname map | connect.rillet.com → rillet |
| URL path | /partner/rillet/... → rillet |
| localStorage override | __partner_override__ = rillet (dev/testing) |
Branding Config¶
Stored in partners table → branding JSONB column. Fetched via GET /v1/partners/{id}/config.
{
"preferred_theme": "light",
"texts": {
"company_display_name": "Rillet",
"product_name": "Rillet Integration Platform",
"page_title": "Rillet",
"ai_copilot_name": "Rillet Copilot",
"support_email": "hello@rillet.com",
"documentation_url": "https://docs.stacksync.com"
},
"colors": {
"light": {
"primary": "#5B4CFF",
"primary_hover": "#4A3DE6",
"primary_active": "#3A2ECC"
}
},
"logos": {
"icon_url": "https://asset.brandfetch.io/...",
"favicon_url": "https://asset.brandfetch.io/..."
},
"create_resource_page_header_image": "https://cdn.brandfetch.io/...",
"emails": {
"logo_url": "https://cdn.brandfetch.io/.../logo.png",
"logo_width": "80",
"frontend_url": "https://connect.rillet.com",
"support_email": "hello@rillet.com",
"button_color": "#5B4CFF"
}
}
What Gets White-Labeled¶
| Area | How | Where |
|---|---|---|
| Login page | Auth0 Universal Login template detects Rillet client ID, swaps logo | Auth0 tenant config |
| Passwordless email | Auth0 connection template uses application.client_metadata.partner_email_logo_url |
Auth0 passwordless connection |
| App UI | usePartnerConfig() hook provides texts, colors, logos to all components |
src/hooks/use-branding.ts |
| CSS variables | Partner colors override Carbon Design System variables via !important |
src/app/(carbon)/layout.tsx |
| Error notification emails | {platform_name} in all error templates, resolved from workspace → partner |
connectors/utils/error_notification_builder.py |
| SendGrid email templates | Handlebars: {{platform_name}}, {{logo_url}}, {{button_color}}, {{support_email}} |
SendGrid dynamic templates |
| Workspace invite emails | platform_name passed as template variable + sender name |
endpoints/common/workspaces.py |
| Write error messages | Generic wording: "in our platform" instead of "in Stacksync" | apps/*/write.py |
| Workflow modules | Backend replaces "Stacksync" in module names + swaps icons for stacksync*/file_service app types. Category icons also replaced for system. |
fe-logics/utils/a_utils/partner_branding_modules.py |
| Apps branding | Category names/icons in module sidebar branded when workspace_id passed |
fe-logics/endpoints/common/apps_branding/endpoint.py |
| Resource table icons | "Apps" column uses appBrandings from store for partner icon resolution |
resource-app-icons-tooltip.tsx |
Partner Provisioning Gate (workflows-frontend)¶
When a user logs in via a partner URL but isn't provisioned:
POST /loginwithX-Partner-Idheader- Backend checks
user.options.partners.{partner_id} - Returns
partner_provisioned: falseif not found - Frontend shows "Account Not Provisioned" page with partner branding
Workspace Filtering¶
- Partner mode:
/loginreturns only workspaces withoptions.partner.partner_idmatching - Normal mode:
/loginexcludes all partner-provisioned workspaces
Hidden UI Elements in Partner Mode¶
- "Create new workspace" button (workspace switcher)
- Pricing section (settings page)
- Stacksync-specific text replaced with partner name throughout
Key Files¶
| File | Purpose |
|---|---|
wf-fe/src/api/partners/get-config.ts |
Partner detection, config types, hostname map |
wf-fe/src/hooks/use-branding.ts |
React hook for partner branding |
wf-fe/src/components/partner-provisioning-gate.tsx |
"Not provisioned" gate |
fe-logics/routes/partners/endpoint.py |
Public partner config API |
fe-logics/routes/partners/partner_api.py |
Partner workspace/user CRUD API |
fe-logics/functions/partners/branding.py |
resolve_branding_vars() — all SendGrid template vars |
fe-logics/db/workspaces.py |
get_workspace_partner_branding() — single JOIN query |
fe-logics/endpoints/common/notifications.py |
send_email_notification() — merges branding vars |
fe-logics/endpoints/common/detect_schema_changes/utils.py |
switch_off_base_due_to_error() — schema change notifications with branding |
fe-logics/endpoints/common/users/users.py |
Login with partner context |
wf-fe/src/utils/oauth/app-oauth-handler.ts |
OAuth popup handlers — Airtable PKCE with cross-origin postMessage |
connectors/commons/partners.py |
resolve_workspace_partner() — single lookup for name, docs URL, email vars |
connectors/utils/error_notification_builder.py |
Partner-aware error emails with full branding vars and details_link |
fe-logics/utils/a_utils/partner_branding_modules.py |
apply_partner_branding_to_modules(), apply_partner_branding_to_app_brandings(), translate_search_term() |
fe-logics/db/partners.py |
get_partner_branding_for_workspace() — single JOIN (workspaces → partners) |
fe-logics/endpoints/workflows/modules/endpoint.py |
Module list/search/info with partner branding on responses |
fe-logics/endpoints/common/apps_branding/endpoint.py |
Apps branding with optional workspace_id for partner context |
wf-fe/src/components/tables/_carbon/resources/resource-app-icons-tooltip.tsx |
Resource table icon resolution using appBrandings |
wf-fe/src/api/app_brandings/get.ts |
getAppBrandings() — passes workspace_id, in-memory cache with workspace invalidation |