- Check notUpdated/notDestroyed per-item errors in Email/set; throw JmapSetItemException for permanent failures (notFound, forbidden) so they are discarded immediately rather than retried - Add _maxChangeAttempts=5 constant; _recordChangeError() evicts the pending-change row when attempts reach the limit, preventing unbounded queue growth from transient errors - Both IMAP and JMAP flush paths now use _recordChangeError() consistently - Document server-wins conflict-resolution policy in DB-SYNC.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.9 KiB
3.9 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. syncEmailsfetchesbodyValuesduring the sync pass so bodies are cached without a separate on-demand fetch.flushPendingChangespassesifInStateto everyEmail/set; astateMismatchresponse clears the local checkpoint and triggers a full re-sync before retrying.
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
- 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.
Shared / cross-protocol
- Conflict-resolution policy: server-wins. The next sync cycle always
overwrites local state with server values. Outbound mutations in
pending_changesare retried up to 5 times before being evicted, preventing unbounded queue growth. Permanent per-item JMAP errors (notFound,forbidden) are discarded immediately; transient errors (network, 500) are retried up to the limit. ifInStateguard on every JMAPEmail/setcall;notUpdated/notDestroyedper-item errors are detected and treated as permanent failures.