> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature Specification: Team Messaging (Mattermost)

> Integrated team messaging for Equa organizations powered by Mattermost

# 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

<Warning>
  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.
</Warning>

**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](/api/endpoints/mattermost-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

1. **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)

2. **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)

3. **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)

4. **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)

5. **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

* [ ] AC-1: Navigating to `/:orgId/messaging` provisions a Mattermost user, team, and membership on first visit — **backend ready, frontend not built**
* [ ] AC-2: Mattermost UI loads in an iframe, auto-authenticated with the provisioned token — **frontend not built**
* [ ] AC-3: User sees only their organization team and channels — **backend ready, frontend not built**
* [ ] AC-4: Messages persist across sessions — **backend ready, frontend not built**
* [ ] AC-5: Mentioning `@equabot` in `#agent` channel triggers an agent response — **gateway config needed**
* [ ] AC-6: Share issuance posts a notification to `#cap-table` — **notification service exists, event hooks not wired**
* [ ] AC-7: Adding/removing org members syncs Mattermost team membership — **sync hooks exist, equa-server redeploy needed**

***

## 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                     |
