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
149 lines
8.0 KiB
Markdown
149 lines
8.0 KiB
Markdown
# 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:
|
|
|
|
1. Ticket comments: represent and render contact-authored comments without requiring a client-portal user record.
|
|
2. Inbound email: use the contact-authorship capability in new-ticket and reply flows.
|
|
|
|
## Problem
|
|
|
|
Today, ticket comment display logic is user-centric:
|
|
|
|
- `comments.user_id` is the primary identity used by ticket detail UIs.
|
|
- Conversation rendering builds `userMap` only from `users`.
|
|
- Contact-authored comments without `user_id` are rendered as `Unknown 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/contact` replies still move ticket to `awaiting_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)
|
|
|
|
1. System receives inbound email for configured mailbox.
|
|
2. Sender email matches an existing contact with no linked `users` row.
|
|
3. Ticket is created (or routed as configured).
|
|
4. Initial comment is stored with contact authorship.
|
|
5. Ticket UI shows contact identity instead of `Unknown User`.
|
|
|
|
### Flow B — Reply inbound email on existing ticket from known contact (no user)
|
|
|
|
1. System resolves ticket by reply token/thread headers.
|
|
2. Sender email matches contact without user.
|
|
3. Reply comment is appended with contact authorship.
|
|
4. Ticket UI shows contact author and response state updates as client-side reply.
|
|
|
|
### Flow C — Contact has linked client user
|
|
|
|
1. Existing matched client-user behavior remains valid.
|
|
2. Comment continues to store user linkage.
|
|
3. UI still shows user identity as today.
|
|
|
|
### Flow D — Sender cannot be matched to contact
|
|
|
|
1. Existing unmatched behavior is preserved.
|
|
2. UI fallback remains `Unknown User` only 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
|
|
|
|
1. Add a nullable tenant-scoped contact reference on comments (e.g., `comments.contact_id`) and enforce FK integrity to `contacts`.
|
|
2. Extend shared comment types and model inputs to carry contact authorship (`contact_id`) in addition to existing user authorship (`author_id`/`user_id`).
|
|
3. Update shared ticket comment creation logic to persist contact reference when provided.
|
|
4. Ensure comment validation enforces tenant-safe contact references; reject invalid or cross-tenant contacts.
|
|
5. Keep author-type semantics aligned: external/customer comments remain client-side comments for response-state behavior.
|
|
6. Update inbound email new-ticket flow to pass matched contact ID on initial comment creation, even when no user is found.
|
|
7. Update inbound email reply flows (reply-token + thread-header paths) to resolve sender contact and pass contact ID for comment creation.
|
|
8. Preserve matched client-user behavior by continuing to pass `author_id` when available.
|
|
9. Update workflow runtime email action schemas/wiring to pass through contact authorship fields.
|
|
10. Update ticket details data assembly (`optimizedTicketActions` and client-portal equivalent) to build author display data from both users and contacts.
|
|
11. Update conversation/comment UI components to resolve and render contact-authored comments without user records.
|
|
12. Update ticket API comment payload shape as needed so contact-authored comments can be represented without invalid UUID assumptions for `created_by`.
|
|
13. Preserve existing behavior for internal/system comments and unknown fallback paths.
|
|
14. 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 to `contacts(contact_name_id)`.
|
|
- Shared types to extend:
|
|
- `packages/types/src/interfaces/comment.interface.ts`
|
|
- `shared/models/ticketModel.ts` create-comment input/output shapes
|
|
- email workflow action inputs where comments are created from inbound email
|
|
- Ticket detail loaders to enrich author data from `contacts` when `comments.user_id` is null but `comments.contact_id` is 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
|
|
|
|
1. Should contact-authored comments display as `Name (Contact)` or just `Name` with contact avatar styling?
|
|
2. Should we always store `contact_id` when `author_id` is present (denormalized dual linkage), or only when no user exists?
|
|
3. For replies where sender contact cannot be resolved, should we fallback to ticket-level `contact_name_id` as author, or keep unknown?
|
|
4. Which API contract do we want for comment author output: additive fields (`author_contact_id`, `author_name`) vs richer nested `author` object?
|
|
5. 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 `users` row) 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.
|