Matrix Private Support Chat Plan

Summary

Add a lightweight landing-page support chat widget for apps/webdevelop-pro that uses Matrix as the staff-side communication channel. The website must not embed a full Matrix client and must not expose Matrix bot, admin, or appservice credentials in the browser.

The chosen architecture is private support rooms:

  • The visitor chats with a small website widget.
  • The widget talks only to a backend support-chat API.
  • The backend creates one private Matrix room per website conversation.
  • Staff reply from Matrix; replies are relayed back to the visitor through the backend.

This replaces the earlier public-channel direction. The independent critique was correct: a public Matrix room is a poor support-chat default because it leaks support conversations into shared room history, complicates privacy expectations, and makes abuse control harder.

Research basis:

  • Matrix Client-Server API supports room creation, message sending, sync, membership, and room state.
  • Matrix rooms can be private, invite-only, and non-federated.
  • Synapse open registration without verification is a spam and abuse risk.
  • Hosted Matrix clients such as Element are not a good website widget fit; a backend relay is the safer integration boundary.

Useful references:

Key Changes

Frontend

Add a new minimal Vue 3 widget in apps/webdevelop-pro.

Default placement:

  • Floating bottom-right widget.
  • Landing page / only.
  • Wrapped in ClientOnly.
  • Lazy-loaded after the page is interactive or after first user intent.

Feature flags:

  • VITE_MATRIX_CHAT_ENABLED=true
  • VITE_MATRIX_CHAT_API_URL=https://matrix-chat.webdevelop.pro

Do not reuse the existing @webdevelop-pro/ui-kit/chat/ChatWidget transport for this feature. That widget is tied to VITE_CHATBOT_URL and a streaming AI chatbot API. Reusing visual pieces is acceptable only if it does not keep the old chatbot contract.

Backend

Add a new backend service, recommended path/name:

  • apps/matrix-support-api

Recommended stack:

  • Node.js + TypeScript
  • Fastify
  • matrix-bot-sdk
  • Postgres for session/message persistence

The backend owns all Matrix access tokens and room-management logic.

DevOps

Add a new Ansible deployment role instead of mixing product-specific relay logic into the existing Matrix homeserver role:

  • ansible-devops/roles/matrix-support-chat

Keep ansible-devops/roles/matrix responsible for Synapse, nginx, LiveKit/MatrixRTC, and homeserver-level configuration.

Required Matrix hardening:

  • Disable public registration by default.
  • Disable guest access by default.
  • Move Matrix secrets out of role defaults into inventory/vault.
  • Pin Matrix container image versions instead of using latest.
  • Add explicit Synapse rate limits for registration and messaging.
  • Keep staff account provisioning separate from anonymous website traffic.

Backend Contract

Create Session

POST /v1/support-chat/sessions

Request:

json
{
  "initialMessage": "Hello, I need help with tokenization infrastructure.",
  "sourceUrl": "https://www.webdevelop.biz/",
  "pageTitle": "Tokenize Real-World Assets with Enterprise-Grade Infrastructure",
  "visitorName": "Optional visitor name",
  "visitorEmail": "optional@example.com"
}

Response:

json
{
  "sessionId": "support_session_id",
  "token": "opaque_session_token",
  "messages": []
}

Behavior:

  • Validate message length and reject empty messages.
  • Create a private Matrix room.
  • Invite configured support staff.
  • Post the visitor's initial message into the Matrix room.
  • Store the session, token hash, Matrix room ID, and first message.

Send Visitor Message

POST /v1/support-chat/sessions/:sessionId/messages

Headers:

http
Authorization: Bearer <token>

Request:

json
{
  "body": "Can we talk to someone about launch timelines?"
}

Response:

json
{
  "messageId": "message_id",
  "createdAt": "2026-05-23T00:00:00.000Z"
}

Behavior:

  • Validate the bearer token.
  • Validate message length.
  • Rate-limit by session and IP.
  • Send the message into the Matrix room as the support bot.
  • Store the message in Postgres.

Poll Messages

GET /v1/support-chat/sessions/:sessionId/messages?after=<messageId>

Headers:

http
Authorization: Bearer <token>

Response:

json
{
  "messages": [
    {
      "id": "message_id",
      "sender": "staff",
      "body": "Yes. What timeline are you targeting?",
      "createdAt": "2026-05-23T00:00:00.000Z"
    }
  ]
}

Behavior:

  • Use short polling every 2 seconds while the widget is open.
  • Return only messages for that visitor session.
  • Do not expose Matrix room IDs, Matrix event IDs, access tokens, or staff internal metadata unless explicitly needed.

Close Session

POST /v1/support-chat/sessions/:sessionId/close

Headers:

http
Authorization: Bearer <token>

Behavior:

  • Mark the website session closed.
  • Keep the Matrix room for staff history and audit.
  • Stop relaying new messages to the visitor unless the session is reopened by a future version.

Matrix Room Behavior

Create one Matrix room per website conversation.

Bot account:

  • @supportbot:matrix.webdevelop.pro

Staff configuration:

  • MATRIX_SUPPORT_STAFF_MXIDS=@alice:matrix.webdevelop.pro,@bob:matrix.webdevelop.pro

Room creation defaults:

json
{
  "preset": "private_chat",
  "creation_content": {
    "m.federate": false
  },
  "initial_state": [
    {
      "type": "m.room.history_visibility",
      "state_key": "",
      "content": {
        "history_visibility": "joined"
      }
    }
  ]
}

Operational behavior:

  • Invite all configured staff users to the room.
  • Set room name to a useful support label, for example Website chat - 2026-05-23 - webdevelop.biz.
  • Set room topic to: Messages sent here are visible to the website visitor.
  • Relay non-bot m.room.message text events back to the website.
  • Ignore bot-origin messages when reading Matrix events to avoid echo loops.
  • Keep v1 unencrypted so the bot can read staff replies reliably.
  • Prevent normal staff users from enabling room encryption accidentally through room power-level policy where practical.

Out of scope for v1:

  • File uploads.
  • Voice/video calls.
  • MatrixRTC.
  • Public room aliases.
  • Matrix guest accounts in the browser.
  • E2EE.
  • Push notifications.
  • Multi-agent assignment workflow.

Frontend Behavior

The widget should be deliberately small and support-focused.

Core states:

  • Closed launcher.
  • Open empty state.
  • Sending first message.
  • Active session.
  • Operator reply received.
  • Network error with retry.
  • Offline state.
  • Session closed.

Data handling:

  • Create the backend session only after the first visitor message.
  • Store { sessionId, token } in sessionStorage.
  • Do not store support-chat tokens in localStorage.
  • Render text as text, not HTML.
  • Escape all user and Matrix-originated message content.
  • Keep transcript limited to the current browser session in v1.

Polling:

  • Poll every 2 seconds while the widget is open.
  • Pause polling while closed after a short grace period.
  • Resume polling when reopened.
  • Stop polling on unmount.

Vue implementation requirements:

  • Use Vue 3 Composition API.
  • Use <script setup lang="ts">.
  • Use typed props/emits.
  • Use ref() for primitive state.
  • Use computed() for derived UI state.
  • Put API/session logic in a composable if it is used by more than one component.
  • Use Pinia only if the state needs to outlive the widget component or be shared outside it.

Security And Abuse Controls

Frontend:

  • No Matrix tokens in browser code.
  • No Matrix room IDs exposed to the browser unless a later feature explicitly needs them.
  • No HTML rendering in chat messages.
  • Disable send while request is in flight.
  • Enforce client-side length limits, but do not trust them.

Backend:

  • Hash session tokens at rest.
  • Rate-limit by IP and session.
  • Enforce message length and session lifetime.
  • Add basic spam throttles before creating Matrix rooms.
  • Log request IDs and Matrix room IDs server-side for debugging.
  • Do not log raw bearer tokens.
  • Optionally add Cloudflare Turnstile before the first message if spam appears in production.

Matrix/Synapse:

  • Disable open public registration unless there is a separate verified onboarding flow.
  • Keep support rooms non-federated for v1.
  • Keep support rooms invite-only.
  • Reduce media upload limits from the current broad Matrix defaults before enabling attachments.
  • Add moderation tooling separately for public community rooms; it is not required for private support rooms v1.

Test Plan

Frontend

Run:

bash
pnpm --dir apps/webdevelop-pro exec vue-tsc --noEmit
pnpm --dir apps/webdevelop-pro run test:unit:cov
pnpm --dir apps/webdevelop-pro run build

Scenarios:

  • Widget is hidden when VITE_MATRIX_CHAT_ENABLED is not true.
  • Widget appears on / and not on unrelated pages.
  • First visitor message creates a session.
  • Later visitor messages use the bearer token.
  • Polling appends staff replies.
  • Polling stops on unmount.
  • Message content is escaped and never rendered as HTML.
  • Network failure shows retry state without losing the typed draft.
  • Mobile layout does not overlap the page footer or primary CTA.

Backend

Use mocked Matrix client tests for:

  • Private, non-federated room creation.
  • Staff invitations.
  • Initial visitor message relay.
  • Later visitor message relay.
  • Staff message ingestion from Matrix sync.
  • Bot-origin event ignored to prevent echo.
  • Invalid token rejected.
  • Missing session rejected.
  • Expired/closed session rejected.
  • Rate limit response.
  • Matrix API failure response and retry/log behavior.

DevOps

Run:

bash
ansible-playbook --syntax-check <matrix-support-chat-playbook>
nginx -t

Staging smoke test:

  • Deploy Synapse and support-chat API.
  • Confirm support bot can log in.
  • Send first message from website.
  • Confirm a private Matrix room is created.
  • Confirm staff users are invited.
  • Reply from Matrix.
  • Confirm the website widget receives the reply.

Assumptions

  • Staff accounts already exist or will be provisioned separately on matrix.webdevelop.pro.
  • The v1 support bot is @supportbot:matrix.webdevelop.pro.
  • The v1 relay API domain is matrix-chat.webdevelop.pro.
  • Support rooms are intentionally unencrypted in v1 because the bot must read staff replies.
  • Private support rooms are not public community chat. If a community channel is desired later, add a separate matrix.to CTA and a moderated public Matrix room.
  • The implementation can add backend and Ansible changes outside apps/webdevelop-pro.