- Include bodyValues/textBody/htmlBody/attachments in every Email/get call during syncEmails; _upsertJmapEmails writes to email_bodies so first open is instant even for freshly synced messages - Extract _parseJmapBody helper shared by sync path and on-demand fetch - Add JmapStateMismatchException; _applyPendingChangeJmap passes ifInState and returns newState; on stateMismatch the local checkpoint is cleared so the next cycle does a full re-sync before retrying the mutation - Update DB-SYNC.md to reflect what has been implemented Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.6 KiB
3.6 KiB
DB Sync Status
This document covers the mail-to-database sync layer only, not the UI.
Implemented features
JMAP
- JMAP accounts can be stored in the database.
- JMAP endpoint discovery is implemented.
- JMAP connection testing is implemented.
sync_statetable stores server-side state tokens per (account, resource type).pending_changestable provides a protocol-agnostic outbound mutation queue.JmapClientfetches the JMAP Session object, extractsapiUrlandaccountId, and provides acall()helper for API requests.syncMailboxesfor JMAP: first run usesMailbox/get; subsequent runs useMailbox/changeswith the stored state token.syncEmailsfor JMAP: first run usesEmail/query+Email/get; subsequent runs useEmail/changes. Chains both calls in a single API request via#idsback-reference.Email/querypagination: cursor-based loop withpositionoffset andcalculateTotalhandles mailboxes larger than 500 emails.- JMAP background sync worker (
_JmapAccountSync): session → flush outbound queue → syncMailboxes → syncEmails per mailbox → 30 s poll → repeat. Exponential backoff 5–300 s on failure. - Local mutations (flag, move, delete) on JMAP accounts are written to
pending_changeswith an optimistic local update.flushPendingChangesdrains the queue viaEmail/setat the start of each sync cycle. - Email bodies are fetched on demand via
Email/getwithbodyValuesand cached inemail_bodiesso subsequent opens are instant.
IMAP
- Background sync starts automatically for IMAP accounts.
- Mailbox lists and mailbox counters are synced into the local database.
- Email headers, flags, and attachment metadata are pulled from IMAP into the local database.
- Email bodies are fetched on demand and cached locally.
- All mailboxes (not just INBOX) are synced each cycle.
- Incremental sync:
(lastUid, uidValidity)checkpoint stored insync_state; only new UIDs are fetched on subsequent runs; UID-validity change triggers a full re-scan. - Deletion reconciliation: server UID set is compared against local rows; any email absent from the server is removed from the local DB.
- Local mutations (flag, move, delete) are written to
pending_changeswith an optimistic local update;flushPendingChangesdrains the queue over a single IMAP connection at the start of each sync cycle. - Sent messages are appended to the Sent folder after SMTP delivery.
- Sync retries use exponential backoff after failures.
Cross-protocol
sync_logtable records each sync cycle's account, result (ok / error), error message, start time, and finish time. Used for debugging and "last synced" UI.
Next steps
JMAP hardening
- Body caching during sync:
syncEmailscurrently syncs headers only. IncludebodyValues+htmlBody/textBodyin theEmail/getproperties list so bodies are written toemail_bodiesduring the sync pass, not just on first open. - JMAP send: implement outgoing mail via
EmailSubmission/setin addition to the current SMTP path. - Push instead of polling: upgrade
_JmapAccountSync._wait()to use anEventSourceconnection to the JMAP push URL when the server advertises push capability. Fall back to 30 s polling when push is unavailable. - Conflict handling: pass
ifInStatetoEmail/setinflushPendingChangesso the server can reject a stale mutation; retry the affected change after re-syncing.
Shared / cross-protocol
- Explicit conflict-resolution strategy: decide and document the policy (last-write- wins vs. server-wins) and implement it consistently across both protocols.