Calendar Scheduling in Caldav not working in Apple Calendar

Issue Description

Apple Calendar usually supports Scheduling and Principal Queries when creating events for a CalDav Calendar (I have a working example with OX).
According to the documentation and the CalDav endpoints, Stalwart should also support the whole scheduling and availability stuff, but Apple Calendar does neither query the users nor show the “Show Availabilities” button for my Stalwart CalDav.

In case this helps somebody, I can share the DAV Headers and report sets from OX - to me it seems like the feature is just not listed in the capability checks, because per documentation stalwart should support exactly this feature.

Expected Behavior

Scheduling view of invitees calendars and availabilies

Actual Behavior

Feature not available

Reproduction Steps

  1. Setup Stalwart
  2. Connect in Apple Calendar
  3. Try to create event and invite people

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

The code confirms that calendar-user-address-set, schedule-inbox-URL, and schedule-outbox-URL are all implemented in Stalwart’s DAV layer. This means the scheduling infrastructure is present. Let me check what the actual propfind handler returns and look for any known Apple Calendar specific quirks.

Based on my research, here is what can be confirmed:

Stalwart does implement the CalDAV Scheduling Extensions (RFC 6638), including calendar-user-address-set, schedule-inbox-URL, schedule-outbox-URL, and free/busy support at the protocol level. The fact that Apple Calendar is not showing the “Show Availabilities” button or querying users is most likely a capability advertisement issue – Apple Calendar is very picky about how servers advertise scheduling support in their PROPFIND responses, and a mismatch or missing property can silently disable the entire scheduling UI.

A few things worth checking and providing in your report that will help diagnose this:

  1. Sharing the DAV headers/PROPFIND response from your Stalwart server – as you mentioned you have a working OX example, a diff of the two OPTIONS / PROPFIND responses (especially the DAV header capability tokens and supported-report-set property) would be very valuable for narrowing down exactly which capability Apple Calendar is looking for but not seeing.

  2. Verify scheduling is enabled – in the WebUI under Settings › Calendar & Contacts › Scheduling, confirm that the enable field is true (it should be on by default).

  3. Verify user permissions – ensure the account has the calendarSchedulingSend and calendarSchedulingReceive permissions granted. These can be scoped per-account and may be missing if permissions were customized.

Sharing your DAV capability headers from both OX and Stalwart here would be very helpful for the community to compare and identify the gap. The scheduling docs are at Scheduling | Stalwart and the relevant permissions are listed at Permissions | Stalwart.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Here are the responses from OX compared to those from stalwart:

Stalwart Propfind supported-report-set:

<?xml version="1.0" encoding="UTF-8"?><D:multistatus xmlns:D="DAV:"><D:response><D:href>/dav/pal/user%40stalwart.server/</D:href><D:propstat><D:prop><D:current-user-privilege-set/></D:prop><D:status>HTTP/1.1 404 Not Found</D:status></D:propstat><D:propstat><D:prop><D:supported-report-set><D:supported-report><D:report><D:principal-property-search/></D:report></D:supported-report><D:supported-report><D:report><D:principal-search-property-set/></D:report></D:supported-report><D:supported-report><D:report><D:principal-match/></D:report></D:supported-report></D:supported-report-set><D:resourcetype><D:principal/><D:collection/></D:resourcetype></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response></D:multistatus>

Stalwart schedule urls:

<?xml version="1.0" encoding="UTF-8"?><D:multistatus xmlns:D="DAV:" xmlns:A="urn:ietf:params:xml:ns:caldav"><D:response><D:href>/dav/pal/user%40stalwart.server/</D:href><D:propstat><D:prop><A:schedule-inbox-URL><D:href>/dav/itip/user%40stalwart.server/inbox/</D:href></A:schedule-inbox-URL><A:schedule-outbox-URL><D:href>/dav/itip/user%40stalwart.server/outbox/</D:href></A:schedule-outbox-URL><A:calendar-user-address-set><D:href>mailto:[email protected]</D:href></A:calendar-user-address-set></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response></D:multistatus>

OX supported report set:

<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:APPLE="http://apple.com/ns/ical/" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/">
  <D:response>
    <D:href>/principals/users/11</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype>
          <D:principal />
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:current-user-privilege-set />
        <D:supported-report-set />
      </D:prop>
      <D:status>HTTP/1.1 404 NOT FOUND</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

OX schedule URLs:

<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:APPLE="http://apple.com/ns/ical/" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/">
  <D:response>
    <D:href>/principals/users/11</D:href>
    <D:propstat>
      <D:prop>
        <schedule-outbox-URL xmlns="urn:ietf:params:xml:ns:caldav">
          <D:href>/caldav/schedule-outbox</D:href>
        </schedule-outbox-URL>
        <calendar-user-address-set xmlns="urn:ietf:params:xml:ns:caldav">
          <D:href preferred="1">mailto:[email protected]</D:href>
          <D:href>mailto:[email protected]</D:href>
          <D:href>/principals/users/11</D:href>
          <D:href>urn:uuid:0002f414-0000-000b-00fc-c0e11e000001</D:href>
        </calendar-user-address-set>
        <schedule-inbox-URL xmlns="urn:ietf:params:xml:ns:caldav">
          <D:href>/caldav/schedule-inbox</D:href>
        </schedule-inbox-URL>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

Stalwart DAV Header:

1, 2, 3, access-control, extended-mkcol, calendar-access, calendar-auto-schedule, calendar-no-timezone, addressbook

OX DAV Header:

1,2,3,access-control,calendar-access,addressbook,extended-mkcol,calendar-auto-schedule,calendar-schedule,calendarserver-sharing,calendarserver-principal-search,calendarserver-principal-property-search,calendarserver-subscribed,resource-sharing,calendar-managed-attachments,calendarserver-private-events,calendarserver-recurrence-split

Maybe somebody has already successfully used the whole feature and knows what I am maybe missing? It doesn’t seem to work in Thunderbird either.

I just realized, that the problem might be much deeper than I thought.

No matter whether I use thunderbird, Apple Calendar or Bulwark: I have the feeling, that all calendar invitations and participants are handled like external people, which is why it all does not work as expected. I did a very long debugging session with some AI Agent I prepared for debugging CalDAV problems using curl.

I dug into this and I don’t think it’s the bug it looks like at first glance, so let me try to untangle the different pieces.

The core of CalDAV scheduling, sending and receiving invitations, actually does work against Stalwart on Apple Calendar. I have it running here and invites flow in both directions. That part relies on the standard automatic scheduling mechanism that Stalwart already advertises, where the client just adds an attendee and saves the event, and the server takes care of delivering the invitation. It never needs to look anything up to do that, which is why it works even with everything else locked down.

The difference you spotted in the headers is a bit of a red herring. The token the other server advertises that Stalwart doesn’t is from the older, pre standard draft of CalDAV scheduling, and Stalwart deliberately implements the modern IETF standard instead. Advertising that older token without implementing the matching behaviour behind it would actually be worse, because clients would then try operations that the server cannot answer. So that is not something we want to add.

The two things you are actually noticing come down to this. First, the directory and participant lookup features. Stalwart disables directory queries by default for privacy reasons, so that users cannot enumerate or search other accounts on the server unless an administrator turns that on. With it off, the searches and cross account lookups that Apple Calendar uses to populate attendee suggestions come back empty, which is probably what you are seeing. If you want that behaviour, an administrator can enable directory queries in the configuration. Worth knowing as well that Apple also has its own proprietary, non standard search extension that Stalwart does not implement, so even with directory queries enabled the experience may not be identical to a server that speaks Apple’s own dialect.

Second, the Show Availability button. That one is largely on Apple’s side. Apple removed or broke the availability lookup over CalDAV starting around macOS Big Sur, and it has been flaky or absent ever since regardless of which server you point it at; Apple themselves steer people towards Exchange for that feature. So the missing button is not something the server headers control.

So in short: sending and receiving invitations works and is standards compliant, the missing participant search is an intentional privacy default you can switch on, and the availability button is an Apple client limitation (you can provide raw HTTP requests/responses to confirm).