Skip to content

IONOS(sharebymail): add BeforeShare*MailSentEvent dispatched before each mail send (HDNEXT-1010)#262

Merged
tanyaka merged 7 commits into
ionos-dev-v30from
HDNEXT-1010-add-share-mail-event
Jul 3, 2026
Merged

IONOS(sharebymail): add BeforeShare*MailSentEvent dispatched before each mail send (HDNEXT-1010)#262
tanyaka merged 7 commits into
ionos-dev-v30from
HDNEXT-1010-add-share-mail-event

Conversation

@printminion-co

@printminion-co printminion-co commented Jun 12, 2026

Copy link
Copy Markdown

Summary

Introduces a PSR-14 event hook inside ShareByMailProvider that allows app-layer listeners to replace the native Nextcloud SMTP path for share-by-mail notifications.

New event classes in apps/sharebymail/lib/Event/

  • AbstractBeforeShareMailSentEvent — abstract base carrying the fully-prepared IMessage, validated recipient list, pre-computed mail data, and a markMailHandled() flag
  • BeforeShareMailSentEvent — fired by sendEmail() before the share-link notification send
  • BeforeSharePasswordMailSentEvent — fired by sendPassword() and sendPasswordToOwner() before password emails
  • BeforeShareNoteMailSentEvent — fired by sendNote() before note-update emails

How the replacement mechanism works

When a listener calls $event->markMailHandled(), the native mailer->send() is skipped. If the listener's own send throws, the exception propagates — native send is also skipped. No silent fallback. When no listener is active, native SMTP runs as before.

mailData array

Each send method passes the data it already computed before building the IMessage. Listeners read $event->getMailData() instead of re-deriving from the share, avoiding duplicate user/node lookups.

BeforeShareMailSentEvent carries: senderUserId, fileName, resourceUrl, note, expiration (\DateTime|null).

Dispatch point

Events fire inside each private send method immediately before mailer->send() — covering both Manager::createShare() and sendShareEmail() Controller paths without touching either.

Related PRs

Test plan

  • apps/sharebymail/ unit tests pass
  • E2E: file request → send email via UI → IONOS API called; no native SMTP
  • E2E: regular share by email → IONOS API called once; no double-send
  • E2E: disable nc_ionos_processes → native SMTP resumes without code changes

@printminion-co printminion-co force-pushed the HDNEXT-1010-add-share-mail-event branch 2 times, most recently from 0dfb4f0 to 05ca5da Compare June 12, 2026 12:45
@printminion-co printminion-co force-pushed the HDNEXT-1010-add-share-mail-event branch from 05ca5da to e5d9cd6 Compare June 15, 2026 13:46
@printminion-co printminion-co changed the title feat(files_sharing): dispatch BeforeShareMailNotifiedEvent from sendShareEmail IONOS(sharebymail): add BeforeShare*MailSentEvent dispatched before each mail send (HDNEXT-1010) Jun 15, 2026
@printminion-co printminion-co force-pushed the HDNEXT-1010-add-share-mail-event branch 3 times, most recently from 450ed8c to ccc0f83 Compare June 15, 2026 15:20
@tanyaka tanyaka force-pushed the HDNEXT-1010-add-share-mail-event branch 5 times, most recently from 0d58932 to 6b86f4e Compare June 26, 2026 08:57
@tanyaka

tanyaka commented Jun 26, 2026

Copy link
Copy Markdown

Code review

Found 2 issues:

  1. createPasswordSendActivity() fires unconditionally when mail is suppressed by a listener

In both sendPassword() and sendPasswordToOwner(), createPasswordSendActivity() is called outside the if (!$event->isMailHandled()) guard. This means every time the companion listener (nc-ionos-processes) calls markMailHandled() to intercept a password email, an activity-log entry is still written for a mail that was never sent by the native mailer. sendEmail() avoids this by using an early return instead, so no side effects run after the flag is set.

}
$message->useTemplate($emailTemplate);
$event = new BeforeSharePasswordMailSentEvent($share, $emails, $message, $templateData);
$this->eventDispatcher->dispatchTyped($event);
if (!$event->isMailHandled()) {
$failedRecipients = $this->mailer->send($message);
if (!empty($failedRecipients)) {
$this->logger->error('Share password mail could not be sent to: ' . implode(', ', $failedRecipients));
return false;
}
}
$this->createPasswordSendActivity($share, $shareWith, false);
return true;
}

$event = new BeforeSharePasswordMailSentEvent($share, [$initiatorEMailAddress], $message, $templateData);
$this->eventDispatcher->dispatchTyped($event);
if (!$event->isMailHandled()) {
$this->mailer->send($message);
}
$this->createPasswordSendActivity($share, $shareWith, true);
return true;
}

  1. BeforeShareNoteMailSentEvent has no test coverage

The PR adds test expectations for BeforeShareMailSentEvent and BeforeSharePasswordMailSentEvent via withConsecutive, but BeforeShareNoteMailSentEvent is never imported in the test file and the sendNote() dispatch path has no corresponding dispatchTyped expectation. The sendNote() event firing and its markMailHandled() bypass are therefore unverified.

use OCP\Mail\IMessage;
use OCA\ShareByMail\Event\BeforeShareMailSentEvent;
use OCA\ShareByMail\Event\BeforeSharePasswordMailSentEvent;

->willReturn('autogeneratedPassword');
$this->eventDispatcher->expects($this->exactly(3))
->method('dispatchTyped')
->withConsecutive(
[new GenerateSecurePasswordEvent()],
[$this->isInstanceOf(BeforeShareMailSentEvent::class)],
[$this->isInstanceOf(BeforeSharePasswordMailSentEvent::class)],
);

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tanyaka tanyaka force-pushed the HDNEXT-1010-add-share-mail-event branch 2 times, most recently from 2d0c98c to c0d9b40 Compare June 30, 2026 10:35
@bromiesTM bromiesTM requested a review from Copilot July 1, 2026 08:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds PSR-14 “BeforeShare*MailSentEvent” hooks to apps/sharebymail so app-layer listeners can intercept share-by-mail outgoing messages and optionally suppress the native mailer->send() path.

Changes:

  • Dispatch BeforeShareMailSentEvent, BeforeSharePasswordMailSentEvent, and BeforeShareNoteMailSentEvent immediately before sending share-by-mail notifications.
  • Refactor mail template input into $templateData arrays that are passed to both createEMailTemplate() and the dispatched events.
  • Extend unit tests to assert event dispatch and “handled” behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
apps/sharebymail/lib/ShareByMailProvider.php Dispatches new events before mailer->send() in share, password, note, and owner-password flows.
apps/sharebymail/lib/Event/AbstractBeforeShareMailSentEvent.php Introduces shared event base with message, recipients, and “handled” flag.
apps/sharebymail/lib/Event/BeforeShareMailSentEvent.php Adds event for share-link notification email sends.
apps/sharebymail/lib/Event/BeforeSharePasswordMailSentEvent.php Adds event for password email sends (recipient + owner flows).
apps/sharebymail/lib/Event/BeforeShareNoteMailSentEvent.php Adds event for note-update email sends.
apps/sharebymail/tests/ShareByMailProviderTest.php Updates expectations for added dispatches and adds tests for note + handled password scenarios.
apps/sharebymail/composer/composer/autoload_static.php Updates classmap to include new event classes.
apps/sharebymail/composer/composer/autoload_classmap.php Updates classmap to include new event classes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/sharebymail/lib/Event/AbstractBeforeShareMailSentEvent.php
Comment thread apps/sharebymail/lib/ShareByMailProvider.php
Comment thread apps/sharebymail/lib/ShareByMailProvider.php
…ateData variables

Extract the inline array literals passed to each createEMailTemplate() call
into named $templateData variables. For sendNote(), which previously passed no
data array, add a minimal array so all four send methods are consistent.

Zero behaviour change — $templateData is passed straight through to
createEMailTemplate() with identical contents.
@tanyaka tanyaka force-pushed the HDNEXT-1010-add-share-mail-event branch from c0d9b40 to 71f4973 Compare July 3, 2026 08:59
printminion-co and others added 6 commits July 3, 2026 11:34
…ach mail send

Dispatch a typed PSR-14 event immediately before every mailer->send() call
in ShareByMailProvider so that external listeners can intercept and replace
Nextcloud's native SMTP delivery.

Event hierarchy:
  AbstractBeforeShareMailSentEvent (base: share, resolvedEmails, message,
    markMailHandled / isMailHandled)
  ├── BeforeShareMailSentEvent   – sendEmail()
  ├── BeforeSharePasswordMailSentEvent – sendPassword() + sendPasswordToOwner()
  └── BeforeShareNoteMailSentEvent – sendNote()

Each concrete class holds its own typed $templateData (psalm array-shape)
and exposes named getters (getSenderUserId(), getFileName(), …) instead of
a generic getMailData(): array<string,mixed>.  This avoids defensive
is_string() / null guards in listeners.

$templateData reuses the array already passed to createEMailTemplate(), with
one extra key added for sendEmail(): senderUserId (the raw user ID, distinct
from the display name stored under 'initiator').

The native mailer->send() is skipped when a listener calls markMailHandled().
If the listener's own send throws, the exception propagates and the native
send is also skipped — no silent SMTP fallback.

sendEmail() and sendPassword() are flattened from nested
if (!isMailHandled()) { ... } pyramids to early-return style.

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
…ssmap

autoload_real.php sets setClassMapAuthoritative(true), which disables
PSR-4 fallback entirely. Any class not listed in $classMap is never
found, regardless of whether PSR-4 would resolve it.

Signed-off-by: Tatjana Kaschperko Lindt <kaschperko-lindt@strato.de>
…a and event refactors

- Add senderUserId to the expected RecipientNotification templateData in 3 tests
  (added by the createEMailTemplate params refactor in 242b896)
- Update dispatchTyped expectation from once(GenerateSecurePasswordEvent) to
  exactly(3) with withConsecutive for GenerateSecurePasswordEvent,
  BeforeShareMailSentEvent, and BeforeSharePasswordMailSentEvent
  (sendEmail() and sendPassword() now each dispatch a Before*Event)

Signed-off-by: Tatjana Kaschperko Lindt <kaschperko-lindt@strato.de>
…t dispatch

Signed-off-by: Tatjana Kaschperko Lindt <kaschperko-lindt@strato.de>
…s suppressed

sendPassword() and sendPasswordToOwner() called createPasswordSendActivity()
unconditionally outside the isMailHandled() guard. A listener intercepting the
BeforeSharePasswordMailSentEvent would suppress the SMTP send but still cause
a false activity-log entry for a mail that was never delivered.

Apply the same early-return pattern used by sendEmail(): return immediately
when isMailHandled() is true so no side effects run after the flag is set.

Signed-off-by: Tatjana Kaschperko Lindt <kaschperko-lindt@strato.de>
…r "file request")

https://github.com/IONOS-Productivity/nc-ionos-processes/releases/tag/1.0.0-9501a5e

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
Co-authored-by: Tatjana Kaschperko Lindt <kaschperko-lindt@strato.de>
@tanyaka tanyaka force-pushed the HDNEXT-1010-add-share-mail-event branch from 71f4973 to e97f706 Compare July 3, 2026 09:35
@tanyaka tanyaka merged commit 4a6159d into ionos-dev-v30 Jul 3, 2026
8 of 9 checks passed
@tanyaka tanyaka deleted the HDNEXT-1010-add-share-mail-event branch July 3, 2026 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants