Communications Inbox Product Contract
Purpose
The communications inbox is the human back-and-forth layer that connects client-owned work across the organization app and client portal. It is not a second task system, a free-standing chat product, or another presentation of system notifications.
The canonical mental model is:
Shared conversation threads, personal inbox state, and current access checked on every read, reply, and email delivery.
Tasks, requests, files, updates, and agreements remain the owning business records. A communication thread supplies their common conversation history and inbox projection.
Product boundaries
Communications versus notifications
- Communications are messages written by an organization member or client and are expected to support a reply.
- Notifications report system activity, deadlines, lifecycle changes, billing events, or other one-way signals.
- A source event may produce both kinds when both outcomes are useful, but the inboxes must not contain duplicate cards for the same human message.
- The communications drawer only lists
communicationentries. The notifications drawer excludes them from its primary mental model.
Threads versus inbox entries
- One
communicationThreadis the shared history for one source record. - One
notificationInboxcommunication entry is the personal projection of that thread for one user and one surface. - Read and archive state belong to the personal projection. They are never shared across participants.
- Replying adds one message to the shared thread and updates each currently participating person's projection.
- A new message restores an archived personal projection for active participants. Archiving is therefore inbox cleanup, not unsubscribe or shared resolution.
Participant rules
Participation determines who should receive a conversation. Authorization is a separate, stricter check. Being recorded in a historical participant array never overrides current organization membership, client assignment, portal grant, portal scope, tenant selection, or source visibility.
| Source | Organization participants | Client participants |
|---|---|---|
| Project task | Project owner, task assignee, mentioned members, organization authors | Active full or delivery portal users when both project and task are client-visible |
| Client support/work/project request | Organization owner, assigned request owner, organization responders | The client user who submitted the request |
| File request | Organization owner, project owner when present, requester, organization responders | Active full or delivery portal users |
| Project update | Organization owner, project owner, configured relationship contacts | Active full or delivery portal users |
| Shared drive item | Organization owner, project owner when present, configured relationship contacts | Active full or delivery portal users |
| Published contract | Organization owner, project owner when present, configured relationship contacts | Active portal users because Agreements is available to every portal scope |
The quote source type is reserved by the communication contract. When a quote
producer is connected, only full and billing portal users may participate.
Participant changes follow these rules:
- Organization participation is additive so a member who took part in a conversation retains its history while they remain an active member with access to that client.
- Current membership and client assignment are still revalidated. Removing a member or their client access removes inbox, drawer, and email access immediately even if their historical participant ID remains.
- Current portal grants and scopes are revalidated. Disabling a grant, changing
deliveryaccess tobilling, disabling the portal, or switching the active client company takes effect immediately. - Mentions route attention among already eligible organization members. A mention must never bypass tenant or client access.
- Granting a new person access does not manufacture unread work from before they joined. They become an inbox participant on the next eligible conversation event; the owning record remains their historical source of truth.
Client portal behavior
The client portal is a shared client workspace, not one shared mailbox.
- Eligible client participants see the same thread history.
- Each signed-in client user has independent read and archive state.
- One client user's read or archive action cannot clear another user's inbox.
- A support, work, or project request stays with its requester unless an explicit future client-sharing feature expands it.
- Delivery conversations are not visible to billing-only users.
- Agreements are visible to all portal scopes; quotes are excluded from delivery-only access.
- In a multi-company portal identity, the drawer only exposes conversations for the currently selected client account.
Organization behavior
- Each organization member has a personal communications inbox.
- A member sees only threads in which they participate and only while they have current access to the owning client.
- Reassignment does not erase conversation history for a former participant who still has legitimate client access.
- Removing organization membership or client assignment immediately removes drawer, reply, and email access.
- A future team-supervision view may expose broader routing, but V1 does not turn every client conversation into organization-wide noise.
Source visibility and lifecycle
Portal authorization rechecks the owning source every time:
- Project tasks require a client-visible task inside a client-visible, non-archived project.
- Project updates require published, non-archived updates.
- Drive conversations require a shared, non-archived, non-folder item.
- Contracts require published and shared state.
- Quotes require a non-draft state.
- Client and file requests must still exist and belong to the same organization and client account as the thread.
If the source is hidden, unpublished, archived where applicable, deleted, moved to another tenant/client, or otherwise becomes ineligible, the portal inbox and thread fail closed. Organization history may remain available to authorized staff for audit continuity.
A task with conversation history cannot be permanently deleted. Other source types follow their existing published-history, submission-history, or archival retention rules.
Message and inbox state rules
- Empty messages are rejected after trimming.
- Messages are capped at 10,000 characters.
- Source message keys and provider IDs make source projection and inbound email processing idempotent.
- The author sees their own sent message as read.
- Every other active participant receives an unread projection, including same-side colleagues.
- Opening the conversation marks only the viewer's projection read.
- Archiving marks only the viewer's projection archived and read.
- A later message unarchives the projection for current participants.
- A reply reopens a resolved thread. Shared resolve controls are not exposed in V1; personal archive is the shipped completion gesture.
- An older migrated or delayed source message is retained in chronological history but cannot replace a newer thread preview, sort timestamp, or inbox state.
Email delivery and replies
Email is a transport for the same thread, not a parallel conversation.
- Each non-author participant may receive an email after an in-app message.
- Organization delivery honors
customerActivityEmails. - Portal delivery honors
supportEmails. - Disabling email does not remove the in-app conversation.
- Email is sent only after current membership/grant, client access, participant status, portal scope, and source visibility are revalidated.
- Each Reply-To alias is signed for the exact thread, recipient user, and surface. Separate organization and portal identities may therefore use the same email address without replies being attributed to the wrong realm.
- The Postmark inbound webhook verifies deployment-owned HTTP Basic authentication, validates the application-signed alias, checks the sender address against that exact recipient identity, revalidates current access, removes common quoted history, and appends one message to the shared thread.
- Forwarded replies, revoked participants, stale scopes, altered aliases, duplicate webhook events, and unknown senders fail closed.
- Email replies mirror back into task comments or client-request messages where that owning source supports native messages.
- Provider
MessageIDvalues are deduplication keys. Retries are safe and a duplicate delivery cannot append a second human message.
Live reply-by-email additionally requires the deployment configuration listed
in docs/dev/integrations/optional-features.md.
Retention and privacy
- Thread messages are retained with the owning business history unless the source's explicit retention policy permits deletion.
- Account purge keeps the work history but replaces the author's snapshot name
with
Deleted userand removes stored author and inbound-receipt email data. - Deleting an inbox projection never deletes the shared thread or its messages.
- A deleted or currently unauthorized user cannot receive new email or submit an email reply through a retained participant reference.
Non-goals for V1
- No unlinked direct messages or free-standing channels.
- No shared read/archive state.
- No organization-wide or client-wide supervisor mailbox.
- No automatic participant expansion merely because someone can view the client account.
- No client-private side conversation inside an organization thread.
- No inbound email attachments in V1. Postmark attachment payloads are discarded; clients use the owning task or file request when a file must become project data.
- No replacement for the owning task, request, file, agreement, or project lifecycle.
Release invariants
The feature is correct only while all of the following remain true:
- One source record maps to at most one thread per organization and source type.
- One user and surface maps to at most one inbox projection per thread.
- Read and archive changes never affect another participant.
- Current access is checked independently of historical participation.
- Portal source visibility is checked on list, count, open, reply, outbound email, and inbound email paths.
- Email identity is bound to both recipient and account realm.
- Duplicate source events and email webhooks cannot duplicate messages.
- Historical backfill cannot move a thread backward in time.
- Account purge removes retained communication email identity data.
- Human communication and system notifications remain separate inbox mental models.
Implementation map
- Thread and message model:
packages/backend/convex/communications/model.ts - Current-access and source checks:
packages/backend/convex/communications/access.ts - Source participant adapters:
packages/backend/convex/communications/sources.ts - Organization API:
packages/backend/convex/org/communications.ts - Portal API:
packages/backend/convex/portal/communications.ts - Email transport and inbound processing:
packages/backend/convex/communicationsNodeActions.tsandpackages/backend/convex/communications.ts - Personal inbox projections:
packages/backend/convex/notifications/inbox.ts - Shared drawer UI:
apps/app/src/app/[locale]/_components/communications/communication-inbox-sheet.tsx - Regression coverage:
packages/backend/tests/communications/thread-model.test.ts