CalendarEvent/set accepts UID-less events without generating a UID

Issue Description

Creating a calendar event through CalendarEvent/set with the uid property omitted succeeds, but Stalwart stores the event without a UID.

This affects both ordinary and recurring events. For recurring events, the missing UID makes it difficult or impossible for clients to associate expanded occurrences with the underlying series. It also produces malformed CalDAV data for clients such as Thunderbird, which rely on UID as the stable identity of an event or recurrence series.

The affected code path appears to validate uniqueness using:

assert_is_unique_uid(self, account_id, ical.uids().next())

When ical.uids().next() returns None, the uniqueness check succeeds, and the event is stored without generating a UID.

Expected Behavior

When uid is omitted from a newly created CalendarEvent, Stalwart should generate a new globally unique UID before storing the event.

The JMAP Calendars draft states:

If omitted on create, the server MUST set the following properties:

  • @type MUST be set to "Event"
  • uid MUST be set to a new globally unique identifier, such as a UUID
  • created MUST be set to the current date-time

This is specified in the CalendarEvent/set creation semantics in draft-ietf-jmap-calendars-26.

Actual Behavior

Stalwart accepts the creation request but stores and returns the event without a uid.

For example, the following recurring event was successfully created:

{
  "using": [
    "urn:ietf:params:jmap:core",
    "urn:ietf:params:jmap:calendars"
  ],
  "methodCalls": [
    [
      "CalendarEvent/set",
      {
        "accountId": "c",
        "create": {
          "event": {
            "@type": "Event",
            "calendarIds": {
              "c": true
            },
            "title": "Test event",
            "start": "2026-07-25T09:00:00",
            "duration": "PT1H",
            "timeZone": "Pacific/Auckland",
            "recurrenceRule": {
              "frequency": "daily",
              "interval": 1
            }
          }
        }
      },
      "create"
    ]
  ]
}

A subsequent CalendarEvent/get returned the stored base event without a UID:

{
  "title": "Test event",
  "duration": "PT3600S",
  "recurrenceRule": {
    "frequency": "daily",
    "interval": 1
  },
  "timeZone": "Pacific/Auckland",
  "start": "2026-07-25T09:00:00",
  "showWithoutTime": false,
  "@type": "Event",
  "id": "ta",
  "calendarIds": {
    "c": true
  },
  "isDraft": false,
  "isOrigin": true
}

The expected generated uid property is absent.

Reproduction Steps

  1. Configure an account with JMAP Calendars enabled.
  2. Send a CalendarEvent/set create request.
  3. Include the required event properties but omit uid.
  4. Confirm that the create operation succeeds.
  5. Fetch the created event using CalendarEvent/get.
  6. Observe that the returned event has no uid.
  7. Optionally create the event with a recurrence rule.
  8. Query it with expandRecurrences: true.
  9. Observe that the expanded occurrences also have no UID, making recurrence-series association unreliable.

Relevant Log Output

No log output

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

This confirms the reported behavior in the source: in calendar_event/set.rs, the create path calls assert_is_unique_uid(self, account_id, ical.uids().next()), and when there is no UID component in the submitted iCalendar object, ical.uids().next() simply returns None, which passes the uniqueness check trivially rather than triggering UID generation. There’s no fallback that generates a new UID and injects it into the stored iCalendar data when one is missing, unlike what the JMAP Calendars draft requires for CalendarEvent/set create.

This looks like a genuine bug matching your description rather than something covered in current docs. I don’t see a documented workaround or note about this behavior in the docs, so I’ll leave this for a human maintainer to confirm and address, since it involves a source code fix (generating and injecting a UID on create when omitted) rather than a documentation or configuration answer.

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