Plötzlich leer
Anfang Oktober waren auf meinem MacBook und meinem Mac Studio die Kontakte aus Microsoft 365 weg. Auf dem iPhone? Alles da. Der Grund ist schnell gefunden: Microsoft schaltet seit dem 1. Oktober 2026 Exchange Web Services (EWS) in Exchange Online ab, Tenant für Tenant. Am 1. April 2027 ist dann endgültig Schluss, ohne Hintertür.
Das Problem: Mail, Kalender und Kontakte auf dem Mac sprechen mit Exchange Online immer noch über genau dieses EWS. Angekündigt war das Ende seit Jahren. Apple hat die Umstellung auf Microsoft Graph für «ein künftiges Update von macOS 27» versprochen, ein Datum gibt es nicht. iPhone und iPad sind nicht betroffen, sie verwenden Exchange ActiveSync.
Ich wollte meine Kontakte aber jetzt auf dem Mac haben, nicht irgendwann. M365 bleibt dabei der Master, der Mac soll nur lesen.
Die naheliegenden Auswege – und warum ich sie nicht wollte
| Variante | Was sie macht | Haken |
|---|---|---|
| EWS im Tenant wieder erlauben | EWSEnabled + Allow-List für die Apple-App | Läuft am 1. April 2027 ab, egal was Apple bis dahin liefert |
| iPhone als Brücke | Outlook für iOS schreibt die Kontakte nach iCloud, iCloud bringt sie auf den Mac | Ein Handy als Sync-Server, Firmenkontakte in der privaten Cloud |
| Outlook für Mac oder OWA | Kontakte direkt in Outlook | Kein Zugriff aus Kontakte, Nachrichten, Telefon oder der Autovervollständigung |
Alles Bastellösungen mit Ablaufdatum. Was ich wollte, ist simpel: Die Kontakte-App auf dem Mac spricht CardDAV, und das kann sie seit Jahren zuverlässig. Es fehlt nur jemand, der M365 in CardDAV übersetzt.
Die Lösung: m365-carddav
Also habe ich mir den Übersetzer gebaut: einen einzigen Docker-Container, der auf dem NAS läuft. Er holt die Kontakte per Microsoft Graph und stellt sie über einen eingebauten CardDAV-Server bereit.

Der Sync-Loop fragt alle 15 Minuten nur die Änderungen ab (Graph Delta Query) und schreibt sie als vCards in Radicale. Als Schlüssel dient die unveränderliche ID des Kontakts aus Exchange. Zieht jemand um, wird die ganze vCard neu geschrieben, M365 gewinnt immer. Einmal pro Nacht läuft zur Sicherheit ein vollständiger Abgleich.
Die Kontakte-App auf dem Mac merkt davon nichts. Für sie ist das ein gewöhnliches CardDAV-Konto, auch offline verfügbar dank lokaler Kopie.
Sicherheit: so wenig Rechte wie möglich
Eine App, die Kontakte aus Postfächern liest, ist ein heikles Ding. Darum ist das Berechtigungsmodell strikt:
- Keine Berechtigung in Entra. Die App bekommt dort kein
Contacts.Readund keinen Admin-Consent. Das würde sie für alle Postfächer im Tenant freischalten. - Exchange Online RBAC for Applications. Die Rolle
Application Contacts.Readgilt nur für einen Management Scope: ein einzelnes Postfach oder die direkten Mitglieder einer mail-aktivierten Sicherheitsgruppe. - Zertifikat statt Client Secret. Der private Schlüssel verlässt den Docker-Host nie. Kein Refresh-Token, das bei MFA oder Passwortwechsel kaputtgeht.
- Nur lesen. Auf dem Mac ist das Adressbuch schreibgeschützt, Radicale lehnt Änderungen ab. Jeder Benutzer sieht ausschliesslich sein eigenes Adressbuch.
- Eigenes CardDAV-Passwort. Es ist nicht das M365-Passwort und existiert nur als bcrypt-Hash im Container. Der Benutzer setzt es über einen Einmal-Link selbst, der Admin sieht es nie.
Ein neuer Benutzer ist damit eine Gruppenmitgliedschaft in Exchange und ein Eintrag in der Admin-Oberfläche. Die App selbst fasst du nie wieder an.
Einrichtung in sieben Schritten
Die vollständige Anleitung mit allen Befehlen steht im README auf GitHub. Hier der grobe Ablauf, damit du siehst, worauf du dich einlässt:
- Image bauen auf einem Host, der rund um die Uhr läuft. Bei mir ist das eine Synology.
- Graph-Zertifikat erzeugen mit
docker run … gen-cert. - Entra-App registrieren mit
1-Register-EntraApp.ps1. Das Skript lädt das Zertifikat hoch und vergibt keine API-Berechtigung. - Mailbox-Zugriff erteilen mit
2-Grant-MailboxAccess.ps1 -Group carddav-users@…. Dafür braucht es einen Exchange-Admin. - Container starten mit
ADMIN_UI=true. Das initiale Admin-Passwort landet nicht im Log, sondern einmalig in einer Datei. - Benutzer anlegen in der Admin-Oberfläche auf Port 5233. Du bekommst einen Einmal-Link samt Text zum Verschicken.
- Mac einrichten: Der Benutzer öffnet den Link, wählt sein Passwort und folgt der Anleitung auf der Seite.




Wer kein eigenes Zertifikat hat, bekommt vom Container ein selbstsigniertes. Mit einer internen CA geht es eleganter: tls-cert.pem und tls-key.pem nach data/config/ legen, fertig, keine Warnungen mehr.
Drei Stolpersteine auf dem Mac
«Manuell», nicht «Erweitert». Beim CardDAV-Account bietet macOS zwei Accounttypen an. Mit «Erweitert» (Server, Pfad und Port in getrennten Feldern) und auch mit einem Konfigurationsprofil meldet macOS 26 nur «Accountname oder Passwort kann nicht überprüft werden». Und zwar ohne je eine Verbindung aufzubauen, das habe ich mit tcpdump nachgeprüft. Mit «Manuell» und der vollständigen URL klappt es sofort:
https://nas.example.lan:5232/alice/

Konto umbenennen. macOS nennt das Konto nach der Server-URL, und genau so steht es dann in Kontakte. In Systemeinstellungen → Internetaccounts das Konto auswählen und die Beschreibung auf «M365» ändern. Ein Terminal-Befehl dafür existiert leider nicht.
Zertifikat. Bei einem selbstsignierten Zertifikat fragt macOS einmal nach: «Zertifikat einblenden» → «Immer vertrauen». Mit einer eigenen CA, deren Root auf den Macs installiert ist, entfällt das.
Fazit
Bei uns laufen jetzt fünf Postfächer über einen einzigen Container. Der erste Abgleich von 345 Kontakten dauerte ein paar Minuten, danach kommen Änderungen innert 15 Minuten auf den Mac. Und wenn Apple irgendwann Graph nachliefert: Container löschen, App entfernen, fertig. Es bleibt nichts zurück.
Code, Anleitung und Skripte findest du auf GitHub, unter MIT-Lizenz: github.com/ElFerminador/m365-carddav. Rückmeldungen und Pull Requests sind willkommen.