Issue Description
CalendarEvent/get omits the baseEventId property from synthetic recurring-event occurrences when the request does not specify a properties list.
Under JMAP semantics, omitting properties requests all properties. A synthetic expanded occurrence should therefore include baseEventId, since that property identifies the real base event from which the occurrence was generated.
Stalwart does return the correct baseEventId when that property is explicitly requested. The issue is specifically with the default all-properties response.
This can prevent clients from associating expanded occurrence IDs with editable base events, particularly when the event UID is missing or malformed.
Expected Behavior
For a synthetic event ID returned from a query with expandRecurrences: true, a subsequent CalendarEvent/get with properties omitted should include:
{
"id": "synthetic-id",
"baseEventId": "base-id",
"recurrenceId": "2026-07-25T09:00:00"
}
The JMAP Calendars draft defines baseEventId as:
This is only defined if the
idproperty is a synthetic id, generated by the server to represent a particular instance of a recurring event. This property gives the id of the realCalendarEventthis was generated from.
When the properties argument is omitted, the server should return all properties of the object, including server-defined JMAP properties applicable to that object.
Actual Behavior
When properties is omitted, Stalwart returns the synthetic occurrence without baseEventId.
For example:
{
"using": [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:calendars"
],
"methodCalls": [
[
"CalendarEvent/get",
{
"accountId": "c",
"ids": [
"iiaaaaa3"
],
"timeZone": "Pacific/Auckland"
},
"all"
]
]
}
The response included:
{
"recurrenceId": "2026-07-25T02:20:00",
"uid": "[email protected]",
"title": "Water Softener Running",
"start": "2026-07-25T02:20:00",
"id": "iiaaaaa3",
"calendarIds": {
"c": true
},
"isDraft": false,
"isOrigin": true
}
The baseEventId property is absent.
However, explicitly requesting the property:
{
"accountId": "c",
"ids": [
"iiaaaaa3"
],
"properties": [
"id",
"baseEventId",
"uid",
"recurrenceId",
"title"
],
"timeZone": "Pacific/Auckland"
}
returns the correct value:
{
"uid": "[email protected]",
"recurrenceId": "2026-07-25T02:20:00",
"title": "Water Softener Running",
"id": "iiaaaaa3",
"baseEventId": "3"
}
Therefore, Stalwart can calculate the property correctly but omits it from the default all-properties response.
Reproduction Steps
-
Create or import a recurring calendar event.
-
Run
CalendarEvent/querywith:expandRecurrences: true- bounded
afterandbeforevalues.
-
Select one of the returned synthetic occurrence IDs.
-
Call
CalendarEvent/getfor that synthetic ID with thepropertiesargument omitted. -
Observe that
baseEventIdis absent. -
Repeat the same request while explicitly including
baseEventIdinproperties. -
Observe that Stalwart now returns the correct base event ID.
Relevant Log Output
No error is logged because both requests complete successfully.
The relevant responses are:
CalendarEvent/get with properties omitted:
id = iiaaaaa3
baseEventId = absent
CalendarEvent/get with baseEventId explicitly requested:
id = iiaaaaa3
baseEventId = 3
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
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