JMAP CalendarEvent/set: iMIP invitations silently skipped when the client omits uid (ItipError::MissingUid swallowed)

Issue Description

Creating a calendar event via JMAP CalendarEvent/set with external participants and sendSchedulingMessages: true
→ never sends any iMIP invitation when the client does not provide a uid (which JMAP clients legitimately do on create: the bundled webmail does).
→ The event is created, the response says “created”, and nothing else happens: no queued message, no error, no log line at info level.

Environment: Stalwart 0.16.14 (also reproduced on 0.16.12), Debian 13, single node, everything on the internal RocksDB store, no external directory,
scheduling enabled (CalendarScheduling.enable=Yes), account has calendarSchedulingSend. Participants use calendarAddress (current jscalendarbis form), scheduleAgent: “server”.

Expected Behavior

Per RFC 8984 §4.1.2 every JSCalendar object must carry a uid, so a JMAP create without one should have the server generate it. With sendSchedulingMessages: true and participants with scheduleAgent “server”, an iMIP REQUEST should be queued and sent (as described on the Calendar Scheduling docs page). If the server cannot send scheduling messages, the client should get an error — not a silent success.

Actual Behavior

The event is stored WITHOUT any UID line in the underlying iCalendar object.
itip_create() fails with ItipError::MissingUid (crates/groupware/src/scheduling/
snapshot.rs:355) and the create path in crates/jmap/src/calendar_event/set.rs swallows the error silently (the non-is_jmap_error match arm). No invitation, no error, no log.

Side effects of the same missing UID: the stored object violates RFC 8984, and the
event is not addressable as a CalDAV resource (no .ics href in PROPFIND Depth:1).
The exact same event PUT via CalDAV (UID inside the .ics) sends the REQUEST fine (verified with a real external round-trip through an M365 recipient in ~30 s.)

Reproduction Steps

  1. Scheduling enabled, account with at least one email address.
  2. CalendarEvent/set create WITHOUT uid:
    {“using”:[“urn:ietf:params:jmap:core”,“urn:ietf:params:jmap:calendars”],
    “methodCalls”:[[“CalendarEvent/set”,{“accountId”:“c”,“sendSchedulingMessages”:true,
    “create”:{“t1”:{“@type”:“Event”,“calendarIds”:{“b”:true},“title”:“repro”,
    “start”:“2026-07-24T12:15:00”,“timeZone”:“Europe/Paris”,“duration”:“PT15M”,
    “organizerCalendarAddress”:“mailto:…”,
    “participants”:{
    “o1”:{“@type”:“Participant”,“calendarAddress”:“mailto:…”,
    “roles”:{“owner”:true},“participationStatus”:“accepted”,“expectReply”:false},
    “a1”:{“@type”:“Participant”,“calendarAddress”:“mailto:…”,
    “roles”:{“attendee”:true},“participationStatus”:“needs-action”,
    “expectReply”:true,“scheduleAgent”:“server”}}}}},“0”]]}
  3. Observe: “created” returned, but nothing in the outbound queue, no scheduleStatus,
    guest receives nothing.
  4. Same request WITH “uid”: “[email protected]” → invitation queued and delivered.
  5. Same event via CalDAV PUT → invitation queued and delivered.

Relevant Log Output

Nothing at default logging level — that is part of the bug (the error is swallowed).
Output from a build instrumented around the scheduling block in set.rs:

ITIP-DBG create gate: ssm=true enabled=true addrs=["organizer@…", …] perm=true range_end=1784896200 now=1784833508
ITIP-DBG create: itip_create ERR MissingUid

Same request with an explicit uid: “itip_create OK, 1 msg”.

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

Proposed fix (~15 lines): generate a v4 UUID right after the JSCalendar→iCalendar
conversion in the JMAP create path when no UID is present. Branch:
github fix/jmap-calendar-uid-generation
Running in production on my server since 2026-07-23; external M365 round-trips observed in 16–26 s; destroy correctly sends the CANCEL. Two follow-ups I did not include: log ItipError values that are not is_jmap_error() instead of dropping them, and check whether the copy/import paths can also produce uid-less objects.

My pull request 3226 was auto-closed (not vouched, and the description was flagged as AI-generated — fair enough). Full disclosure: I used an AI assistant to instrument and diagnose this; per CONTRIBUTING I have read and understand every line of the patch, and I’m happy to adjust the implementation or answer anything about it. I’ll sign the FLA.
Asking to be vouched so I can reopen the pull request.

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