Issue Description
I found this with the Bulwark webmail: events created in Bulwark have no PRODID, no VTIMEZONE and an alarm without DESCRIPTION when read via CalDAV. The problem is not specific to Bulwark. It reproduces with a plain CalendarEvent/set request (see Reproduction Steps).
When a calendar event is created or updated via JMAP (CalendarEvent/set), the iCalendar representation that CalDAV returns (GET and calendar-query REPORT) is not a valid RFC 5545 object:
PRODIDis missing. RFC 5545 §3.6 requires it in every VCALENDAR. The JSCalendar→iCalendar export adds a fallbackVERSION:2.0but no fallbackPRODID.- No
VTIMEZONEfor the referencedTZID.DTSTART;TZID=Europe/Rome:...is emitted without a matching VTIMEZONE. RFC 5545 §3.2.19 says a VTIMEZONE must be present for each unique TZID parameter value. (JMAP invitation missing VTIMEZONE causes offset in Outlook fixed the same problem for iTIP messages generated from JMAP events. The CalDAV representation is still affected.) ACTION:DISPLAYVALARM withoutDESCRIPTION. RFC 5545 §3.6.6 requires DESCRIPTION for display alarms. A JSCalendar alert has no description, and none is added during conversion.
Strict clients reject these objects. For example, go-ical (used by several Go CalDAV clients) refuses to re-encode the fetched object (want exactly one PRODID property, got 0), so a client that fetches an event and PUTs it back fails. Events created via CalDAV are not affected: they keep the PRODID and VTIMEZONE the client sent.
Expected Behavior
The CalDAV representation of a JMAP-created event is valid RFC 5545:
- it contains a
PRODID(e.g. Stalwart’s own, the same wayVERSIONis already added), - it contains a
VTIMEZONEfor every TZID it references, - each
ACTION:DISPLAYVALARM contains aDESCRIPTION(e.g. the event title, or a generic “Reminder”).
Actual Behavior
The event from the reproduction below, as returned by GET / calendar-query REPORT:
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
DTSTART;TZID=Europe/Rome:20261001T120000
SUMMARY:Test
DURATION:PT1H
DTSTAMP:20260928T203943Z
UID:52b36a9f-3529-4461-a2a6-9b73be118fbf
BEGIN:VALARM
TRIGGER:-PT1H
ACTION:DISPLAY
JSID:a1
END:VALARM
END:VEVENT
END:VCALENDAR
The result is the same after a subsequent JMAP update of the event.
An event created in Bulwark (with a 1-hour alert) comes back from CalDAV with the same problems:
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
DTSTART;TZID=Europe/Rome:20260930T220000
UID:4fcf575e-fbc1-48bb-9c5c-a5092edd9661
SUMMARY:Test
DESCRIPTION:
DURATION:PT1H
STATUS:CONFIRMED
TRANSP:OPAQUE
SHOW-WITHOUT-TIME:FALSE
CLASS:PUBLIC
DTSTAMP:20260928T193239Z
BEGIN:VALARM
TRIGGER;RELATED=START:-PT1H
ACTION:DISPLAY
JSPROP;JSPTR=acknowledged:null
JSPROP;JSPTR=relatedTo:null
JSID:alert1
END:VALARM
END:VEVENT
END:VCALENDAR
Reproduction Steps
- Create an event via JMAP:
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:calendars"],
"methodCalls": [["CalendarEvent/set", {
"accountId": "<account>",
"create": {"a": {
"@type": "Event",
"calendarIds": {"<calendarId>": true},
"title": "Stalwart repro (JMAP)",
"start": "2026-10-01T12:00:00",
"timeZone": "Europe/Rome",
"duration": "PT1H",
"alerts": {"a1": {
"@type": "Alert",
"trigger": {"@type": "OffsetTrigger", "offset": "-PT1H"},
"action": "display"
}}
}}
}, "0"]]
}
- Fetch it via CalDAV, e.g. with a
calendar-queryREPORT filtered by UID, or aGETon its href (in my run:/dav/cal/<account>/default/<uid>). - The returned object has no
PRODID, noVTIMEZONE, and a DISPLAY VALARM withoutDESCRIPTION(see above).
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
PostgreSQL
Blob Storage
PostgreSQL
Search Engine
PostgreSQL
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
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