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?