- sendEmail dispatches on account type: IMAP keeps SMTP+APPEND path, JMAP chains Email/set create + EmailSubmission/set in one API call - Sent mailbox looked up by role='sent' from local DB so sent mail lands in the right folder - JmapClient gains uploadUrl/eventSourceUrl/capabilities from session, supportsSubmission getter, withSubmission flag on call(), and uploadBlob() for attachment upload before send - Mailboxes table gains nullable role column (schema v8); _upsertJmapMailboxes persists role from JMAP Mailbox/get response Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.7 KiB
3.7 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 hardening: document and enforce the server-wins policy
consistently — check
notUpdated/notDestroyedper-item errors in JMAPEmail/setresponses, handle IMAPNO/BADgracefully, and evict changes that exceed a maximum retry threshold (e.g. 5 attempts) to prevent queues from growing unboundedly.