Apple / iCloud and CalDAV
The adapter at apps/api/src/sync/adapters/caldav.ts serves Apple/iCloud,
Nextcloud, Radicale, Fastmail, and other CalDAV servers. It synchronizes VEVENT
and VTODO collections in both directions, supports multiple accounts, and uses
Basic authentication rather than OAuth.
Connect an account
Section titled “Connect an account”-
Configure credential encryption.
Terminal window openssl rand -hex 32Put the result in the API environment:
CALDAV_ENC_KEY=<64-hex-characters>Changing this key without migrating stored ciphertext makes existing CalDAV credentials and federated member tokens unreadable.
-
Collect server credentials.
You need a CalDAV server URL, username, and password. Outside development, the URL must use HTTPS and every discovered or redirected target is checked against the private-address SSRF guard before credentials are sent. iCloud requires an Apple app-specific password, never the Apple ID password.
-
Connect through Settings.
Credentials are stored in
caldav_accounts, separate from Better Auth’s OAuthaccounttable. Passwords are AES-GCM encrypted before persistence. -
Inspect discovery and the first pull.
Use
LOG_LEVEL=debugtemporarily and correlate failures byrequestId. Verify calendar discovery before diagnosing individual event mapping.
providerFlavor() labels an icloud.com server as Apple for display. The
underlying sync path remains CalDAV.
Incremental reconciliation
Section titled “Incremental reconciliation”When supported, the first pull uses WebDAV sync-collection without a token to
fetch the complete collection and obtain its cursor. Later pulls fetch only
changed resource URLs; deleted URLs become tombstones for both resource kinds so
the existing mapping is removed safely.
An expired token, unsupported report, malformed response, or missing next token
falls back to a complete depth-one PROPFIND listing and calendar multiget.
This avoids iCloud’s calendar-query time-range requirement while keeping reset
sweeps complete. Unchanged ETags remain verified no-ops.
Event mapping
Section titled “Event mapping”| CalDAV/iCalendar shape | Musubi behavior |
|---|---|
| Resource URL | Stable externalId; UID alone is not used |
DATE DTSTART |
All-day event at UTC midnight |
Exclusive DTEND |
Subtract one day for inclusive Musubi end |
RRULE, EXDATE, RDATE, detached instances |
Preserve and project recurrence exceptions |
TZID + VTIMEZONE |
Resolve instants and preserve the original zone on update |
VALARM |
Preserve alarm components while editing event or task fields |
| VTODO | Import and write task fields, UID, and ETag |
| ETag unchanged | No database write and no updatedAt bump |
Updates patch the existing calendar resource rather than replacing its complete iCalendar payload. This keeps server-owned fields, detached recurrence components, timezones, and alarms intact. If the recurrence rule itself changes, stale detached instances are removed with the old series.
Calendar-level operations
Section titled “Calendar-level operations”Event objects use the typed tsdav client. Calendar lifecycle operations use
raw WebDAV so Musubi controls XML and Apple properties:
MKCALENDARfor creation;PROPPATCHfor display name and Apple calendar color; andDELETEfor removal.
The adapter derives the calendar-home URL from an existing calendar. A
207 Multi-Status response to PROPPATCH is successful; a server that ignores
Apple’s color property is non-fatal.
Troubleshooting
Section titled “Troubleshooting”iCloud authentication fails
Section titled “iCloud authentication fails”Generate a fresh app-specific password after confirming two-factor authentication is enabled. Do not enter the normal Apple ID password.
Discovery succeeds but no events arrive
Section titled “Discovery succeeds but no events arrive”Check the exact collection URL and whether the server permits depth-one
PROPFIND, calendar-multiget, or sync-collection on that collection.
Every sync appears to rewrite everything
Section titled “Every sync appears to rewrite everything”Inspect ETag preservation and reset-upsert behavior. A quiet, unchanged full-fetch should not bump local timestamps or emit an external-sync nudge.
Credentials became unreadable after deployment
Section titled “Credentials became unreadable after deployment”Verify the deployment still has the original CALDAV_ENC_KEY. Restore that key
from the secret manager; guessing or regenerating it cannot decrypt existing
rows.
Contributor checklist
Section titled “Contributor checklist”- test against a generic CalDAV server and iCloud when feasible;
- preserve resource URLs, ETags, inclusive/exclusive dates, and DATE-typed exceptions;
- cover
207 Multi-Statusand ignored color-property behavior; - never log Basic auth values, encrypted payloads, tokens, or full request bodies;
- verify incremental pulls and expired-token fallback; and
- run
pnpm checkplus a disposable-account round trip.