@heywoodlh

heywoodlh thoughts

iCloud as a Cross-Platform Source of Truth (CalDav, CardDav)

This is a summary of how to use iCloud for syncing contacts, calendars and reminders on non-Apple clients/platforms.

TL;DR use these settings for CalDav/CardDav on iCloud #

Remember to set up an App-specific Password for authentication.

  • https://contacts.apple.com: Contacts
  • https://caldav.icloud.com*: Calendar, Reminders

* In order to use the same calendars/reminders on iOS and other platforms, you must configure an external CardDav/CalDav account for iCloud on iOS. The built-in iCloud Reminders/Calendar that is setup after logging into doesn’t use traditional CalDav.

Why use iCloud? #

I recently switched away from an iPhone as my daily driver to GrapheneOS on a Pixel 8. However, I still have an iPad and switch regularly between Linux and MacOS when I’m on a workstation. So, I wanted to use iCloud due to Apple’s (often overhyped) adherence to privacy but relying on endpoints that will work with clients that support CalDav and CardDav.

As a long-time Proton user, why not use Proton? #

* Traditional CalDav and CardDav clients don’t work with Proton.

Proton offers a variety of end-to-end encrypted cloud services rivaling iCloud. However, Proton isn’t as portable since they break the protocols to ensure end-to-end encryption. While I think Proton is the superior suite of products from a privacy perspective, they are less functional for traditional protocols like CardDav and CalDav.

* Check out Ferroxide for a third-party, open source solution that exposes CalDav and CardDav endpoints that integrate with Proton.

Platforms and clients I’m using #

Reminders, calendars #

  • Linux, MacOS: vdirsyncer, todoman
  • Android: , [DAVx⁵](https://www.davx5.com)
  • iOS*: Native clients/settings

* As stated in the first TL;DR in order for iOS to show the same reminders and calendars as the other platforms, you need to add an “external” CalDav account for iCloud since the built-in iCloud integration for Reminders and Calendars doesn’t use the same CalDav endpoint.

Contacts #

  • Linux, MacOS: vdirsyncer, khard
  • Android: DAVx⁵
  • iOS: Native clients/settings

Android setup with DAVx⁵ #

I won’t be going in-depth on Android setup, use the settings in the TL;DR at the beginning and use DAVx⁵’s wizard to set them up.

Tasks.org integration #

Use the following article to setup tasks.org with DAVx⁵: tasks.org: DAVx⁵

Linux/MacOS configuration #

Vdirsyncer configuration #

Vdirsyncer is a tool for downloading CalDav and CardDav data locally.

Here is my configuration – using 1Password’s CLI for retrieving credentials – at the time of writing:

[general]
status_path = "/home/heywoodlh/.config/vdirsyncer/status/"

## Calendar/Reminders

[pair icloud_calendar]
a = "apple_local"
b = "apple_remote"
collections = ["from b", "from a"]

[storage apple_local]
type = "filesystem"
path = "~/.todo/apple"
fileext = ".ics"

# Reminder, this is not the iOS-provided iCloud integration
# All devices (i.e. Apple devices) who want to see this should be using caldav
[storage apple_remote]
type = "caldav"
item_types = ["VTODO"]
url = "https://caldav.icloud.com"
username.fetch = ["shell", "op item get '<some-item>' --fields label=username --reveal"]
password.fetch = ["shell", "op item get '<some-item>' --fields label=app-password --reveal"]

## Contacts

[pair icloud_contacts]
a = "apple_remote_contacts"
b = "apple_local_contacts"
collections = [["icloud_contacts", "card", "main"]]
conflict_resolution = "a wins"
metadata = ["displayname"]

[storage apple_local_contacts]
type = "filesystem"
path = "~/.contacts/apple"
fileext = ".vcf"

[storage apple_remote_contacts]
type = "carddav"
url = "https://contacts.icloud.com"
username.fetch = ["shell", "op item get '<some-item>' --fields label=username --reveal"]
password.fetch = ["shell", "op item get '<some-item>' --fields label=app-password --reveal"]

Once Vdirsyncer is configured, run the following to download data:

vdirsyncer discover
vdirsyncer sync

Khard configuration #

Here is my khard configuration at the time of writing:

[addressbooks]
[[personal]]
path = ~/.contacts/apple/main

List all contacts with this command:

khard list

Aerc configuration #

[protonmail]
address-book-cmd = khard email -a personal --parsable --remove-first-line %s
aliases = Spencer Heywood <*@protonmail.com>,Spencer Heywood <*@pm.me>, Spencer Heywood <heywoodlh@heywoodlh.io>
copy-to = Sent
default = INBOX
from = Spencer Heywood <spencer@heywoodlh.io>
outgoing = smtp+insecure://<some-user>%40protonmail.com@protonmail-bridge:25
outgoing-cred-cmd = op read '<some-item>'
source = imap+insecure://<some-user>%40protonmail.com@protonmail-bridge:143
source-cred-cmd = op read '<some-item>'

I use Protonmail Bridge over Tailscale for SMTP+IMAP.

Here is my Kubernetes deployment for Protonmail Bridge at the time of writing: protonmail-bridge.yaml

Written on January 21, 2026

carddav caldav apple linux ios icloud macos email imap smtp protonmail proton