Skip to content

feat: sync IsRead from IMAP \Seen / Graph isRead - #501

Open
fhalamzie wants to merge 1 commit into
s1t5:mainfrom
fhalamzie:feat/isread-flag
Open

feat: sync IsRead from IMAP \Seen / Graph isRead#501
fhalamzie wants to merge 1 commit into
s1t5:mainfrom
fhalamzie:feat/isread-flag

Conversation

@fhalamzie

Copy link
Copy Markdown

Summary

Adds an IsRead boolean column to ArchivedEmails to track the read state synced from the mail provider.

Motivation: The archive currently has no way to distinguish read from unread emails. This blocks downstream tools (search UIs, dashboards) from showing unread badges or filtering by read state.

Changes

Schema

  • New IsRead boolean NOT NULL DEFAULT false column on ArchivedEmails (idempotent DO $$ ... IF NOT EXISTS migration, house style)
  • EF Core entity + DbContext Fluent config (HasDefaultValue(false))
  • Model snapshot updated

IMAP sync (ImapMailSyncService)

  • Batch-fetches \Seen flags per UID batch via folder.FetchAsync(batch, MessageSummaryItems.Flags | MessageSummaryItems.UniqueId) — one extra round-trip per batch, not per message
  • Builds a UID → isRead map, threads it through to ArchiveEmailAsync
  • Gracefully defaults to false if the flag fetch fails

Graph sync (GraphMailSyncService + GraphMailArchiver)

  • "isRead" added to all 4 $select lists (primary, reduced-fields fallback, no-filter fallback, single-message enrichment)
  • IsRead = message.IsRead ?? false in entity construction

EML/MBox import (MailImporter)

  • Explicit IsRead = false (no reliable read state in file imports)

API + MCP

  • IsRead exposed in EmailSummaryDto and EmailDetailDto via FromEntity — the REST API, MCP server, and web UI all flow through these

Search (EmailCoreService)

  • Both raw-SQL SELECT branches (CTE + flat) updated to include e."IsRead"
  • Reader hydration updated
  • ArchiveEmailAsync gains optional bool isRead = false parameter

Backward compatibility

  • Existing emails default to IsRead = false (column default). A full resync updates them going forward.
  • The ArchiveEmailAsync signature change is backward-compatible (new param has a default value).
  • The migration is idempotent (IF NOT EXISTS) and can be safely re-run.

Testing

  • No dotnet SDK was available locally to run a build check — the maintainer's CI will validate compilation.
  • The IMAP flag-fetch pattern (FetchAsync(IList<UniqueId>, MessageSummaryItems.Flags | MessageSummaryItems.UniqueId)) is standard MailKit API.
  • The Graph isRead property already exists on the Microsoft.Graph.Models.Message type (used at GraphMailRestorer.cs:148).

Out of scope (potential follow-ups)

  • API ?isRead=true/false filter parameter
  • Restore round-tripping (GraphMailRestorer hardcodes IsRead = false, ImapMailRestorer always appends with MessageFlags.Seen)
  • Web UI unread badges / filtering

Add IsRead boolean column to ArchivedEmails to track read state synced
from the mail provider. IMAP sync batch-fetches \Seen flags per UID
batch via MailKit FetchAsync; Graph sync adds isRead to all
lists. EML/MBox import defaults to false (no reliable read state in
file imports).

- Entity: ArchivedEmail.IsRead (bool, default false)
- DbContext: HasDefaultValue(false) config
- Migration: 20260806120000_MigrateV2607_4 (idempotent DO 3361622 style)
- EmailCoreService: ArchiveEmailAsync gains isRead param; both raw SQL
  SELECT branches and reader hydration updated
- ImapMailSyncService: batch FetchAsync(MessageSummaryItems.Flags),
  UID→isRead map passed to ArchiveEmailAsync
- GraphMailSyncService: isRead added to all 4  lists
- GraphMailArchiver: IsRead = message.IsRead ?? false in entity init
- MailImporter: explicit IsRead = false for EML/MBox path
- EmailSummaryDto + EmailDetailDto: expose IsRead via FromEntity

Existing emails default to IsRead=false (migration default); a full
resync updates them going forward.
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