fix(calendar): send invitation emails client-side via iMIP
Publish Docker Image / prepare (push) Successful in 7s
Publish Docker Image / build (linux/amd64, ubuntu-latest) (push) Failing after 7s
Publish Docker Image / build (linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
Publish Docker Image / merge (push) Canceled after 0s

This commit is contained in:
2026-08-24 17:23:47 +02:00
parent 60fde141b5
commit 88bca86c1e
2 changed files with 61 additions and 19 deletions
+35 -9
View File
@@ -487,9 +487,16 @@ export const useCalendarStore = create<CalendarStore>()(
}
set((state) => ({ events: [...state.events, mappedCreated] }));
// Invitation emails are sent by the server: `sendSchedulingMessages`
// on CalendarEvent/set makes Stalwart queue the iTIP REQUEST itself.
// Sending a client-side iMIP copy here produced duplicate emails.
// Send invitation emails (iTIP REQUEST) to participants. Stalwart
// 0.16 does not reliably queue these server-side via
// `sendSchedulingMessages`, so fall back to a client-side iMIP send.
if (sendSchedulingMessages && created.participants) {
try {
await client.sendImipInvitation(created);
} catch (e) {
debug.error('Failed to send invitation emails:', e);
}
}
return mappedCreated;
} catch (error) {
debug.error('Failed to create event:', error);
@@ -559,9 +566,23 @@ export const useCalendarStore = create<CalendarStore>()(
return merged;
}),
}));
// Update emails (iTIP REQUEST/REPLY) are sent by the server via the
// `sendSchedulingMessages` argument already passed above - a manual
// iMIP send here produced duplicate emails.
// Send invitation emails (iTIP REQUEST) when scheduling is requested.
if (sendSchedulingMessages) {
const mergedParticipants = cleanUpdates.participants ?? storeEvent?.participants;
if (mergedParticipants) {
const eventForInvitation = {
...(storeEvent ?? {}),
...cleanUpdates,
id: realId,
participants: mergedParticipants,
} as CalendarEvent;
try {
await client.sendImipInvitation(eventForInvitation);
} catch (e) {
debug.error('Failed to send invitation emails:', e);
}
}
}
} catch (error) {
debug.error('Failed to update event:', error);
if (isNetworkError(error)) {
@@ -808,9 +829,6 @@ export const useCalendarStore = create<CalendarStore>()(
try {
// Resolve shared event IDs and client-side expanded occurrence IDs
client = resolveAccountClient(client, storeEvent?.localAccountId);
// Cancellation emails (iTIP CANCEL) are sent by the server via the
// `sendSchedulingMessages` argument on the destroy below - a manual
// iMIP send here produced duplicate emails.
debug.log('calendar', 'Calendar deleteEvent', {
storeId: id,
realId,
@@ -823,6 +841,14 @@ export const useCalendarStore = create<CalendarStore>()(
events: state.events.filter(e => e.id !== id),
selectedEventId: state.selectedEventId === id ? null : state.selectedEventId,
}));
// Send cancellation emails (iTIP CANCEL) to participants.
if (sendSchedulingMessages && storeEvent?.participants) {
try {
await client.sendImipCancellation(storeEvent);
} catch (e) {
debug.error('Failed to send cancellation emails:', e);
}
}
} catch (error) {
debug.error('Failed to delete event:', error);
if (isNetworkError(error)) {