Issue Description
About 5,000 calendar events were imported into Stalwart through JMAP (vandelay)
from another server; others were later edited in a JMAP webmail. Apple Calendar
(macOS 10.13.6 and 10.14.6) can display these events but cannot modify them:
moving an event to another day looks fine for a few seconds, then the client
silently reverts it. Events created by Apple Calendar itself over CalDAV can be
modified normally. On our server 3,288 of 4,981 events are affected: all of them
were created via JMAP and have at least one VALARM with a UID property (Apple
clients write UID and X-WR-ALARMUID on every alarm).
Expected Behavior
A PUT with a matching If-Match ETag whose VEVENT keeps the same UID as the stored
event is accepted, whatever the UIDs of its VALARM sub-components.
Actual Behavior
The PUT is rejected with 412 and the no-uid-conflict precondition, although the
VEVENT UID is unchanged (stored event, GET over CalDAV, abridged):
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:38B59F21-B572-4C1B-9DDC-998D657ABC33
…
BEGIN:VALARM
UID:60FB50D0-B8D8-43A4-9565-783F34C6024E
X-WR-ALARMUID:60FB50D0-B8D8-43A4-9565-783F34C6024E
…
END:VALARM
(three more VALARMs, each with its own UID)
END:VEVENT
END:VCALENDAR
What we read in the source at tag v0.16.24:
- crates/dav/src/calendar/update.rs compares
event.inner.data.event.uids().next()with the UID returned by
validate_ical(&ical); validate_ical() only collects UIDs of VEVENT/VTODO/
VJOURNAL/VFREEBUSY/VAVAILABILITY. - Cargo.lock pins calcard 0.3.14, where
ArchivedICalendar::uids()iterates
ALL components, VALARM included. - The JSCalendar → iCalendar export (calcard src/jscalendar/export/convert.rs)
pushes each VALARM withpush_component(alert)while its parent is being
built, and the parent VEVENT is pushed afterwards. For an event created via
JMAP the component order is therefore VCALENDAR, VALARM…, VEVENT, and the
first UID found is the alarm’s.
calcard 0.4.0 (commit f84db86, “Compliance fixes”, 22/09) restricts uids() to
calendar objects and VCALENDAR, which would fix it; Cargo.lock on main still
pins 0.3.14 (checked 01/10).
Reproduction Steps
- Import via JMAP an event whose iCalendar source has a VALARM with a UID
(we used vandelay with events exported from a CalDAV server; Apple Calendar
writes UID and X-WR-ALARMUID on every VALARM). - GET the event over CalDAV: the resource name has no .ics extension; note
the ETag; the VEVENT UID differs from the VALARM UIDs. - PUT the same iCalendar back with only DTSTART/DTEND changed and
If-Match: <etag>. - The server answers 412 with no-uid-conflict.
- Create the same event by PUT (text order: VEVENT before its VALARMs) and
repeat step 3: the PUT is accepted.
Relevant Log Output
2026-10-01T10:56:16Z INFO WebDAV PUT request (web-dav.put) listenerId = "https", localPort = 443, remoteIp = 192.168.12.51, url = "/dav/cal/<account>%40<domain>/OAMjbx41J9/Trb1umzjhN", type = "CalDAV", details = ["Content-Type", "text/calendar", "If", ["ETag", "\"746741392\""]], code = 412, result = "NoUidConflict", reason = (null), elapsed = 0ms
Stalwart Version
v0.16.x
Installation Method
Binary (Linux)
Database Backend
RocksDB
Blob Storage
Filesystem
Search Engine
Internal
Directory Backend
Internal
Additional Context
Clients: Apple Calendar on macOS 10.13.6 and 10.14.6, CalDAV over HTTPS on 443,
no reverse proxy. Workaround that works: copy/paste the event in the client and
delete the original (the copy is created by PUT, so its VEVENT comes first).
The VEVENT UID never changes here, so RFC 4791 section 5.3.2.1 does not apply:
a VALARM UID (RFC 9074) is not the UID of the calendar object resource.
Related topics, none of them a duplicate: 1702 (CardDAV, same precondition,
urn:uuid prefix, a genuine UID change), 1085 (calendar-query on JMAP-created
events, fixed in 0.16.14; on 0.16.24 our REPORTs return the data), 1819 and
1714 (JMAP-created events served over CalDAV), the “iMIP invitations silently
skipped when the client omits uid” topic (missing UID, a different case). If v1.0.0 already ships
calcard >= 0.4.0, a confirmation is enough for us: we will wait for 1.0 and
keep the workaround until then.
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
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on