KamoCRM

Real outside calendar and contacts sync — Google, Microsoft 365, iCloud, CalDAV/CardDAV

FeatureEmailService
Shipped
2026年11月15日 10:00 UTC
Author
Kamo
Commit
3b85588

Settings › Email › CalDAV/CardDAV saved outside connections and showed "Last synced" for a sync that fetched nothing (DaemonService's jobs bumped counters and marked everything IDLE; the provider classes were TODOs). EmailService now syncs them for real (groupware/sync): - CalDAV/CardDAV: discovery (address → principal → home set → collections, with /.well-known fallback, a pasted single-calendar address works), CTag/sync-token check, calendar-query ETag listing (last 366 days on), multiget of what changed, PUT/DELETE with If-Match/If-None-Match. iCloud defaults its addresses. - Google Calendar + People, Microsoft Graph calendars + contacts on the grant the /oauth flow stores; tokens refreshed (and rotated refresh tokens kept) with a write that only replaces the exact blob it refreshed. - Events land through IcsImportService.upsert (series, exceptions, EXDATEs) and contacts through ContactService + ContactDetailStore — no invitations, no reminders, no raw table writes. - Bookkeeping per collection/item (new groupware_sync_* tables) gives updates, deletions and two-way: pull first (outside copy wins a tie), then push Kamo edits/creates/deletes when the integration is two-way and the collection writable. Guards: nothing removed on a failed or suspiciously empty listing, nothing Kamo did not import, nothing that only aged out of the window; a pass that would delete most of a collection out there is refused. - Status is honest: SYNCING while running, IDLE + lastSyncedAt only after a full fetch, ERROR + plain-words errorMessage otherwise; written column-by-column so a member's edit mid-pass survives. Per-collection lastError and counts are in the settings payload (syncSupported, calendarsSynced, eventsSynced, addressBooksSynced, contactsSynced, collections[]). - Scheduling: **************** (DaemonService's clock) and /integrations/{id} (Sync Now via daemon.sync.trigger, local fallback without NATS); per-integration SingletonTaskRunner lease so pods never double-run. - Test Connection now signs in and lists; member rows record their organization; deleting an integration detaches (keeps) what was synced; outside addresses must be https to a public host (no cluster/SSRF reach); credentials never logged. - Removed the dead TODO provider stubs and their unused refreshers.

All changes

Like what you see shipping?

All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.

Start Free ForeverView Pricing