Module Domain: Identity, Access Management, Team Routing & Dynamic Capabilities
Status: Implemented & Enforcing
Last Updated: September 2026
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
| 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 |
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
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
}
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
| 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 |
| 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 |
- 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.
- 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.
- Account Activation: The invited user opens
/accept-invite?token=..., sets their password, and is automatically linked to the tenant with their assigned capabilities.
- 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.