Skip to content

fix(dav): send reply email to organizer when attendee responds via invitation link#61213

Open
ndo84bw wants to merge 1 commit into
nextcloud:masterfrom
ndo84bw:fix/dav-invitation-link-organizer-mail
Open

fix(dav): send reply email to organizer when attendee responds via invitation link#61213
ndo84bw wants to merge 1 commit into
nextcloud:masterfrom
ndo84bw:fix/dav-invitation-link-organizer-mail

Conversation

@ndo84bw

@ndo84bw ndo84bw commented Jun 11, 2026

Copy link
Copy Markdown

Summary

When an attendee responds to a calendar invitation via the Accept/Decline links in the invitation email (/apps/dav/invitation/{accept,decline}/{token}), the attendee's participation status is updated on the organizer's event, but the organizer is never notified by email. Responding to the same invitation through the Calendar web UI or through a CalDAV client does notify the organizer. nextcloud/calendar#7636 reports this inconsistency.

Root cause

The invitation links are handled by InvitationResponseController, which delivers the reply through a dedicated minimal DAV server, InvitationResponseServer. Delivering the reply to the attendee's calendar already triggers sabre's scheduling cascade (Sabre\CalDAV\Schedule\Plugin::scheduleLocalDelivery()processICalendarChange()deliver()): a second iTip REPLY addressed to the organizer is generated and emitted as a schedule event. This is how the organizer's copy of the event gets updated today.

Sending the email is the responsibility of IMipPlugin, which listens on that same schedule event. On the regular DAV server it is registered (apps/dav/lib/Server.php), on InvitationResponseServer it is not. The event that would produce the organizer email fires with nobody listening.

Fix

Register IMipPlugin on the InvitationResponseServer, but only in handleITipMessage(), which is exclusively used by the invitation-link controller. It must not be registered unconditionally in the constructor: the same server class is also used by CalendarImpl (e.g. iMIP processing for the Mail app) and by ICreateFromString::createFromStringMinimal(), whose API documentation explicitly guarantees "no iMIP plugin, no invitation emails". Registering the plugin globally would cause duplicate emails in those flows.

With this change, responding via the email links produces the same organizer notification as responding via the Calendar web UI, generated by the same broker/IMip machinery.

Known limitation (intentionally out of scope)

Attendees that are not users on the instance (external attendees) still do not trigger an organizer email when they use the links. Their reply takes a different path: it is delivered directly into the organizer's calendar via scheduleLocalDelivery() without ever passing through the schedule event, and the token-built REPLY object lacks the event data (DTSTART, SUMMARY) needed to render the email. Supporting that case requires enriching the reply from the stored event first, which is a considerably larger change. I would prefer to address it in a follow-up PR so this fix is not blocked by that discussion. Whether nextcloud/calendar#7636 can be closed with this PR alone or only together with the follow-up is up to the maintainers.

TODO

Manual tests

Performed on a Nextcloud 33.0.3 instance with this change applied (handleITipMessage() is identical on master and stable33). User A (organizer) and user B (attendee) are users on the instance, user C is an external attendee invited by email address only.

  • A creates an event in the Calendar app and invites B and C. B opens the Accept link from the invitation email in a private browser tab: the confirmation page is shown, B's participation status changes to accepted on the server, and A receives the "accepted" notification email.
  • B afterwards uses the "More options" link and selects Tentative: the status updates on the server and A receives another notification email.
  • Recurring event: B responds to the whole series via the link: the status is stored on the server and A receives the notification email.
  • Recurring event with a modified single occurrence: A moves one occurrence, B receives a new invitation for it and declines via the link: the declined status is stored for that occurrence only, the remaining occurrences stay unchanged, and A receives the notification email.
  • C (external) opens the Accept link: the participation status updates on the server, the confirmation page is shown. A receives no email (see known limitation above, behaviour unchanged).
  • Regression: B responds to an invitation through the Calendar web UI: the status updates on the server and A receives exactly one notification email, as before this change.

Checklist

  • Code is properly formatted
  • Sign-off message is added to all commits
  • Tests (unit, integration, api and/or acceptance) are included: InvitationResponseServerTest asserts that the constructor does not register the iMIP plugin (the createFromStringMinimal() guarantee) and that handleITipMessage() does.
  • Screenshots before/after for front-end changes: not applicable (backend only)
  • Documentation (manuals or wiki) has been updated or is not required
  • Backports requested where applicable (ex: critical bugfixes)
  • Labels added where applicable (ex: bug/enhancement, 3. to review, feature component)
  • Milestone added for target branch/version (ex: 32.x for stable32)

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

…vitation link

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Nico Donath <ndo84bw@gmx.de>
@susnux susnux added the community pull requests from community label Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community pull requests from community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants