# atdayev/submission-triage architecture

> Architecture diagram of the GitHub repository atdayev/submission-triage, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-08-01). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/atdayev/submission-triage
- Repository: https://github.com/atdayev/submission-triage

## Overview

#### Purpose and entry points
`submission-triage` is a single-binary Go service that monitors an insurance Gmail inbox, evaluates submission attachments against policy checklists, and sends threaded requests for missing material. The production process starts at `cmd/server/main.go`; `cmd/authorize/main.go` supports mailbox authorization. Composition and lifecycle wiring are in `internal/app/app.go` and `internal/app/build.go`.

#### Runtime and operational boundary
The service is a long-running poller, not a request-driven application. It runs on Go 1.25+, uses a local SQLite file with SQL migrations, and can be installed as a systemd service via `deploy/systemd/submission-triage.service`. A small HTTP health endpoint exposes mailbox reachability through `internal/delivery/http/router.go` and `internal/delivery/http/handler/health.go`.

#### Mail ingestion and control plane
IMAP is the inbound boundary: the poller reads unread messages, tracks ingestion state, and applies mailbox-folder controls such as Hold/status folders. Core implementation is in `internal/delivery/imap/poller.go`, `internal/delivery/imap/client.go`, and `internal/repository/ingest_control.go`. Raw messages are translated into domain email/document structures by `internal/delivery/emailingest/translate.go`; reusable MIME parsing resides in `pkg/emlparse/emlparse.go`.

#### Submission triage pipeline
The submission service deduplicates incoming messages, associates follow-ups with existing submissions, determines policy type, evaluates checklist completion, and decides whether broker contact or human review is needed. Its main orchestration is `internal/service/submissions.go`, based on domain models in `internal/model/submission.go`, `internal/model/document.go`, and `internal/model/reply.go`. Checklist definitions are YAML files under `checklists/`, loaded through `internal/infrastructure/checklist/store.go`.

#### Document extraction and classification
Attachments cross an extraction/classification boundary before entering checklist evaluation. Extractors support PDF, DOCX, XLSX, and CSV in `internal/infrastructure/extractor/pdf.go`, `internal/infrastructure/extractor/docx.go`, and `internal/infrastructure/extractor/xlsx.go`. Deterministic filename/content matching is supplemented, when configured and within spend limits, by Anthropic Claude through `internal/infrastructure/classifier/classifier.go` and `internal/infrastructure/llm/anthropic.go`. Unreadable or uncertain documents become internal review signals rather than broker-facing claims.

#### Durable state and reliable outbound effects
SQLite persistence owns submissions, audit history, LLM spend, ingestion controls, and a transactional outbox. Database setup is in `internal/database/sqlite.go`; schema evolution is in `migrations/0001_init.sql` through `migrations/0010_ingest_control.sql`. Repository boundaries include `internal/repository/submissions.go`, `internal/repository/outbox.go`, and `internal/repository/audit.go`. The outbox worker in `internal/service/outbox_worker.go` decouples persisted reply intent from SMTP delivery, enabling restart-safe sending and duplicate prevention.

#### Outbound communication and scheduled lifecycle
SMTP sends broker replies and internal daily digests using `internal/infrastructure/email/smtp.go` and `internal/infrastructure/email/sender.go`. Lifecycle jobs handle quiet-submission escalation, digest generation, and retention in `internal/service/escalation.go` and `internal/service/retention.go`. These operations update both persisted state and IMAP organization, while `REPLIES_ENABLED` separates inbox processing from externally visible broker messaging.

Main entry points: [`internal/service/submissions.go`](https://github.com/atdayev/submission-triage/blob/main/internal/service/submissions.go) (Submission triage), [`internal/delivery/imap/poller.go`](https://github.com/atdayev/submission-triage/blob/main/internal/delivery/imap/poller.go) (IMAP poller), [`internal/database/sqlite.go`](https://github.com/atdayev/submission-triage/blob/main/internal/database/sqlite.go) (SQLite state), [`internal/service/escalation.go`](https://github.com/atdayev/submission-triage/blob/main/internal/service/escalation.go) (Lifecycle jobs)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_runtime["Runtime"]
  node_server{{"Service process<br/>Go entry point<br/>[main.go]"}}
  node_app["App composition<br/>lifecycle wiring<br/>[app.go]"]
  node_systemd["systemd unit<br/>deployment"]
  node_health["Health endpoint<br/>HTTP delivery<br/>[router.go]"]
end

subgraph group_inbound["Mail ingestion"]
  node_imap["IMAP poller<br/>inbound adapter<br/>[poller.go]"]
  node_email_translate["Email translation<br/>message mapper<br/>[translate.go]"]
  node_mime_parser["MIME parser<br/>mail parser<br/>[emlparse.go]"]
  node_ingest_control["Ingestion controls<br/>[ingest_control.go]"]
end

subgraph group_triage["Triage pipeline"]
  node_submissions["Submission triage<br/>application service<br/>[submissions.go]"]
  node_documents["Document extraction<br/>extractor suite<br/>[pdf.go]"]
  node_classification["Document classification<br/>classifier<br/>[classifier.go]"]
  node_claude["Anthropic Claude<br/>LLM provider<br/>[anthropic.go]"]
  node_checklists["Checklist store<br/>YAML policy rules<br/>[store.go]"]
end

subgraph group_durability["Durable effects"]
  node_sqlite[("SQLite state<br/>database<br/>[sqlite.go]")]
  node_migrations["Schema migrations<br/>SQL migrations<br/>[0001_init.sql]"]
  node_submission_repo["Submission repository<br/>[submissions.go]"]
  node_outbox["Transactional outbox<br/>[outbox.go]"]
end

subgraph group_outbound["Outbound &amp; lifecycle"]
  node_outbox_worker["Outbox worker<br/>delivery worker<br/>[outbox_worker.go]"]
  node_smtp["SMTP sender<br/>outbound adapter<br/>[smtp.go]"]
  node_lifecycle["Lifecycle jobs<br/>scheduled service<br/>[escalation.go]"]
end

node_systemd -->|"runs"| node_server
node_server -->|"initializes"| node_app
node_app -->|"starts polling"| node_imap
node_app -->|"serves"| node_health
node_health -.->|"checks reachability"| node_imap
node_imap -->|"raw messages"| node_email_translate
node_email_translate -->|"parses MIME"| node_mime_parser
node_imap -->|"reads and updates"| node_ingest_control
node_email_translate -->|"domain email and attachments"| node_submissions
node_submissions -->|"extracts attachments"| node_documents
node_documents -->|"extracted content"| node_classification
node_classification -.->|"optional classification"| node_claude
node_submissions -->|"loads policy checklist"| node_checklists
node_submissions -->|"persists triage state"| node_submission_repo
node_submission_repo -->|"stores"| node_sqlite
node_ingest_control -->|"stores"| node_sqlite
node_migrations -->|"evolves schema"| node_sqlite
node_submissions -->|"records reply intent"| node_outbox
node_outbox -->|"stores"| node_sqlite
node_outbox_worker -->|"claims pending sends"| node_outbox
node_outbox_worker -->|"delivers replies"| node_smtp
node_lifecycle -->|"updates lifecycle state"| node_submission_repo
node_lifecycle -->|"organizes mailbox"| node_imap
node_lifecycle -->|"sends internal digests"| node_smtp

click node_server "https://github.com/atdayev/submission-triage/blob/main/cmd/server/main.go"
click node_app "https://github.com/atdayev/submission-triage/blob/main/internal/app/app.go"
click node_systemd "https://github.com/atdayev/submission-triage/blob/main/deploy/systemd/submission-triage.service"
click node_health "https://github.com/atdayev/submission-triage/blob/main/internal/delivery/http/router.go"
click node_imap "https://github.com/atdayev/submission-triage/blob/main/internal/delivery/imap/poller.go"
click node_email_translate "https://github.com/atdayev/submission-triage/blob/main/internal/delivery/emailingest/translate.go"
click node_mime_parser "https://github.com/atdayev/submission-triage/blob/main/pkg/emlparse/emlparse.go"
click node_ingest_control "https://github.com/atdayev/submission-triage/blob/main/internal/repository/ingest_control.go"
click node_submissions "https://github.com/atdayev/submission-triage/blob/main/internal/service/submissions.go"
click node_documents "https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/extractor/pdf.go"
click node_classification "https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/classifier/classifier.go"
click node_claude "https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/llm/anthropic.go"
click node_checklists "https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/checklist/store.go"
click node_sqlite "https://github.com/atdayev/submission-triage/blob/main/internal/database/sqlite.go"
click node_migrations "https://github.com/atdayev/submission-triage/blob/main/migrations/0001_init.sql"
click node_submission_repo "https://github.com/atdayev/submission-triage/blob/main/internal/repository/submissions.go"
click node_outbox "https://github.com/atdayev/submission-triage/blob/main/internal/repository/outbox.go"
click node_outbox_worker "https://github.com/atdayev/submission-triage/blob/main/internal/service/outbox_worker.go"
click node_smtp "https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/email/smtp.go"
click node_lifecycle "https://github.com/atdayev/submission-triage/blob/main/internal/service/escalation.go"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_server,node_app,node_systemd,node_health toneBlue
class node_imap,node_email_translate,node_mime_parser,node_ingest_control toneAmber
class node_submissions,node_documents,node_classification,node_claude,node_checklists toneMint
class node_sqlite,node_migrations,node_submission_repo,node_outbox toneRose
class node_outbox_worker,node_smtp,node_lifecycle toneIndigo
```

## Components

### Runtime

- [Service process](https://github.com/atdayev/submission-triage/blob/main/cmd/server/main.go): Go entry point · Long-running poller with health endpoint. · `cmd/server/main.go`
- [App composition](https://github.com/atdayev/submission-triage/blob/main/internal/app/app.go): lifecycle wiring · `internal/app/app.go`
- [systemd unit](https://github.com/atdayev/submission-triage/blob/main/deploy/systemd/submission-triage.service): deployment · `deploy/systemd/submission-triage.service`
- [Health endpoint](https://github.com/atdayev/submission-triage/blob/main/internal/delivery/http/router.go): HTTP delivery · Reports mailbox reachability. · `internal/delivery/http/router.go`

### Mail ingestion

- [IMAP poller](https://github.com/atdayev/submission-triage/blob/main/internal/delivery/imap/poller.go): inbound adapter · Reads unread Gmail messages and applies folder controls. · `internal/delivery/imap/poller.go`
- [Email translation](https://github.com/atdayev/submission-triage/blob/main/internal/delivery/emailingest/translate.go): message mapper · Converts raw mail into domain email and documents. · `internal/delivery/emailingest/translate.go`
- [MIME parser](https://github.com/atdayev/submission-triage/blob/main/pkg/emlparse/emlparse.go): mail parser · `pkg/emlparse/emlparse.go`
- [Ingestion controls](https://github.com/atdayev/submission-triage/blob/main/internal/repository/ingest_control.go): repository · Tracks ingestion state and mailbox controls. · `internal/repository/ingest_control.go`

### Triage pipeline

- [Submission triage](https://github.com/atdayev/submission-triage/blob/main/internal/service/submissions.go): application service · Deduplicates, associates replies, evaluates policy completion, and routes outcomes. · `internal/service/submissions.go`
- [Document extraction](https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/extractor/pdf.go): extractor suite · Extracts attachment content from PDF, DOCX, XLSX, and CSV. · `internal/infrastructure/extractor/pdf.go`
- [Document classification](https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/classifier/classifier.go): classifier · Uses deterministic matching and optional Claude within spend limits. · `internal/infrastructure/classifier/classifier.go`
- [Anthropic Claude](https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/llm/anthropic.go): LLM provider · `internal/infrastructure/llm/anthropic.go`
- [Checklist store](https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/checklist/store.go): YAML policy rules · `internal/infrastructure/checklist/store.go`

### Durable effects

- [SQLite state](https://github.com/atdayev/submission-triage/blob/main/internal/database/sqlite.go): database · Stores submissions, audit history, spend, controls, and outbox rows. · `internal/database/sqlite.go`
- [Schema migrations](https://github.com/atdayev/submission-triage/blob/main/migrations/0001_init.sql): SQL migrations · `migrations/0001_init.sql`
- [Submission repository](https://github.com/atdayev/submission-triage/blob/main/internal/repository/submissions.go): repository · `internal/repository/submissions.go`
- [Transactional outbox](https://github.com/atdayev/submission-triage/blob/main/internal/repository/outbox.go): repository · Persists reply intent before delivery. · `internal/repository/outbox.go`

### Outbound & lifecycle

- [Outbox worker](https://github.com/atdayev/submission-triage/blob/main/internal/service/outbox_worker.go): delivery worker · Restart-safe broker reply delivery with duplicate prevention. · `internal/service/outbox_worker.go`
- [SMTP sender](https://github.com/atdayev/submission-triage/blob/main/internal/infrastructure/email/smtp.go): outbound adapter · Sends broker replies and internal digests. · `internal/infrastructure/email/smtp.go`
- [Lifecycle jobs](https://github.com/atdayev/submission-triage/blob/main/internal/service/escalation.go): scheduled service · Escalates quiet submissions, generates digests, and applies retention. · `internal/service/escalation.go`

## Connections

- systemd unit → Service process: runs
- Service process → App composition: initializes
- App composition → IMAP poller: starts polling
- App composition → Health endpoint: serves
- Health endpoint → IMAP poller: checks reachability
- IMAP poller → Email translation: raw messages
- Email translation → MIME parser: parses MIME
- IMAP poller → Ingestion controls: reads and updates
- Email translation → Submission triage: domain email and attachments
- Submission triage → Document extraction: extracts attachments
- Document extraction → Document classification: extracted content
- Document classification → Anthropic Claude: optional classification
- Submission triage → Checklist store: loads policy checklist
- Submission triage → Submission repository: persists triage state
- Submission repository → SQLite state: stores
- Ingestion controls → SQLite state: stores
- Schema migrations → SQLite state: evolves schema
- Submission triage → Transactional outbox: records reply intent
- Transactional outbox → SQLite state: stores
- Outbox worker → Transactional outbox: claims pending sends
- Outbox worker → SMTP sender: delivers replies
- Lifecycle jobs → Submission repository: updates lifecycle state
- Lifecycle jobs → IMAP poller: organizes mailbox
- Lifecycle jobs → SMTP sender: sends internal digests
