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
6.5 KiB
6.5 KiB
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_idsfilter. 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 = proceedcontinues 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_nameand the deterministic matcher resolves it. No client list in the prompt. - (2026-06-10) Packaging: rules engine CE; only
ai_classifygated 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-effortcreate_distributed_table('<table>', 'tenant'),exports.config = { transaction: false }, functional unique indexes onlower(...)— mirrorserver/migrations/20260213180500_create_client_inbound_email_domains.cjs. - (2026-06-10)
email_processed_messages.processing_statushas a CHECK constraint (success|failed|partial) — addingskippedrequires dropping/re-adding the constraint (migration20250130200000created it). - (2026-06-10) Unknown senders are currently dropped as
skipped/missing_defaultsinprocessInboundEmailInApp.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) inshared/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.tsis 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 onlyloadRules(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
reorderInboundEmailRulesthe same way. - Deviation — alias quick-add:
packages/integrationsdoes not depend onpackages/clients, so the tester quick-add uses its ownaddClientNameAliasFromRuleTesteraction (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 usagelog 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.) keeppartialto 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) andprocessInboundEmailInApp.additionalPaths.test.ts(1) fail at HEAD too. - Remaining unimplemented tests in
tests.jsonneed 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-testingskill (DB bootstrap, tenant isolation, transaction cleanup). - (2026-06-10) Manual smoke: dev stack + MailHog (see
alga-dev-env-manager/alga-manual-smoke-testsskills); 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.