feat: per-book roles for CardDAV multi-address-book sync#264
Open
salmonumbrella wants to merge 2 commits into
Open
feat: per-book roles for CardDAV multi-address-book sync#264salmonumbrella wants to merge 2 commits into
salmonumbrella wants to merge 2 commits into
Conversation
Adds two-way contact synchronisation with any CardDAV server, building on the existing read-only client. - Remote-first create/update/delete: local state changes only after the server confirms, with committed intent and read-only recovery for ambiguous writes (no replayed PUT/DELETE). - Retained lossless vCards: properties MailFlow does not model (X-*, CATEGORIES, KEY, TZ, grouped item properties) survive every edit path, including edits made through MailFlow's own CardDAV server and both conflict resolutions. - Conflict detection and resolution: simultaneous local/remote edits raise an explicit conflict with a side-by-side comparison and keep-mine / keep-theirs resolution; deletions conflict too. - RFC 6578 incremental sync with full-resync fallback, RFC 7232 conditional writes (If-Match required on mapped writes), and Retry-After throttling. - Per-book capability discovery (create/update/delete allowed | denied | unknown) drives the UI; read-only books are surfaced as such. - Contacts UI: photos (uploadable from the detail view too), typed additional fields with humanized vCard labels, per-operation controls, conflict queue, and sync-state markers, translated in all seven locales. - Contact list pagination orders by a unique key so paging reaches every contact; the integration's contact count is derived from the sync ledger rather than accumulated, so it cannot drift. Provider-neutral: no code branches on a provider name or hostname; discovery is RFC 6764/6352 (current-user-principal -> addressbook-home-set), and every server capability is optional. Verified against Nextcloud, plus iCloud- and Fastmail-shaped protocol fixtures. Migrations 0034-0037 add the sync schema; the only changes to pre-existing data are a guarded consolidation of duplicate CardDAV address books and removal of the obsolete dupMode config key.
A CardDAV credential often exposes several address books — a curated "Apple Contacts" book next to the "Email contacts" book MailFlow should write to. Pulling every book and writing into whichever came first flooded the contact list and risked writing into a curated book. Each book now carries explicit roles, managed per book in the admin panel and enforced across sync, export, and conflict handling: - Write target: exactly one per account; every MailFlow-originated create/update/delete goes there and nowhere else. A stable alias keeps the role when re-discovery changes the book's canonical URL. - Subscribed: pulled and shown in the contact list. - Lookup source: pulled into the sync ledger only, to resolve inbound senders (names, avatars) without materializing contacts. - Neither: ignored entirely; its ledger rows are dropped. Publication into a shared book becomes a deliberate act. Editing an auto-collected contact no longer silently publishes it; an explicit promote action does. Sending mail to someone still marks them explicit for autocomplete but no longer publishes them: a new publishEmailedContacts setting (default OFF) gates that, and turning it ON restores a book that fills with everyone you correspond with. Default OFF means nothing new reaches a shared address book without a deliberate act. The gate is a separate carddav_publish_intent column, so send.js is untouched and autocomplete ranking on is_auto is unaffected; contacts already exporting keep exporting via the backfill. The role model follows JMAP's isDefault/isSubscribed split rather than a single flag. Migrations 0038-0040 add role, alias, and publish-intent columns with behaviour-preserving backfills; the only pre-existing object touched is a widened mapping-status CHECK constraint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #263 (bidirectional CardDAV sync). Until that merges, GitHub shows both diffs. This PR adds the multi-book layer.
Summary
One CardDAV credential often exposes several address books — a curated Apple Contacts book alongside the "Email contacts" book you want MailFlow writing to. The client pulled every book and wrote to the first writable: thousands of unwanted contacts in, writes to the wrong book. This PR assigns each book an explicit role and makes publication to a shared book deliberate.
Changes
isDefault/isSubscribedsplit rather than a single flag:publishEmailedContactssetting, default off. When off, nothing reaches shared books without deliberate action; when on, MailFlow fills books with correspondents. Pre-existing exports continue (the migration backfills intent). A separatecarddav_publish_intentcolumn keepssend.jsuntouched and preserves autocomplete ranking onis_auto.Testing
Backend 1,466 unit + 171 PostgreSQL-backed tests; frontend 1,450. Tests verify both toggle states: when off, an emailed-only contact produces zero PUTs; when on, it exports to the write-target book.
Note
macOS Contacts.app cannot handle multi-collection CardDAV accounts (Stalwart's maintainer hit the same limit). When MailFlow's CardDAV server exposes multiple books, it hits this client limitation — something to document, not fix server-side.
Contributor License Agreement
By submitting this pull request I confirm that:
third-party material and confirmed it is compatible with the CLA).