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
120 lines
6.5 KiB
Markdown
120 lines
6.5 KiB
Markdown
# Scratchpad — Inbound Email Rules
|
||
|
||
- Plan slug: `2026-06-10-inbound-email-rules`
|
||
- Created: `2026-06-10`
|
||
|
||
## What This Is
|
||
|
||
Rolling log of discoveries and decisions while implementing inbound email rules.
|
||
Approved design: `docs/plans/2026-06-10-inbound-email-rules-design.md` (commit fd14d3e445).
|
||
|
||
## Decisions
|
||
|
||
- (2026-06-10) Inline rules engine in `processInboundEmailInApp()`, not
|
||
workflow-compiled and not a generic automation framework. In-app processing
|
||
already replaced workflow-based email handling; generic framework is premature
|
||
with one consumer.
|
||
- (2026-06-10) Tenant-wide ordered rules with optional per-rule `provider_ids`
|
||
filter. Board is an *output* of rules (via destination/defaults), never a scope —
|
||
board isn't known until after rules run.
|
||
- (2026-06-10) New-ticket path only; replies that thread onto tickets bypass rules
|
||
so skip patterns can't eat genuine replies.
|
||
- (2026-06-10) Client matching is exact-normalized + per-client aliases
|
||
(`client_name_aliases`). Fuzzy matching rejected: wrong match = ticket (and its
|
||
contents) on the wrong client.
|
||
- (2026-06-10) `on_no_match = proceed` continues down the rules list (not straight
|
||
to the normal pipeline) — enables "regex rule first, AI catch-all later" so AI
|
||
only burns tokens when deterministic extraction fails.
|
||
- (2026-06-10) Rule-assigned client beats sender contact/domain matching — the
|
||
sender is a service (e.g. alerts@huntress.com), not the client.
|
||
- (2026-06-10) AI never picks a client_id; it returns `extracted_client_name` and
|
||
the deterministic matcher resolves it. No client list in the prompt.
|
||
- (2026-06-10) Packaging: rules engine CE; only `ai_classify` gated on EE + AI
|
||
add-on. AI action is visible-but-disabled (upsell tease) without the add-on —
|
||
explicit user request.
|
||
- (2026-06-10) Live tester calls the real shared evaluator via a server action; no
|
||
parallel test implementation.
|
||
|
||
## Discoveries / Constraints
|
||
|
||
- (2026-06-10) Migration conventions: composite `(tenant, id)` PK, best-effort
|
||
`create_distributed_table('<table>', 'tenant')`, `exports.config = { transaction:
|
||
false }`, functional unique indexes on `lower(...)` — mirror
|
||
`server/migrations/20260213180500_create_client_inbound_email_domains.cjs`.
|
||
- (2026-06-10) `email_processed_messages.processing_status` has a CHECK constraint
|
||
(`success|failed|partial`) — adding `skipped` requires dropping/re-adding the
|
||
constraint (migration `20250130200000` created it).
|
||
- (2026-06-10) Unknown senders are currently dropped as
|
||
`skipped/missing_defaults` in `processInboundEmailInApp.ts` — running rules
|
||
*before* defaults resolution means skip rules work for tenants with no defaults.
|
||
- (2026-06-10) EE dynamic-load pattern to copy:
|
||
`shared/services/email/inboundReplyAcknowledgementDecider.ts` (OSS stub + EE
|
||
module).
|
||
- (2026-06-10) Domain-match contact fallback today uses
|
||
`findValidClientPrimaryContactId` (clients.properties.primary_contact_id) in
|
||
`shared/workflow/actions/emailWorkflowActions.ts` — reuse for rule-assigned
|
||
clients.
|
||
- (2026-06-10) UI home: `packages/integrations/src/components/email/` (provider
|
||
config, `admin/InboundTicketDefaultsManager.tsx`,
|
||
`forms/InboundTicketDefaultsForm.tsx`). Alias UI goes next to the client
|
||
inbound-domains UI (`packages/clients/src/actions/clientInboundEmailDomainActions.ts`
|
||
is the actions-layer sibling).
|
||
|
||
## Implementation Notes (2026-06-10)
|
||
|
||
- Implemented in commits `824f5a9178` (engine/pipeline/EE/migrations),
|
||
`a3df3eb94e` (server actions), `a2b564237c` (UI).
|
||
- Engine lives in `shared/services/email/inboundEmailRules/` with injectable
|
||
deps (`loadRules`, `matchClientByName`, `resolveDefaultsById`,
|
||
`classifyWithAi`) so the walk semantics are unit-testable without a DB; the
|
||
default deps hit the real tables. The tester server action injects only
|
||
`loadRules` (the draft rule), so client/alias matching is live.
|
||
- **Deviation — reorder**: up/down arrow controls instead of drag-and-drop;
|
||
the repo has no dnd dependency and adding one wasn't justified. Positions
|
||
persist through `reorderInboundEmailRules` the same way.
|
||
- **Deviation — alias quick-add**: `packages/integrations` does not depend on
|
||
`packages/clients`, so the tester quick-add uses its own
|
||
`addClientNameAliasFromRuleTester` action (system_settings RBAC) writing the
|
||
same table the clients-package CRUD uses.
|
||
- AI usage logging: there was no pre-existing usage-tracking module; the EE
|
||
classifier emits a structured `token usage` log line (tenant/provider/rule/
|
||
model/usage) that metering can consume later.
|
||
- `email_processed_messages.processing_status='skipped'` is only used for
|
||
rule skips; other skip reasons (missing_defaults etc.) keep `partial` to
|
||
avoid changing existing diagnostics semantics.
|
||
- The rule tester does not exercise provider filtering (draft rule runs with
|
||
`provider_ids: null`) — testers have no receiving mailbox.
|
||
- Migrations were applied against the worktree's local-test DB
|
||
(port 5472) and the resulting schema verified, including the
|
||
`lower(regexp_replace(trim(...), '\s+', ' ', 'g'))` matcher expression.
|
||
Citus distribution was skipped there (non-Citus dev DB) as designed.
|
||
- Pre-existing test failures on this branch (NOT from this work):
|
||
`processInboundEmailInApp.test.ts` (2) and
|
||
`processInboundEmailInApp.additionalPaths.test.ts` (1) fail at HEAD too.
|
||
- Remaining unimplemented tests in `tests.json` need a DB-backed harness
|
||
(matcher SQL, unique index, inactive-rule filtering), an EE/AI mock harness
|
||
(T058–T065), action-layer tests (T070–T078), and UI tests (T079–T094).
|
||
|
||
## Commands / Runbooks
|
||
|
||
- (2026-06-10) Integration tests: see `integration-testing` skill (DB bootstrap,
|
||
tenant isolation, transaction cleanup).
|
||
- (2026-06-10) Manual smoke: dev stack + MailHog (see `alga-dev-env-manager` /
|
||
`alga-manual-smoke-tests` skills); send sample Huntress-style email and a
|
||
status-update email.
|
||
|
||
## Links / References
|
||
|
||
- Design doc: `docs/plans/2026-06-10-inbound-email-rules-design.md`
|
||
- Pipeline: `shared/services/email/processInboundEmailInApp.ts`
|
||
- Matching/defaults: `shared/workflow/actions/emailWorkflowActions.ts`
|
||
- Reply parsing: `shared/lib/email/replyParser.ts`
|
||
- Prior related plans: `ee/docs/plans/2026-02-13-inbound-email-domain-matching-default-contact/`,
|
||
`ee/docs/plans/2026-02-25-inbound-email-sender-routing-to-boards/`,
|
||
`ee/docs/plans/2026-03-31-inbound-email-reopen-on-reply/`
|
||
|
||
## Open Questions
|
||
|
||
- None blocking. Deferred (out of scope per PRD): any_of condition groups,
|
||
apply-to-replies flag, fuzzy matching, global AI fallback rule, token billing.
|