feat/email-ingest-inbox #18

Open
hyungi wants to merge 4 commits from feat/email-ingest-inbox into main
Owner
No description provided.
hyungi added 4 commits 2026-05-13 07:55:12 +09:00
migrations 259~261:
- documents.source_external_id TEXT NULL (email 에선 always non-null, ingest 책임)
- documents.email_metadata JSONB NULL (from/to/cc/subject/folder/uidvalidity/uid/received_at/attachments)
- partial unique on (source_external_id) WHERE source_channel = email AND source_external_id IS NOT NULL

ORM:
- Document.source_external_id / email_metadata mapped_column 추가

dedup 진실원장 = DB unique index. server-side IMAP \\Seen flag 는 best-effort.
mailplus_archive 의 INBOX root archive row 는 source_external_id=NULL 이라 unique 에서 자연 제외.

plan: ~/.claude/plans/document-enchanted-candy.md
신규 워커 app/workers/inbox_ingest.py (337줄):
- 5분 APScheduler cron (mailplus_archive 와 분리 — INBOX root archive vs DocumentServer/Ingest folder)
- UID SEARCH SINCE 14일 (UNSEEN 단독 의존 X, 사용자가 MailPlus UI 에서 먼저 읽어도 누락 회피)
- Message-ID 정규화 또는 imap:{folder}:{uidvalidity}:{uid} fallback → source_external_id always non-null
- ON CONFLICT DO NOTHING (DB unique 진실원장)
- 신규 row 만 BODY parse: snippet + HTML stripping + attachment metadata (binary 저장 X)
- enqueue_stage(doc.id, classify) 로 기존 classify pipeline 진입
- HC.io heartbeat (옵션, INBOX_INGEST_HC_URL)
- parse 실패 분기: row 생성 전 (logger.error + HC fail) / 후 (email_metadata.parse_error 기록)

env (credentials.env.example):
- INBOX_INGEST_ENABLED=false (기본 dormant, 사용자가 alias/folder 셋업 후 true)
- INBOX_INGEST_FOLDER=DocumentServer/Ingest
- INBOX_INGEST_DAYS=14
- INBOX_INGEST_HC_URL=

main.py:
- inbox_ingest_run import + scheduler.add_job interval 5m

email_ingest 정책 (사용자 라운드 2026-05-12):
- 직접 events row 생성 X
- 이메일은 universal inbox item, source_channel=email memo 로 저장
- classify_worker 가 ai_event_kind 채움 (별 PR 의 4B robustness fix 선결)
- 사용자 1-click promote 만이 events row 생성 path

plan: ~/.claude/plans/document-enchanted-candy.md
list 쿼리 확장:
- 기존 source_channel IN (memo, voice) → OR (source_channel = email AND source_external_id IS NOT NULL)
- mailplus_archive 의 INBOX root archive row (source_external_id=NULL) 는 자동 제외
- inbox_ingest 가 만든 email memo 만 /memos UI 에 노출

MemoResponse 확장:
- source_external_id: Message-ID 또는 imap UID fallback
- email_subject: email_metadata.subject (UI 부제/툴팁)

_to_memo_response 가 email_metadata JSONB 에서 subject 추출.

ingest 가 만든 row 가 UI 에 보이는 게 PR-2B 의 분류 배지/4 버튼/promote flow 자산 재사용의 전제.

plan: ~/.claude/plans/document-enchanted-candy.md
- lucide-svelte Mail icon import 추가
- 배지 영역 조건에 source_channel === email 추가
- voice 배지 다음에 email 배지 분기 (sky 색조, title=email_subject)

PR-2B/2C 의 기존 배지 영역 (voice / ai_event_kind / _last_promoted) 그대로.
사용자가 한 화면에서 텍스트/음성/이메일 메모를 source 시각 식별.

plan: ~/.claude/plans/document-enchanted-candy.md
Checking for merge conflicts…
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/email-ingest-inbox:feat/email-ingest-inbox
git checkout feat/email-ingest-inbox
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: hyungi/hyungi_document_server#18