Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
8.0 KiB
8.0 KiB
PRD — Ticket Contact Commentors + Inbound Email Contact Authorship
- Slug:
contact-commentors-inbound-email - Date:
2026-02-11 - Status: Draft
Summary
Add first-class support for ticket comments authored by a contact that does not have a users record.
For inbound email processing, when sender email matches a contact, create/attach comments as that contact instead of falling back to unknown-user display.
This work has two connected scopes:
- Ticket comments: represent and render contact-authored comments without requiring a client-portal user record.
- Inbound email: use the contact-authorship capability in new-ticket and reply flows.
Problem
Today, ticket comment display logic is user-centric:
comments.user_idis the primary identity used by ticket detail UIs.- Conversation rendering builds
userMaponly fromusers. - Contact-authored comments without
user_idare rendered asUnknown User.
Inbound email processing can already match a sender to a contact, but if that contact has no linked client user, comment identity is incomplete in the UI and APIs.
Goals
- Support ticket comments authored by contacts who are not users.
- Persist explicit contact authorship linkage on comments.
- Render contact identity (name/avatar/email, with clear contact semantics) in ticket conversation UI.
- Ensure inbound email flows write comments with contact authorship when sender matches a contact.
- Keep existing behavior for internal comments and client-user-authored comments intact.
- Keep response-state behavior intact (
client/contactreplies still move ticket toawaiting_internal).
Non-goals
- Creating or inviting client-portal users automatically for unmatched contacts.
- Redesigning ticket conversation UX beyond author identity display.
- Reworking mention systems, permissions model, or comment edit/delete policy.
- Adding ticket-list/table filtering for contact-authored comments.
- Full historical backfill of legacy unknown comments in this phase.
Users and Primary Flows
Flow A — New inbound email from known contact (no user)
- System receives inbound email for configured mailbox.
- Sender email matches an existing contact with no linked
usersrow. - Ticket is created (or routed as configured).
- Initial comment is stored with contact authorship.
- Ticket UI shows contact identity instead of
Unknown User.
Flow B — Reply inbound email on existing ticket from known contact (no user)
- System resolves ticket by reply token/thread headers.
- Sender email matches contact without user.
- Reply comment is appended with contact authorship.
- Ticket UI shows contact author and response state updates as client-side reply.
Flow C — Contact has linked client user
- Existing matched client-user behavior remains valid.
- Comment continues to store user linkage.
- UI still shows user identity as today.
Flow D — Sender cannot be matched to contact
- Existing unmatched behavior is preserved.
- UI fallback remains
Unknown Useronly when neither user nor contact author can be resolved.
UX / UI Notes
- In MSP ticket conversation (
CommentItem/TicketConversation), contact-authored comments should display:- contact avatar treatment,
- contact full name,
- optional contact email line when available.
- Unknown-user fallback should only occur when both user and contact are unavailable.
- Keep existing comment badges/metadata display (internal/resolution/response source) unchanged.
- Do not change who can edit/delete comments in this phase; those checks remain tied to authenticated user identity.
Requirements
Functional Requirements
- Add a nullable tenant-scoped contact reference on comments (e.g.,
comments.contact_id) and enforce FK integrity tocontacts. - Extend shared comment types and model inputs to carry contact authorship (
contact_id) in addition to existing user authorship (author_id/user_id). - Update shared ticket comment creation logic to persist contact reference when provided.
- Ensure comment validation enforces tenant-safe contact references; reject invalid or cross-tenant contacts.
- Keep author-type semantics aligned: external/customer comments remain client-side comments for response-state behavior.
- Update inbound email new-ticket flow to pass matched contact ID on initial comment creation, even when no user is found.
- Update inbound email reply flows (reply-token + thread-header paths) to resolve sender contact and pass contact ID for comment creation.
- Preserve matched client-user behavior by continuing to pass
author_idwhen available. - Update workflow runtime email action schemas/wiring to pass through contact authorship fields.
- Update ticket details data assembly (
optimizedTicketActionsand client-portal equivalent) to build author display data from both users and contacts. - Update conversation/comment UI components to resolve and render contact-authored comments without user records.
- Update ticket API comment payload shape as needed so contact-authored comments can be represented without invalid UUID assumptions for
created_by. - Preserve existing behavior for internal/system comments and unknown fallback paths.
- Add unit/integration/UI coverage for contact-only authorship across ticket rendering and inbound email paths.
Non-functional Requirements
- No tenant isolation regressions.
- No breaking behavior for existing comments that lack contact authorship fields.
- Query changes should remain bounded and avoid N+1 lookups in ticket detail loading.
Data / API / Integrations
- Likely data model change: add
comments.contact_id(nullable) with tenant-scoped FK tocontacts(contact_name_id). - Shared types to extend:
packages/types/src/interfaces/comment.interface.tsshared/models/ticketModel.tscreate-comment input/output shapes- email workflow action inputs where comments are created from inbound email
- Ticket detail loaders to enrich author data from
contactswhencomments.user_idis null butcomments.contact_idis set. - API layer (
TicketService+ ticket comment schemas) may need shape updates to represent contact authors cleanly.
Security / Permissions
- Contact linkage writes must be server-side and tenant-scoped.
- Validate contact belongs to the same tenant before persisting.
- If ticket-client enforcement is required, validate contact client alignment with ticket client in write path.
Observability
- No new telemetry/metrics requirements in this phase (core functionality only).
Rollout / Migration
- Add migration for comment contact linkage column and FK/index.
- No mandatory historical backfill in this phase.
- Existing comments continue to render with fallback logic.
Open Questions
- Should contact-authored comments display as
Name (Contact)or justNamewith contact avatar styling? - Should we always store
contact_idwhenauthor_idis present (denormalized dual linkage), or only when no user exists? - For replies where sender contact cannot be resolved, should we fallback to ticket-level
contact_name_idas author, or keep unknown? - Which API contract do we want for comment author output: additive fields (
author_contact_id,author_name) vs richer nestedauthorobject? - Should contact-authored comments be visible in client portal exactly as today (if ticket is visible), or gated differently?
Acceptance Criteria (Definition of Done)
- A contact-only sender (no
usersrow) replying by inbound email creates a comment that is rendered as that contact in ticket conversation. - A contact-only sender creating a new inbound-email ticket produces initial comment authored by contact (not unknown fallback).
- Existing matched client-user inbound behavior continues to work and remains associated to user identity.
- Existing unmatched sender behavior remains stable with unknown fallback.
- Response-state transitions remain unchanged for client/contact replies.
- MSP ticket details and client-portal ticket details both render contact-authored comments without regressions.