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
112 lines
5.1 KiB
Markdown
112 lines
5.1 KiB
Markdown
# PRD — Email Attachments → Ticket Documents
|
||
|
||
- Slug: `2026-01-11-email-attachments-to-tickets`
|
||
- Date: `2026-01-11`
|
||
- Status: Draft
|
||
|
||
## Summary
|
||
|
||
When an inbound email (new ticket or reply) includes attachments, automatically store those attachments in Alga file storage and associate them as Documents on the relevant ticket.
|
||
|
||
## Problem
|
||
|
||
Today, inbound email processing detects attachments and calls `process_email_attachment`, but the current action implementation only creates a document association record (metadata) and does not reliably download and store the actual attachment content in Alga’s file storage. This leaves tickets without usable attachment files.
|
||
|
||
## Goals
|
||
|
||
- For **new tickets** created from inbound emails: attach all eligible email attachments as ticket documents.
|
||
- For **replies** that thread onto an existing ticket: attach all eligible email attachments as ticket documents.
|
||
- Make attachment ingestion **idempotent** (safe across webhook duplicates, workflow retries, and worker restarts).
|
||
- Support both **Microsoft Graph** and **Gmail** providers.
|
||
|
||
## Non-goals
|
||
|
||
- Inline image rendering inside the email/comment body (CID embedding) beyond storing the files as documents.
|
||
- Antivirus scanning, DLP, or content moderation (unless explicitly requested).
|
||
- Rewriting or re-architecting the document system beyond what’s required to store and associate attachments.
|
||
|
||
## Users and Primary Flows
|
||
|
||
### Flow A — New ticket from inbound email w/ attachments
|
||
|
||
1. Email arrives and is converted into a ticket.
|
||
2. Attachments are downloaded from the email provider.
|
||
3. Each attachment is stored in `file_stores` and linked to a `documents` record.
|
||
4. Each document is associated to the ticket (`document_associations`).
|
||
|
||
### Flow B — Reply email threaded onto existing ticket w/ attachments
|
||
|
||
1. Email reply arrives and is added as a ticket comment.
|
||
2. Attachments are downloaded and stored as above.
|
||
3. Documents are associated to the existing ticket.
|
||
|
||
## UX / UI Notes
|
||
|
||
- Ticket Documents UI already exists; newly attached documents should appear there without additional UI changes.
|
||
- (Optional) If feasible, include the source email metadata (provider/messageId) in document details for traceability.
|
||
|
||
## Requirements
|
||
|
||
### Functional Requirements
|
||
|
||
- Download attachment content for Microsoft/Gmail based on `emailData.id` (message id) and `emailData.attachments[].id` (attachment id).
|
||
- Create a stored file entry in Alga storage for each attachment and link it to a `documents` row.
|
||
- Associate each created document to the ticket.
|
||
- Enforce a maximum attachment size of **100 MB** (100 * 1024 * 1024 bytes).
|
||
- Allow all file types (no extension/mime blocklist).
|
||
- Skip inline/CID attachments by default.
|
||
- Use the system user as the uploader/creator for attachment-ingested documents.
|
||
- Strict idempotency: repeated processing of the same `{tenant, providerId, emailId, attachmentId}` must not create duplicates, even across retries and worker restarts.
|
||
- Failure handling: attachment failures must not prevent ticket creation / comment ingestion; errors should be recorded for later troubleshooting.
|
||
|
||
### Non-functional Requirements
|
||
|
||
- Avoid large memory spikes (attachment downloads can be big); apply safe limits.
|
||
- Ensure all writes on Citus distributed tables include `tenant` where required to avoid scatter/gather and tenant RLS issues.
|
||
|
||
## Data / API / Integrations
|
||
|
||
### Microsoft Graph
|
||
|
||
- Need a reliable “download attachment bytes” API call per attachment id.
|
||
- Must handle different attachment types (e.g. file attachments vs item/reference attachments); skip unsupported types initially.
|
||
|
||
### Gmail
|
||
|
||
- Need to call Gmail “get attachment” API using the message id + attachment id.
|
||
|
||
### Storage / Documents
|
||
|
||
- Persist bytes via the existing storage layer (`file_stores` + backing provider).
|
||
- Create `documents` rows pointing at the stored `file_id`.
|
||
- Create `document_associations` rows linking to the ticket.
|
||
|
||
## Security / Permissions
|
||
|
||
- Uploader/creator attribution: **system user**.
|
||
- Attachment filtering rules:
|
||
- max size: **100 MB**
|
||
- blocked mime types / extensions: **none**
|
||
- inline/CID: **skipped by default**
|
||
|
||
## Rollout / Migration
|
||
|
||
- Add a new idempotency table for attachment ingestion (`email_processed_attachments`) with a unique constraint on `{tenant, provider_id, email_id, attachment_id}`.
|
||
- Backfill is not required initially; new inbound emails should work after deploy.
|
||
|
||
## Open Questions
|
||
|
||
Resolved:
|
||
1. Inline/CID attachments: **skip by default**.
|
||
2. Max attachment size: **100 MB**.
|
||
3. File type policy: **allow all**.
|
||
4. Uploader attribution: **system user**.
|
||
5. Idempotency/audit: **strict**, via `email_processed_attachments`.
|
||
|
||
## Acceptance Criteria (Definition of Done)
|
||
|
||
- Given an inbound email with 1+ attachments, the resulting ticket shows those files in the Ticket Documents section, and each document has a stored file in `file_stores`.
|
||
- Given a threaded reply email with attachments, the existing ticket gains those documents.
|
||
- Reprocessing the same email/webhook does not create duplicate documents for the same attachment.
|
||
- A failure to fetch/store an attachment does not prevent ticket/comment creation; the failure is recorded for debugging.
|