SMSPort RBAC, Members & Teams Architecture Specification

Module Domain: Identity, Access Management, Team Routing & Dynamic Capabilities
Status: Implemented & Enforcing
Last Updated: September 2026


1. High-Level Architecture Overview

SMSPort employs a Decoupled, Capability-Based Multi-Tenant RBAC & Routing Engine built on three foundational pillars: Roles, Members, and Teams.

graph TD
    subgraph Pillar1["Pillar 1: Roles (Security and Capabilities)"]
        A["Permissions Tab / Create Role"] -->|POST /tenants/current/roles| B["TenantCustomRolesService"]
        B -->|Saves Capabilities Array| C[("tenant_custom_roles DB")]
        C -->|Dynamic Capability Check| D["TenantRbacGuard"]
    end

    subgraph Pillar2["Pillar 2: Members (User Accounts and Auth)"]
        E["Invite Member Form"] -->|POST /agents/invite| F["TenantsService / Auth"]
        F -->|Assigns Role| G[("tenant_users DB")]
        G -->|Enforces Role Context| D
    end

    subgraph Pillar3["Pillar 3: Teams (Chat Routing and Queues)"]
        H["Teams Tab / Create Team"] -->|POST /teams| I["TeamsService"]
        I -->|Groups Members| J[("teams and team_members DB")]
        K["WhatsApp Cloud API Webhook"] -->|Bot / Routing Rules Engine| J
        J -->|Assigns Conversation| G
    end

2. Core Pillars & Interconnection Mechanics

Pillar Entity Purpose Real-World Example
1. Role TenantCustomRoleEntity / TenantMemberRole Defines what actions and screens a user can access Support Manager, Campaign Specialist, Frontline Agent
2. Member TenantMemberEntity (tenant_users) Represents who logs into the system (User identity & credentials) Rahul (rahul@company.com), Priya (priya@company.com)
3. Team TeamEntity (teams) & TeamMemberEntity Represents departments & queues for incoming chat distribution Sales Desk, Billing Escalations, Night Shift Support

3. End-to-End API Request Lifecycle & Guard Enforcement

sequenceDiagram
    autonumber
    actor User as Member (Client Browser)
    participant Web as Next.js Web Console
    participant Guard as TenantRbacGuard
    participant CustomRoles as TenantCustomRolesService
    participant Controller as Feature Controller (e.g. CampaignsController)
    participant DB as Postgres Database

    User->>Web: Clicks "Launch Broadcast Campaign"
    Web->>Guard: POST /campaigns (Authorization: Bearer JWT)
    Guard->>Guard: Read user.role from JWT Context
    alt Role is Standard System Role (owner/admin/manager)
        Guard->>Guard: Fast Lookup in ROLE_CAPABILITIES[user.role]
    else Role is Custom Role (e.g. "support-lead")
        Guard->>CustomRoles: resolveRoleCapabilities(tenantId, role)
        CustomRoles->>DB: Query tenant_custom_roles by tenantId + slug
        DB-->>CustomRoles: Return capabilities[]
        CustomRoles-->>Guard: Return TenantCapability[]
    end

    alt Has Required Capability (TenantCapability.CAMPAIGNS_MANAGE)
        Guard->>Controller: Allow Execution
        Controller->>DB: Mutate Campaign State
        DB-->>Controller: 200 OK
        Controller-->>Web: Return Campaign Response
    else Missing Capability
        Guard-->>Web: 403 Forbidden ("Action requires permission 'campaigns:manage'")
    end

4. Database Schema & Relationships (ERD)

erDiagram
    TENANTS ||--o{ TENANT_USERS : "has members"
    TENANTS ||--o{ TENANT_CUSTOM_ROLES : "defines custom roles"
    TENANTS ||--o{ TEAMS : "defines departments"
    TENANTS ||--o{ ROUTING_RULES : "configures auto-assignment"
    TEAMS ||--o{ TEAM_MEMBERS : "contains"
    TENANT_USERS ||--o{ TEAM_MEMBERS : "joined to"
    TEAMS ||--o{ ROUTING_RULES : "assigned queue"

    TENANTS {
        uuid id PK
        string name
        string slug
        string plan
        timestamptz createdAt
    }

    TENANT_USERS {
        uuid id PK
        uuid tenantId FK
        uuid userId FK
        string email
        string fullName
        string role "owner | admin | manager | agent | viewer | custom_slug"
        string status "active | invited | inactive"
        timestamptz joinedAt
    }

    TENANT_CUSTOM_ROLES {
        uuid id PK
        uuid tenantId FK
        string name "e.g. Campaign Lead"
        string slug "e.g. campaign-lead"
        string description
        string baseRole "optional template inherited"
        jsonb capabilities "array of TenantCapability strings"
        timestamptz createdAt
    }

    TEAMS {
        uuid id PK
        uuid tenantId FK
        string name "e.g. Sales Team"
        string description
        string status "active | archived"
        timestamptz createdAt
    }

    TEAM_MEMBERS {
        uuid id PK
        uuid teamId FK
        uuid tenantUserId FK
        string role "member | lead"
        timestamptz createdAt
    }

    ROUTING_RULES {
        uuid id PK
        uuid tenantId FK
        uuid teamId FK
        string name
        jsonb conditions
        string strategy "round_robin | least_busy"
        boolean active
    }

5. Standard System Roles vs. Dynamic Custom Roles

graph LR
    subgraph StandardRoles["Standard System Roles (Protected)"]
        O["Owner: Full Access"]
        A["Admin: Ops Management"]
        M["Manager: Campaigns and Chats"]
        Ag["Agent: Reply and Contacts"]
        V["Viewer: Read Only"]
    end

    subgraph CustomRoles["Dynamic Custom Roles (Tenant-Defined)"]
        C1["Role: Support Specialist (Live Chat Only)"] --> DB[("tenant_custom_roles DB")]
        C2["Role: Campaign Lead (Campaigns and Templates)"] --> DB
    end

Standard Roles Capability Matrix

Feature / Module Owner Admin Manager Agent Viewer
Dashboard & Inbox Read ✅ Full ✅ Full ✅ Full ✅ Full 👁 View
Live Chat Reply ✅ Full ✅ Full ✅ Full ✅ Full ❌ None
Manage Queues & Routing ✅ Full ✅ Full ✅ Full ❌ None ❌ None
Contacts & Custom Fields ✅ Full ✅ Full ✅ Full 👁 View 👁 View
Campaigns & Broadcasts ✅ Full ✅ Full ✅ Full ❌ None ❌ None
WhatsApp Templates ✅ Full ✅ Full ✅ Full ❌ None ❌ None
WhatsApp Embedded Signup ✅ Full ✅ Full 👁 View ❌ None ❌ None
AI & Bot Flow Builder ✅ Full ✅ Full ✅ Full 👁 Use ❌ None
API Keys & Webhooks ✅ Full ✅ Full ❌ None ❌ None ❌ None
Workspace Settings ✅ Full ✅ Full ❌ None ❌ None ❌ None
Billing & Subscriptions ✅ Full ❌ None ❌ None ❌ None ❌ None

6. Frontend to Backend Wiring Audit

Pillar UI Component / Action Client API Method Backend Controller & Service Database Table
Roles PermissionsView Table fetchTenantRoles() GET /tenants/current/roles → TenantCustomRolesService.getTenantRoles() tenant_custom_roles
Roles CreateRoleModal (+ Create) createTenantRole() POST /tenants/current/roles → TenantCustomRolesService.createCustomRole() tenant_custom_roles
Roles CreateRoleModal (Edit) updateTenantRole() PATCH /tenants/current/roles/:id → TenantCustomRolesService.updateCustomRole() tenant_custom_roles
Roles RoleDetailSidebar (Delete) deleteTenantRole() DELETE /tenants/current/roles/:id → TenantCustomRolesService.deleteCustomRole() tenant_custom_roles
Members MembersTable List fetchAgents() GET /agents → AgentsService.listAgents() tenant_users
Members InviteForm inviteMember() POST /agents/invite → TenantsService.inviteMember() tenant_invites
Members /accept-invite Page acceptInvite() POST /auth/accept-invite → AuthService.acceptInvite() tenant_users + auth_credentials
Members Role Selector Dropdown updateAgentRole() PATCH /agents/:id/role → TenantsService.updateMemberRole() tenant_users
Members Status Toggle Button updateAgentStatus() PATCH /agents/:id/status → TenantsService.updateMemberStatus() tenant_users
Teams TeamsTable List fetchTeams() GET /teams → TeamsService.listTeams() teams + team_members
Teams CreateTeamForm createTeam() POST /teams → TeamsService.createTeam() teams
Teams ManageTeam Modal add/removeTeamMember POST /teams/:id/members/:userId / DELETE team_members
Teams RoutingRulesTable fetchRoutingRules() GET /teams/routing-rules + POST /teams/routing-rules inbox_routing_rules

7. Operator & Admin Runbook

How to Onboard a New Team Member with Custom Permissions:

  1. Create Custom Role: Navigate to /agents → Permissions tab → Click + Create Role. Name the role (e.g., "Junior Support"), select permissions using the categorized toggle switches, and save.
  2. Send Invitation: Go to the Members tab → Click Add Member → Enter the user's name and email → Select the custom role from the dropdown → Click Send Invitation.
  3. Account Activation: The invited user opens /accept-invite?token=..., sets their password, and is automatically linked to the tenant with their assigned capabilities.
  4. Assign to Team: Go to the Teams tab → Open the appropriate department (e.g. "Customer Care") → Add the user. Incoming WhatsApp chats routed to this team will now be distributed to the user.