Team Messaging (Mattermost Integration)
Status: DRAFT
Priority: P2
Last Verified: 2026-03-29
Spec Author: Claude (automated)
1. Problem Statement / Feature Purpose
Equa organizations need integrated team messaging to communicate in real-time without leaving the platform. The messaging feature embeds Mattermost Team Edition inside the Equa web app, with automatic user provisioning, organization-scoped teams, and agent (Equabot) interaction via dedicated channels.
Key benefits:
- Team members communicate within their organization context
- No separate account creation — users are provisioned on first access
- Equabot agent is reachable via the
#agent channel
- Platform events (share issuance, document uploads, member changes) post notifications to relevant channels
2. Current State
Frontend (equa-web) — NOT YET IMPLEMENTED
The frontend messaging module does not exist in the codebase as of 2026-03-29. The component table below describes the planned implementation, not current state. The src/modules/messaging/ directory has not been created.
Planned components:
| Component | Planned Path | Purpose |
|---|
MessagingPage | src/modules/messaging/pages/messaging-page.tsx | Page container with loading/error/embed states |
MattermostEmbed | src/modules/messaging/components/mattermost-embed.tsx | iframe embedding Mattermost UI with token auth |
MessagingLoading | src/modules/messaging/components/messaging-loading.tsx | Spinner during session provisioning |
MessagingError | src/modules/messaging/components/messaging-error.tsx | Error state with retry button |
useMattermostSession | src/modules/messaging/hooks/use-mattermost-session.ts | Hook that calls session endpoint, manages token lifecycle |
mattermost-api | src/modules/messaging/services/mattermost-api.ts | HttpClient wrappers for Mattermost API calls |
Planned routing changes:
| Change | Target File | Description |
|---|
| Path constant | src/logic/paths.ts | Add messagingPath |
| Route registration | src/app/routes/routes.ts | Add lazy-loaded /:orgId/messaging route |
| Navigation link | src/app/components/header/shared/organization-links.tsx | Add “Messaging” to org nav |
Existing placeholder text (marketing copy only, no functional code):
Source: equa-web/src/app/components/header/shared/organization-links.tsx, Line: 125 — “Interact with your organization using our communication system.”
Source: equa-web/src/modules/auth/components/carousel.tsx, Line: 117 — Onboarding carousel mentions “Notification System”
Backend (equa-server)
| Module | File Path | Purpose |
|---|
| Endpoints | equa-server/modules/agent/src/mattermost/mattermost-endpoints.ts, Line: 14 | 4 Express routes for session, config, mapping, sync |
| REST Client | equa-server/modules/agent/src/mattermost/mattermost-client.ts, Line: 7 | Thin wrapper around Mattermost REST API v4 |
| Provisioning | equa-server/modules/agent/src/mattermost/mattermost-provisioning.ts, Line: 7 | User/team/channel provisioning logic |
| Notifications | equa-server/modules/agent/src/mattermost/mattermost-notifications.ts, Line: 9 | Platform event to channel post bridge |
| Types | equa-server/modules/agent/src/mattermost/types.ts | Shared interfaces and default channel config |
| Member Sync | equa-server/modules/organizations/src/mattermost-sync.ts | Async member add/remove sync |
Member sync hooks:
Source: equa-server/modules/organizations/src/writing.ts, Line: 173 (onMemberAdded)
Source: equa-server/modules/organizations/src/writing.ts, Line: 305 (onMemberRemoved)
Agent Bridge (equabot-gateway)
| Module | File Path | Purpose |
|---|
| Mattermost Extension | equabot-gateway/extensions/mattermost/ | Existing bot auth, WebSocket monitoring, message routing |
| Org Resolution | equabot-gateway/extensions/equa-platform/src/dispatch.ts, Line: 91 | Maps Mattermost teamId to Equa organizationId |
Data Flow
equa-web (user navigates to /messaging)
|
| POST /api/v1/mattermost/session { organizationId }
v
equa-server
| 1. Verify session cookie + org membership
| 2. Lookup or create Mattermost user (POST /api/v4/users)
| 3. Issue personal access token (POST /api/v4/users/{id}/tokens)
| 4. Ensure org team exists + membership
| Response: { token, baseUrl, teamName, mmUserId }
v
equa-web
| <iframe src="chat.equa.cc/login/token?token=xxx&redirect_to=/team">
v
Mattermost Server (chat.equa.cc)
| User sends @equabot message in #agent channel
| WebSocket event -> gateway monitor
v
equabot-gateway (Mattermost extension)
| resolveAgentRoute() with teamId
| equa-platform resolves orgId via GET /api/v1/mattermost/team-mapping/{teamId}
| Agent processes with org-scoped tools
| sendMessageMattermost() -> response in channel
v
Mattermost -> displayed in equa-web iframe
3. Target State / Complete Description
The messaging feature provides organization-scoped team chat with:
- Auto-provisioning: First visit creates Mattermost user, team, default channels, and membership
- Seamless auth: No separate login — equa-server issues personal access tokens mapped to the Equa session
- Default channels per org:
#general (auto), #announcements, #cap-table, #documents, #agent
- Platform notifications: Share issuance, document uploads, and member changes post to relevant channels
- Agent integration: Messages in
#agent mentioning the bot are routed through equabot-gateway to the org-scoped agent
- Member lifecycle sync: Adding/removing org members in Equa automatically syncs Mattermost team membership
4. Data Model
| Entity | Field | Type | Description |
|---|
MattermostUserMappings | id | UUID (PK, FK -> Users) | Equa user ID |
| mattermostUserId | string | Mattermost user ID |
| mattermostUsername | string (nullable) | Mattermost username |
| personalAccessToken | string (nullable) | AES-encrypted personal access token |
| created | timestamp | Creation timestamp |
| modified | timestamp | Last update timestamp |
MattermostOrgMappings | organizationId | UUID (PK, FK -> Organizations) | Equa organization ID |
| mattermostTeamId | string | Mattermost team ID |
| mattermostTeamName | string (nullable) | Mattermost team name |
| autoSync | boolean (default: true) | Whether to auto-sync members |
| created | timestamp | Creation timestamp |
Source: equa-server/modules/persistence/src/schema.ts, Lines: 2042-2080
Migration:
Source: equa-server/modules/persistence/lab/sql/migrations/2.35.0-mattermost-mappings.sql
5. API Endpoints
| Method | Path | Auth | Description |
|---|
POST | /api/v1/mattermost/session | Cookie | Provision user/team, return session token |
GET | /api/v1/mattermost/config | Cookie | Return { enabled, baseUrl } |
GET | /api/v1/mattermost/team-mapping/:teamId | Bearer (gateway) | Return { organizationId } for a Mattermost team |
POST | /api/v1/mattermost/sync-org/:orgId | Cookie + admin | Trigger full org membership sync |
Source: equa-server/modules/agent/src/mattermost/mattermost-endpoints.ts, Lines: 34-170
See Mattermost API Endpoints for request/response details.
6. Frontend Components
Route
| Route | Component | Module |
|---|
/:orgId/messaging | MessagingPage | equa-web/src/modules/messaging |
Component Tree
MessagingPage (Redux-connected, withOrganizationHeader HOC)
├── MessagingLoading (while session provisioning)
├── MessagingError (on failure, with retry)
└── MattermostEmbed (iframe with token-based auth URL)
Embedding Strategy
The frontend uses an iframe rather than importing Mattermost React components. This avoids bundling Mattermost’s own React 18 into equa-web and isolates the Mattermost UI completely.
The MattermostEmbed component constructs a login URL:
{baseUrl}/login/token?token={token}&redirect_to=/{teamName}/channels/{channel}
The iframe uses sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox" for security.
7. Business Rules and Validation
-
Organization membership required: Only org members can access messaging for that org.
- Enforced at:
equa-server/modules/agent/src/mattermost/mattermost-endpoints.ts, Line: 34 (session endpoint checks org access)
-
One Mattermost team per org: Each Equa organization maps to exactly one Mattermost team.
- Enforced at:
equa-server/modules/agent/src/mattermost/mattermost-provisioning.ts (ensureTeam uses org ID as key)
-
Token encryption: Personal access tokens stored in the database are AES-encrypted.
- Enforced at:
equa-server/modules/agent/src/mattermost/mattermost-provisioning.ts (issueSessionToken encrypts with AesKey)
-
Non-blocking sync: Member add/remove sync to Mattermost is fire-and-forget to avoid blocking org operations.
- Enforced at:
equa-server/modules/organizations/src/writing.ts, Lines: 173, 305 (async calls with .catch)
-
Signup disabled: Direct Mattermost signup is disabled. Users can only be provisioned through Equa.
- Enforced at: Mattermost env vars
MM_EMAILSETTINGS_ENABLESIGNUPWITHEMAIL=false, MM_TEAMSETTINGS_ENABLEOPENSERVER=false
8. Acceptance Criteria
9. Risks and Edge Cases
| Risk / Edge Case | Likelihood | Impact | Mitigation |
|---|
| iframe X-Frame-Options blocking | Low | High | MM_SERVICESETTINGS_ALLOWCORSFROM configured for app.equa.cc |
| Token expiry or rotation | Medium | Medium | useMattermostSession hook handles refresh; equa-server rotates stale tokens |
| Railway resource limits (hobby plan) | Medium | Medium | Mattermost needs ~512MB RAM; monitor usage, consider upgrade if needed |
| React version conflict | Low | High | iframe embedding sidesteps entirely — Mattermost runs its own React 18 |
| Mattermost server downtime | Low | Medium | Health check at /api/v4/system/ping; MessagingError component shows retry |
| Personal access token leak | Low | High | Tokens AES-encrypted at rest; transmitted over HTTPS only |