Skip to content

feat(sharebymail): dispatch BeforeShare*MailSentEvent before each mail send#61948

Open
printminion-co wants to merge 2 commits into
nextcloud:masterfrom
IONOS-Productivity:feat/sharebymail-before-mail-sent-event
Open

feat(sharebymail): dispatch BeforeShare*MailSentEvent before each mail send#61948
printminion-co wants to merge 2 commits into
nextcloud:masterfrom
IONOS-Productivity:feat/sharebymail-before-mail-sent-event

Conversation

@printminion-co

@printminion-co printminion-co commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

ShareByMailProvider sends share-by-mail notifications straight through the native mailer with no
extension point. This adds a typed PSR-14 event dispatched immediately before every
mailer->send(), so an app can intercept and replace delivery (e.g. route through a different
transport) without patching the provider.

A listener calls markMailHandled() to take ownership of delivery; the native mailer->send() is
then skipped. If the listener throws, the exception propagates and the native send is still skipped
— no silent SMTP fallback. With no listener registered, behaviour is unchanged.

Before — native SMTP only

sequenceDiagram
    participant C as Caller (share flow)
    participant P as ShareByMailProvider
    participant M as IMailer
    C->>P: sendEmail / sendPassword / sendPasswordToOwner / sendNote
    P->>M: mailer->send(message)
    M-->>P: failedRecipients
Loading

After — event dispatched before send

sequenceDiagram
    participant C as Caller (share flow)
    participant P as ShareByMailProvider
    participant D as IEventDispatcher
    participant L as App listener (optional)
    participant M as IMailer
    C->>P: sendEmail / sendPassword / sendPasswordToOwner / sendNote
    P->>D: dispatchTyped(BeforeShare*MailSentEvent)
    D->>L: handle(event)
    alt listener took over delivery
        L->>L: deliver via own transport
        L->>P: markMailHandled()
        Note over P,M: native send skipped, activity not logged
    else no listener / not handled (default)
        P->>M: mailer->send(message)
        M-->>P: failedRecipients
    end
Loading

Notes

  • New public API under OCA\ShareByMail\Event\*; each concrete event carries typed getters for the
    data already computed before building the IMessage (no re-derivation in listeners).
  • createPasswordSendActivity() now runs only when the mail is actually sent (moved inside the
    isMailHandled() guard), so a suppressed password mail no longer logs a false activity entry.

TODO

  • Maintainer feedback on the event API shape before marking ready for review

Checklist

  • Code is properly formatted (php-cs-fixer clean)
  • Sign-off message is added to all commits
  • Tests (unit) are included — apps/sharebymail/tests/ShareByMailProviderTest.php
  • Screenshots before/after — n/a (no front-end changes)
  • Documentation not required (developer-facing event API)
  • Backports requested — n/a (feature)
  • Labels added — maintainer
  • Milestone added — maintainer

AI (if applicable)

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

@printminion-co printminion-co force-pushed the feat/sharebymail-before-mail-sent-event branch from 3391f42 to d928ae7 Compare July 9, 2026 15:31
@printminion-co printminion-co marked this pull request as ready for review July 9, 2026 15:31
@printminion-co printminion-co requested a review from a team as a code owner July 9, 2026 15:31
@printminion-co printminion-co requested review from Altahrim, ArtificialOwl, icewind1991 and provokateurin and removed request for a team July 9, 2026 15:31
…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.

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
…ch 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.

createPasswordSendActivity() in sendPassword() and sendPasswordToOwner() now runs
only when the mail was actually sent: it moved inside the isMailHandled() early
return, so a listener that suppresses the SMTP send no longer produces a false
password-share activity entry.

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
@printminion-co printminion-co force-pushed the feat/sharebymail-before-mail-sent-event branch from d928ae7 to edc6c9c Compare July 10, 2026 07:21
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.

1 participant