Skip to content

feat(mail): capture and surface IMAP \Seen read state (unread)#946

Open
seonghobae wants to merge 1 commit into
developfrom
feat/email-read-state
Open

feat(mail): capture and surface IMAP \Seen read state (unread)#946
seonghobae wants to merge 1 commit into
developfrom
feat/email-read-state

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Why

The mail experience had no read/unread signal: ingestion fetched only (RFC822) and discarded IMAP flags, Email had no read field, and the API/UI couldn't show unread mail — so mockup_19's unread indicator was unbacked. Per the reframe that this missing capability is itself a defect (not a reason to defer the mail LNB), this is the first backed slice toward the design's mail folder/label/LNB.

What

  • Email.is_read column (defaults read; migration 0011 server_default true so existing/file-imported mail isn't surfaced as unread).
  • imap_worker now fetches (RFC822 FLAGS) and threads the \Seen flag through process_fetched_email via flags_indicate_seen() — a pure, unit-tested parser that defaults to unread on a parse miss (never silently marks mail read).
  • /api/emails EmailListItem exposes is_read (coalescing unflushed None → read).
  • EmailList renders an '안 읽음' badge from is_read === false.

Verification (local)

  • Backend 53 passed (emails + imap + threading + pop3) incl. new flags_indicate_seen unit test.
  • Frontend EmailList.test.tsx 5 passed; tsc + eslint clean.
  • Migration parses, chains from 0009.

Follow-ups (same defect, design-backed)

Multi-folder ingestion (select beyond INBOX) + folder column → labels → the left folder-LNB per mockup_19.

Note: migration 0011 chains from 0009; if #938's 0010 lands first, alembic will have two heads off 0009 needing a trivial merge revision.

🤖 Generated with Claude Code

The mail experience had no read/unread signal: ingestion fetched only
"(RFC822)" and discarded IMAP flags, the Email model had no read field, and
the API/UI could not show unread mail — so mockup_19's unread indicator was
unbacked. This is the first backed slice toward the mail folder/label/LNB the
design specifies.

- Email.is_read column (default read; migration 0011 server_default true so
  existing/file-imported mail is not surfaced as unread).
- imap_worker fetches "(RFC822 FLAGS)" and threads the \Seen flag through to
  process_fetched_email via flags_indicate_seen() — a pure, unit-tested parser
  that defaults to unread on a parse miss (never silently marks mail read).
- /api/emails EmailListItem exposes is_read (coalescing unflushed None -> read).
- EmailList renders an '안 읽음' badge from is_read === false.

Verified: backend 53 passed (emails + imap + threading + pop3), new
flags_indicate_seen unit test, frontend EmailList 5 passed, tsc + eslint clean,
migration parses and chains from 0009.

Follow-ups (same defect, design-backed): multi-folder ingestion (select beyond
INBOX) + folder column, labels, then the left folder-LNB per mockup_19.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RkKdtHRLG4wSLh6PVsp8J
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant