Hermes 284313f908
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
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

9.1 KiB
Raw Blame History

Scratchpad — 2026-01-24 Inbound Email In-App Processing

Context / Decision

  • We are choosing Option B for now: bypass workflows entirely and call existing email “domain” functions directly in the webhook request flow.
  • Goal remains to make workflow-worker v2-only and to remove inbound-email dependency on legacy workflow runtimes.

Current wiring notes (file pointers)

  • Gmail webhook route: server/src/app/api/email/webhooks/google/route.tspackages/integrations/src/webhooks/email/google.ts
  • Microsoft webhook route: server/src/app/api/email/webhooks/microsoft/route.tspackages/integrations/src/webhooks/email/microsoft.ts
  • V2 email workflow definition exists (not used in Option B): shared/workflow/runtime/workflows/email-processing-workflow.v2.json
  • Legacy email workflow exists (not used in Option B): shared/workflow/workflows/system-email-processing-workflow.ts
  • Domain helpers we will call:
    • shared/workflow/actions/emailWorkflowActions.ts
    • HTML conversion utility: @alga-psa/shared/lib/utils/contentConversion (see shared/workflow/init/registerWorkflowActions.ts for how its used)

Commands I used during investigation

  • rg -n "INBOUND_EMAIL_RECEIVED|webhooks/email|email-processing-workflow.v2.json" -S server packages shared services
  • sed -n '1,220p' shared/workflow/actions/emailWorkflowActions.ts

Open questions to resolve before implementation

  • What do we do for unmatched senders (no contact match)?
  • What is the canonical idempotency key per provider (Gmail historyId vs messageId)?
  • Do we want comment bodies stored as BlockNote JSON (preferred), or raw text/html?

Progress log

  • 2026-01-24: Implemented F001 by defining the normalized processInboundEmailInApp contract/types in shared/services/email/processInboundEmailInApp.ts (input + union result shape).
  • 2026-01-24: Implemented F002 by adding processInboundEmailInApp() in shared/services/email/processInboundEmailInApp.ts with in-app routing (existing ticket reply vs new-ticket creation).
  • 2026-01-24: Implemented F003 by invoking parseEmailReplyBody() and falling back to raw body content on parser failures (logs + continue).
  • 2026-01-24: Implemented F004 by resolving existing tickets via reply token first (if present), then falling back to thread headers via findTicketByEmailThread.
  • 2026-01-24: Implemented F005 by creating reply comments with BlockNote JSON content (HTML→blocks when available, text→blocks otherwise) and storing message threading metadata on the comment.
  • 2026-01-24: Implemented F006 by processing reply attachments per item via processEmailAttachment with error-continue semantics.
  • 2026-01-24: Implemented F010 by requiring inbound ticket defaults per provider via resolveInboundTicketDefaults(tenantId, providerId) and skipping processing when missing.
  • 2026-01-24: Implemented F011 by matching sender email to an existing contact via findContactByEmail and using its client_id/contact_id on ticket creation.
  • 2026-01-24: Implemented F012 decision: if sender email does not match an existing contact, create the ticket under provider defaults client_id with contact_id unset, and mark the initial comment metadata with unmatchedSender: true for manual triage.
  • 2026-01-24: Implemented F013 by creating tickets via createTicketFromEmail and persisting tickets.email_metadata (messageId/threadId/inReplyTo/references/providerId) for future threading queries.
  • 2026-01-24: Implemented F014 by creating the initial ticket comment via createCommentFromEmail with BlockNote JSON content derived from the inbound email body.
  • 2026-01-24: Implemented F015 by processing new-ticket attachments per item via processEmailAttachment with error-continue semantics.
  • 2026-01-24: Implemented F020 by de-duping reply comment creation via a DB lookup keyed on {tenant, ticket_id, metadata.email.messageId} before inserting.
  • 2026-01-24: Implemented F021 by de-duping new-ticket creation via a DB lookup on tickets.email_metadata.messageId+providerId before creating a ticket.
  • 2026-01-24: Implemented F022 by re-enabling Gmail Pub/Sub historyId de-dupe (skip when gmail_processed_history already contains the notification historyId). Microsoft continues using email_processed_messages PK de-dupe.
  • 2026-01-24: Implemented F030 by wiring Gmail webhook message handling to call processInboundEmailInApp when the in-app flag is enabled (otherwise preserves legacy INBOUND_EMAIL_RECEIVED publish).
  • 2026-01-24: Implemented F031 by wiring Microsoft webhook notifications to call processInboundEmailInApp when enabled and to persist ticket linkage back to email_processed_messages.
  • 2026-01-24: Implemented F040 by adding env-driven, tenant/provider-scoped gating in shared/services/email/inboundEmailInAppFeatureFlag.ts (INBOUND_EMAIL_IN_APP_PROCESSING_ENABLED, INBOUND_EMAIL_IN_APP_TENANT_IDS, INBOUND_EMAIL_IN_APP_PROVIDER_IDS).
  • 2026-01-24: Implemented F041 by supporting provider-level rollout via INBOUND_EMAIL_IN_APP_PROVIDER_IDS allowlist.
  • 2026-01-24: Implemented F050 by defaulting workflow-worker deployments to WORKFLOW_WORKER_MODE=v2 in compose configs (docker-compose.ce.yaml, docker-compose.ee.yaml, and prebuilt variants).
  • 2026-01-24: Implemented F051 by disabling legacy system email workflow updates by default in services/workflow-worker/src/init/updateWorkflows.ts (requires LEGACY_SYSTEM_EMAIL_WORKFLOW_ENABLED=true to opt back in).
  • 2026-01-24: Implemented T001 with an integration test covering Gmail webhook → in-app processing path and asserting exactly one ticket and one initial comment (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T002 with an integration test covering Microsoft webhook → in-app processing path and asserting exactly one ticket and one initial comment (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T003 by adding an integration test for reply-token threading to ensure the in-app processor creates exactly one comment on the matched ticket (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T004 by adding an integration test for header-based threading (In-Reply-To/References) to ensure the in-app processor creates exactly one comment on the matched ticket (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T010 with a unit test ensuring parseEmailReplyBody produces non-empty sanitized text for plain-text emails (server/src/test/unit/email/inboundEmailBodyParsing.test.ts).
  • 2026-01-24: Implemented T011 with a unit test ensuring parseEmailReplyBody produces sanitized HTML when HTML input is present (server/src/test/unit/email/inboundEmailBodyParsing.test.ts).
  • 2026-01-24: Implemented T020 with an integration test verifying sender→contact matching overrides default client/contact on ticket creation (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T021 with an integration test verifying unmatched senders create a ticket under provider defaults without a contact and without throwing (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T022 with an integration test verifying missing inbound ticket defaults causes processing to skip without creating tickets/comments (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T030 with an integration test verifying attachment processing errors do not prevent ticket + initial comment creation (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T031 with an integration test verifying attachment processing errors do not prevent reply comment creation (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T040 with an integration test verifying reply idempotency (replaying the same email id does not create duplicate comments) (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T041 with an integration test verifying new-email idempotency (replaying the same email id does not create duplicate tickets/comments) (server/src/test/integration/inboundEmailInApp.webhooks.integration.test.ts).
  • 2026-01-24: Implemented T042 with an integration test ensuring the env-based feature flag switches Gmail webhook behavior between legacy publish and in-app processing (server/src/test/integration/inboundEmailInApp.featureFlag.integration.test.ts).
  • 2026-01-24: Implemented T050 with a smoke integration test asserting compose defaults WORKFLOW_WORKER_MODE=v2 and that processInboundEmailInApp successfully creates a ticket/comment without any worker running (server/src/test/integration/workflowWorkerV2.inboundEmailSmoke.integration.test.ts).
  • 2026-01-24: Test env note: the three DB-backed integration tests in this plan now auto-skip when the configured Postgres host/port is unreachable (to avoid hard failures in dev setups without a local test DB).