Skip to content

Communication

SCOPE_ITEM: In-portal messaging, notification system, support ticket management, and email integration for structured communication between company team and clients.

Decision Tree

IF: Communication is primarily status updates and document sharing. THEN: Activity feed + email notifications are sufficient (no messaging).

IF: Clients need to ask questions or request changes. THEN: Include in-portal messaging (conversation threads).

IF: Company has a support/helpdesk function. THEN: Include support ticket system.

IF: Clients expect structured issue tracking with SLA. THEN: Include support tickets with priority, SLA, and escalation.

IF: Company wants to eliminate email for client communication. THEN: Include messaging + email-to-portal bridge (reply by email).


In-Portal Messaging

Conversation Threads

SCOPE_ITEM: Secure messaging between client and company team.

INCLUDES: - Conversation list (sidebar or dedicated messages page). - Conversation per project (default) or general (no project context). - Text messages with basic formatting (bold, italic, links, lists). - File attachments (reuse document upload infrastructure — presigned URL, virus scan, S3 storage). - Read receipts (message marked as read when viewed). - Unread count badge in navigation. - Message timestamps (relative: "2 hours ago", absolute on hover).

OPTIONAL: - @mention team members (triggers notification to mentioned user). - Message reactions (thumbs up, acknowledged — lightweight feedback). - Message pinning (pin important messages to top of conversation). - Message search (full-text across all conversations for this client). - Message editing (within 15-minute window, show "edited" label). - Message deletion (soft delete, show "message deleted" placeholder).

Conversation Data Model

CREATE TABLE conversations (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  client_org_id UUID REFERENCES client_organisations(id),
  project_id UUID REFERENCES projects(id),  -- null for general conversations
  subject TEXT,
  created_by UUID REFERENCES users(id),
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()       -- updated on new message
);

CREATE TABLE messages (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  conversation_id UUID REFERENCES conversations(id),
  sender_id UUID REFERENCES users(id),
  content TEXT NOT NULL,
  content_html TEXT,                          -- rendered HTML (sanitised)
  attachments JSONB DEFAULT '[]',            -- [{file_name, s3_key, size, mime_type}]
  is_internal BOOLEAN DEFAULT false,         -- internal note, not visible to client
  edited_at TIMESTAMPTZ,
  deleted_at TIMESTAMPTZ,                    -- soft delete
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE message_reads (
  message_id UUID REFERENCES messages(id),
  user_id UUID REFERENCES users(id),
  read_at TIMESTAMPTZ DEFAULT now(),
  PRIMARY KEY (message_id, user_id)
);

CREATE INDEX idx_messages_conversation ON messages (conversation_id, created_at);
CREATE INDEX idx_conversations_client ON conversations (client_org_id, updated_at DESC);

Internal Notes

SCOPE_ITEM: Company team can add notes visible only to staff.

INCLUDES: - Internal note toggle on message compose. - Internal notes visually distinct (different background colour, "Internal" label). - Internal notes hidden from client users. - Used for: handover context, internal discussion, decision rationale.

CHECK: Internal note visibility MUST be enforced server-side in the API, not just hidden in the UI. Query must filter is_internal = false for client users.

Real-Time Message Delivery

SCOPE_ITEM: New messages appear without page refresh.

INCLUDES: - Polling: check for new messages every 10 seconds when conversation is open (React Query with refetchInterval). - Optimistic UI: sent message appears immediately, confirmed on server response. - New message indicator on conversation list (without full refresh).

OPTIONAL: - Server-Sent Events for instant message delivery. - Typing indicator ("Company team is typing..."). - Sound notification on new message (user preference).


Notification Centre

In-App Notifications

SCOPE_ITEM: Centralised notification hub within the portal.

INCLUDES: - Notification bell icon in header with unread count badge. - Notification dropdown/panel with recent notifications. - Notification types: - New document shared. - New message received. - Project status updated. - Milestone completed. - Invoice created. - Support ticket updated. - Upload request received. - Each notification: icon, title, brief description, timestamp, click-to-navigate to source. - Mark as read (individual and mark all).

Notification Data Model

CREATE TABLE notifications (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id),
  type TEXT NOT NULL,
  title TEXT NOT NULL,
  body TEXT,
  target_type TEXT,          -- document, message, project, invoice, ticket
  target_id UUID,
  is_read BOOLEAN DEFAULT false,
  read_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_notifications_user_unread
  ON notifications (user_id, created_at DESC) WHERE is_read = false;

Notification Preferences

OPTIONAL: SCOPE_ITEM: Per-user notification channel preferences.

INCLUDES: - Per notification type: in-app only, email only, both, none. - Email frequency: instant, daily digest, weekly digest. - Preference UI in user settings.

CHECK: Critical notifications (security, payment) should always be sent regardless of preference.


Email Notifications

Transactional Emails

SCOPE_ITEM: Automated email notifications for portal events.

INCLUDES: - Email provider: Brevo (FR) or Mailjet (FR) API (high deliverability, EU-hosted). Resend or Postmark as secondary if client explicitly requires. NOTE: Resend/Postmark are US-based services — EU data sovereignty risk. - Email template per event type (HTML + plain text). - Template variables: {{client_name}}, {{project_name}}, {{document_title}}, {{portal_url}}, {{action_url}}. - Unsubscribe link in every non-critical email. - From address: portal@{client-domain} or noreply@{company-domain}.

Email Types

Event Subject Template Recipients
Document shared New document: {{title}} All client org users
Message received New message in {{project}} Conversation participants
Project status change {{project}} status: {{status}} All client org users
Invoice created Invoice #{{number}} — EUR {{amount}} Client primary contact
Milestone completed {{project}}: {{milestone}} completed All client org users
Ticket updated Ticket #{{id}}: {{status}} Ticket requester
Upload request Please upload: {{title}} Specified client user

Email-to-Portal Bridge

OPTIONAL: SCOPE_ITEM: Reply to email notification creates a portal message.

INCLUDES: - Unique reply-to address per conversation: reply+{conversation_id}@{domain}. - Inbound email processing (webhook from Brevo/Mailjet or Resend/Postmark). - Parse email body (strip signatures, quoted text). - Create message in conversation from email content. - Attachment handling (save to S3, link to message).

CHECK: Inbound email must validate sender (email matches portal user). CHECK: Inbound email must sanitise HTML content (DOMPurify or similar). CHECK: Reply-to address must not expose internal IDs in a guessable format (use UUID or HMAC-signed tokens).


Support Ticket System

OPTIONAL: SCOPE_ITEM: Structured support request handling with status tracking.

Ticket Creation

SCOPE_ITEM: Client submits a support request.

INCLUDES: - Create ticket form: subject, description (rich text), priority (low, medium, high, urgent), category (billing, technical, general, change request). - File attachment support. - Auto-assign to team member (round-robin or category-based). - Confirmation notification to client.

Ticket Workflow

New → Assigned → In Progress → Waiting on Client → Resolved → Closed
                             ↘ Waiting on Client ↗
                                                  ↘ Reopened → In Progress

INCLUDES: - Status transitions visible to client (timeline view). - Status change notification to client. - Internal notes on ticket (not visible to client). - Ticket assignment and reassignment. - Resolution summary (visible to client on close).

OPTIONAL: - SLA tracking per priority: - Urgent: first response 2h, resolution 8h. - High: first response 4h, resolution 24h. - Medium: first response 8h, resolution 48h. - Low: first response 24h, resolution 5 business days. - SLA breach notification to company team. - Canned responses (pre-written replies for common issues). - Satisfaction rating after ticket closure (1-5 stars + comment). - Ticket merging (combine duplicate tickets).

Ticket Data Model

CREATE TABLE support_tickets (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  client_org_id UUID REFERENCES client_organisations(id),
  project_id UUID REFERENCES projects(id),
  subject TEXT NOT NULL,
  description TEXT NOT NULL,
  priority TEXT DEFAULT 'medium',  -- low, medium, high, urgent
  category TEXT NOT NULL,
  status TEXT DEFAULT 'new',       -- new, assigned, in_progress, waiting_client, resolved, closed
  assigned_to UUID REFERENCES users(id),
  created_by UUID REFERENCES users(id),
  first_response_at TIMESTAMPTZ,
  resolved_at TIMESTAMPTZ,
  closed_at TIMESTAMPTZ,
  satisfaction_rating INTEGER,     -- 1-5
  satisfaction_comment TEXT,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE ticket_comments (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  ticket_id UUID REFERENCES support_tickets(id),
  author_id UUID REFERENCES users(id),
  content TEXT NOT NULL,
  is_internal BOOLEAN DEFAULT false,  -- internal note
  attachments JSONB DEFAULT '[]',
  created_at TIMESTAMPTZ DEFAULT now()
);

Knowledge Base / FAQ

OPTIONAL: SCOPE_ITEM: Self-service help content to reduce ticket volume.

INCLUDES: - FAQ page with categorised questions and answers. - Search within FAQ. - Admin CMS for FAQ content management. - "Was this helpful?" feedback on each article. - Suggested articles when creating a ticket (based on subject).


Email Integration

Outbound Email

SCOPE_ITEM: All portal notifications sent via transactional email service.

INCLUDES: - Brevo (FR) or Mailjet (FR) as email provider. Resend/Postmark as secondary (US-based — EU data sovereignty risk). - Email template management (React Email or MJML for responsive HTML). - Delivery tracking (sent, delivered, bounced, complained). - Bounce handling (deactivate notification for hard bounces). - SPF, DKIM, DMARC configured for sender domain.

CHECK: Email sending must be async (BullMQ job, not inline with API response). CHECK: Email templates must be tested across major clients (Gmail, Outlook, Apple Mail). CHECK: Unsubscribe link required in all non-critical emails (CAN-SPAM, GDPR).

Company Branding in Emails

INCLUDES: - Company logo in email header. - Company colour scheme. - Company name in sender display name. - Footer: company name, address, unsubscribe link.

OPTIONAL: - Per-client branding in emails (if white-label portal).


Scoping Questions

CHECK: Is in-portal messaging needed or just email notifications? CHECK: Does the client need internal notes (company-only comments)? CHECK: Is a support ticket system needed? CHECK: What are the SLA targets for ticket response/resolution? CHECK: Is email-to-portal reply (inbound email) needed? CHECK: Is a knowledge base / FAQ section needed? CHECK: What notification channels are needed (in-app, email, both)? CHECK: Does the company need to brand the portal and emails? CHECK: Is real-time message delivery needed or is polling sufficient? CHECK: How many concurrent conversations expected per client?