1919use OCP \Files \IRootFolder ;
2020use OCP \Files \Node ;
2121use OCP \HintException ;
22+ use OCP \IAppConfig ;
2223use OCP \IConfig ;
2324use OCP \IDBConnection ;
2425use OCP \IL10N ;
2728use OCP \IUserManager ;
2829use OCP \Mail \IEmailValidator ;
2930use OCP \Mail \IMailer ;
31+ use OCP \Mail \Provider \Address ;
32+ use OCP \Mail \Provider \IManager as IMailManager ;
33+ use OCP \Mail \Provider \IMessageSend ;
3034use OCP \Security \Events \GenerateSecurePasswordEvent ;
3135use OCP \Security \IHasher ;
3236use OCP \Security \ISecureRandom ;
@@ -74,6 +78,8 @@ public function __construct(
7478 private IEventDispatcher $ eventDispatcher ,
7579 private IShareManager $ shareManager ,
7680 private IEmailValidator $ emailValidator ,
81+ private IMailManager $ mailManager ,
82+ private IAppConfig $ appConfig ,
7783 ) {
7884 }
7985
@@ -328,6 +334,78 @@ private function trySendPasswordToOwner(IShare $share): void {
328334 'exception ' => $ e ,
329335 ]);
330336 }
337+
338+ }
339+
340+ /**
341+ * Try to find a Mail Provider service for the given user that can send mail.
342+ *
343+ * This follows the same pattern as IMipPlugin for calendar invitations:
344+ * if mail providers are enabled globally and the admin has enabled
345+ * user-level sending for share emails, look up the user's mail service.
346+ *
347+ * @param string $userId The user ID of the share initiator
348+ * @return IMessageSend|null A mail service that can send, or null to fall back to the system mailer
349+ */
350+ protected function findMailService (string $ userId ): ?IMessageSend {
351+ if (!$ this ->settingsManager ->useUserEmail ()) {
352+ return null ;
353+ }
354+
355+ if (!$ this ->appConfig ->getValueBool ('core ' , 'mail_providers_enabled ' , true )) {
356+ return null ;
357+ }
358+
359+ $ user = $ this ->userManager ->get ($ userId );
360+ if ($ user === null ) {
361+ return null ;
362+ }
363+
364+ $ userEmail = $ user ->getEMailAddress ();
365+ if ($ userEmail === null ) {
366+ return null ;
367+ }
368+
369+ $ mailService = $ this ->mailManager ->findServiceByAddress ($ userId , $ userEmail );
370+ if ($ mailService instanceof IMessageSend) {
371+ return $ mailService ;
372+ }
373+
374+ return null ;
375+ }
376+
377+ /**
378+ * Send the share notification email via Mail Provider if available,
379+ * otherwise fall back to the system mailer.
380+ *
381+ * @param IMessageSend $mailService The mail provider service
382+ * @param string $senderEmail The sender's email address
383+ * @param string $senderName The sender's display name
384+ * @param array $recipientEmails The recipient email addresses
385+ * @param \OCP\Mail\IEMailTemplate $emailTemplate The email template
386+ */
387+ protected function sendViaMailProvider (
388+ IMessageSend $ mailService ,
389+ string $ senderEmail ,
390+ string $ senderName ,
391+ array $ recipientEmails ,
392+ \OCP \Mail \IEMailTemplate $ emailTemplate ,
393+ ): void {
394+ $ message = $ mailService ->initiateMessage ();
395+ $ message ->setFrom (new Address ($ senderEmail , $ senderName ));
396+
397+ $ recipients = array_map (fn (string $ email ) => new Address ($ email ), $ recipientEmails );
398+ if (count ($ recipients ) > 1 ) {
399+ $ message ->setBcc (...$ recipients );
400+ } else {
401+ $ message ->setTo (...$ recipients );
402+ }
403+
404+ $ message ->setSubject ($ emailTemplate ->renderSubject ());
405+ $ message ->setBodyPlain ($ emailTemplate ->renderText ());
406+ $ message ->setBodyHtml ($ emailTemplate ->renderHtml ());
407+
408+ $ mailService ->sendMessage ($ message );
331409 }
332410
333411 /**
@@ -347,7 +425,6 @@ protected function sendEmail(IShare $share, array $emails): void {
347425
348426 $ initiatorUser = $ this ->userManager ->get ($ initiator );
349427 $ initiatorDisplayName = ($ initiatorUser instanceof IUser) ? $ initiatorUser ->getDisplayName () : $ initiator ;
350- $ message = $ this ->mailer ->createMessage ();
351428
352429 $ emailTemplate = $ this ->mailer ->createEMailTemplate ('sharebymail.RecipientNotification ' , [
353430 'filename ' => $ filename ,
@@ -385,6 +462,62 @@ protected function sendEmail(IShare $share, array $emails): void {
385462 $ link
386463 );
387464
465+ $ instanceName = $ this ->defaults ->getName ();
466+
467+ // Try to send via the user's Mail Provider
468+ $ mailService = $ this ->findMailService ($ initiator );
469+ if ($ mailService !== null && $ initiatorUser instanceof IUser) {
470+ $ initiatorEmail = $ initiatorUser ->getEMailAddress ();
471+ if ($ initiatorEmail !== null ) {
472+ $ emailTemplate ->addFooter ($ instanceName . ($ this ->defaults ->getSlogan () !== '' ? ' - ' . $ this ->defaults ->getSlogan () : '' ));
473+ try {
474+ $ this ->sendViaMailProvider ($ mailService , $ initiatorEmail , $ initiatorDisplayName , $ emails , $ emailTemplate );
475+ return ;
476+ } catch (\Exception $ e ) {
477+ $ this ->logger ->warning ('Failed to send share email via Mail Provider, falling back to system mailer. ' , [
478+ 'app ' => 'sharebymail ' ,
479+ 'exception ' => $ e ,
480+ ]);
481+ // Fall through to system mailer
482+ // Re-create template since footer was already added
483+ $ emailTemplate = $ this ->mailer ->createEMailTemplate ('sharebymail.RecipientNotification ' , [
484+ 'filename ' => $ filename ,
485+ 'link ' => $ link ,
486+ 'initiator ' => $ initiatorDisplayName ,
487+ 'expiration ' => $ expiration ,
488+ 'shareWith ' => $ shareWith ,
489+ 'note ' => $ note
490+ ]);
491+ $ emailTemplate ->setSubject ($ this ->l ->t ('%1$s shared %2$s with you ' , [$ initiatorDisplayName , $ filename ]));
492+ $ emailTemplate ->addHeader ();
493+ $ emailTemplate ->addHeading ($ this ->l ->t ('%1$s shared %2$s with you ' , [$ initiatorDisplayName , $ filename ]), false );
494+ if ($ note !== '' ) {
495+ $ emailTemplate ->addBodyListItem (
496+ htmlspecialchars ($ note ),
497+ $ this ->l ->t ('Note: ' ),
498+ $ this ->getAbsoluteImagePath ('caldav/description.png ' ),
499+ $ note
500+ );
501+ }
502+ if ($ expiration !== null ) {
503+ $ dateString = (string )$ this ->l ->l ('date ' , $ expiration , ['width ' => 'medium ' ]);
504+ $ emailTemplate ->addBodyListItem (
505+ $ this ->l ->t ('This share is valid until %s at midnight ' , [$ dateString ]),
506+ $ this ->l ->t ('Expiration: ' ),
507+ $ this ->getAbsoluteImagePath ('caldav/time.png ' ),
508+ );
509+ }
510+ $ emailTemplate ->addBodyButton (
511+ $ this ->l ->t ('Open shared item ' ),
512+ $ link
513+ );
514+ }
515+ }
516+ }
517+
518+ // Fall back to the system mailer
519+ $ message = $ this ->mailer ->createMessage ();
520+
388521 // If multiple recipients are given, we send the mail to all of them
389522 if (count ($ emails ) > 1 ) {
390523 // We do not want to expose the email addresses of the other recipients
@@ -394,7 +527,6 @@ protected function sendEmail(IShare $share, array $emails): void {
394527 }
395528
396529 // The "From" contains the sharers name
397- $ instanceName = $ this ->defaults ->getName ();
398530 $ senderName = $ instanceName ;
399531 if ($ this ->settingsManager ->replyToInitiator ()) {
400532 $ senderName = $ this ->l ->t (
@@ -457,8 +589,6 @@ protected function sendPassword(IShare $share, string $password, array $emails):
457589 $ plainBodyPart = $ this ->l ->t ('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it. ' , [$ initiatorDisplayName , $ filename ]);
458590 $ htmlBodyPart = $ this ->l ->t ('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it. ' , [$ initiatorDisplayName , $ filename ]);
459591
460- $ message = $ this ->mailer ->createMessage ();
461-
462592 $ emailTemplate = $ this ->mailer ->createEMailTemplate ('sharebymail.RecipientPasswordNotification ' , [
463593 'filename ' => $ filename ,
464594 'password ' => $ password ,
@@ -481,6 +611,47 @@ protected function sendPassword(IShare $share, string $password, array $emails):
481611 $ emailTemplate ->addBodyText ($ this ->l ->t ('This password will expire at %s ' , [$ expirationTime ->format ('r ' )]));
482612 }
483613
614+ $ instanceName = $ this ->defaults ->getName ();
615+
616+ // Try to send via the user's Mail Provider
617+ $ mailService = $ this ->findMailService ($ initiator );
618+ if ($ mailService !== null && $ initiatorEmailAddress !== null ) {
619+ $ emailTemplate ->addFooter ($ instanceName . ($ this ->defaults ->getSlogan () !== '' ? ' - ' . $ this ->defaults ->getSlogan () : '' ));
620+ try {
621+ $ this ->sendViaMailProvider ($ mailService , $ initiatorEmailAddress , $ initiatorDisplayName , $ emails , $ emailTemplate );
622+ $ this ->createPasswordSendActivity ($ share , $ shareWith , false );
623+ return true ;
624+ } catch (\Exception $ e ) {
625+ $ this ->logger ->warning ('Failed to send share password email via Mail Provider, falling back to system mailer. ' , [
626+ 'app ' => 'sharebymail ' ,
627+ 'exception ' => $ e ,
628+ ]);
629+ // Re-create template for fallback
630+ $ emailTemplate = $ this ->mailer ->createEMailTemplate ('sharebymail.RecipientPasswordNotification ' , [
631+ 'filename ' => $ filename ,
632+ 'password ' => $ password ,
633+ 'initiator ' => $ initiatorDisplayName ,
634+ 'initiatorEmail ' => $ initiatorEmailAddress ,
635+ 'shareWith ' => $ shareWith ,
636+ ]);
637+ $ emailTemplate ->setSubject ($ this ->l ->t ('Password to access %1$s shared to you by %2$s ' , [$ filename , $ initiatorDisplayName ]));
638+ $ emailTemplate ->addHeader ();
639+ $ emailTemplate ->addHeading ($ this ->l ->t ('Password to access %s ' , [$ filename ]), false );
640+ $ emailTemplate ->addBodyText (htmlspecialchars ($ htmlBodyPart ), $ plainBodyPart );
641+ $ emailTemplate ->addBodyText ($ this ->l ->t ('It is protected with the following password: ' ));
642+ $ emailTemplate ->addBodyText ($ password );
643+ if ($ this ->config ->getSystemValue ('sharing.enable_mail_link_password_expiration ' , false ) === true ) {
644+ $ expirationTime = new \DateTime ();
645+ $ expirationInterval = $ this ->config ->getSystemValue ('sharing.mail_link_password_expiration_interval ' , 3600 );
646+ $ expirationTime = $ expirationTime ->add (new \DateInterval ('PT ' . $ expirationInterval . 'S ' ));
647+ $ emailTemplate ->addBodyText ($ this ->l ->t ('This password will expire at %s ' , [$ expirationTime ->format ('r ' )]));
648+ }
649+ }
650+ }
651+
652+ // Fall back to the system mailer
653+ $ message = $ this ->mailer ->createMessage ();
654+
484655 // If multiple recipients are given, we send the mail to all of them
485656 if (count ($ emails ) > 1 ) {
486657 // We do not want to expose the email addresses of the other recipients
@@ -490,7 +661,6 @@ protected function sendPassword(IShare $share, string $password, array $emails):
490661 }
491662
492663 // The "From" contains the sharers name
493- $ instanceName = $ this ->defaults ->getName ();
494664 $ senderName = $ instanceName ;
495665 if ($ this ->settingsManager ->replyToInitiator ()) {
496666 $ senderName = $ this ->l ->t (
@@ -542,8 +712,6 @@ protected function sendNote(IShare $share): void {
542712 $ plainHeading = $ this ->l ->t ('%1$s shared %2$s with you and wants to add: ' , [$ initiatorDisplayName , $ filename ]);
543713 $ htmlHeading = $ this ->l ->t ('%1$s shared %2$s with you and wants to add ' , [$ initiatorDisplayName , $ filename ]);
544714
545- $ message = $ this ->mailer ->createMessage ();
546-
547715 $ emailTemplate = $ this ->mailer ->createEMailTemplate ('shareByMail.sendNote ' );
548716
549717 $ emailTemplate ->setSubject ($ this ->l ->t ('%s added a note to a file shared with you ' , [$ initiatorDisplayName ]));
@@ -558,8 +726,37 @@ protected function sendNote(IShare $share): void {
558726 $ link
559727 );
560728
561- // The "From" contains the sharers name
562729 $ instanceName = $ this ->defaults ->getName ();
730+
731+ // Try to send via the user's Mail Provider
732+ $ mailService = $ this ->findMailService ($ initiator );
733+ if ($ mailService !== null && $ initiatorEmailAddress !== null ) {
734+ $ emailTemplate ->addFooter ($ instanceName . ($ this ->defaults ->getSlogan () !== '' ? ' - ' . $ this ->defaults ->getSlogan () : '' ));
735+ try {
736+ $ this ->sendViaMailProvider ($ mailService , $ initiatorEmailAddress , $ initiatorDisplayName , [$ recipient ], $ emailTemplate );
737+ return ;
738+ } catch (\Exception $ e ) {
739+ $ this ->logger ->warning ('Failed to send share note email via Mail Provider, falling back to system mailer. ' , [
740+ 'app ' => 'sharebymail ' ,
741+ 'exception ' => $ e ,
742+ ]);
743+ // Re-create template for fallback
744+ $ emailTemplate = $ this ->mailer ->createEMailTemplate ('shareByMail.sendNote ' );
745+ $ emailTemplate ->setSubject ($ this ->l ->t ('%s added a note to a file shared with you ' , [$ initiatorDisplayName ]));
746+ $ emailTemplate ->addHeader ();
747+ $ emailTemplate ->addHeading (htmlspecialchars ($ htmlHeading ), $ plainHeading );
748+ $ emailTemplate ->addBodyText (htmlspecialchars ($ note ), $ note );
749+ $ emailTemplate ->addBodyButton (
750+ $ this ->l ->t ('Open shared item ' ),
751+ $ link
752+ );
753+ }
754+ }
755+
756+ // Fall back to the system mailer
757+ $ message = $ this ->mailer ->createMessage ();
758+
759+ // The "From" contains the sharers name
563760 $ senderName = $ instanceName ;
564761 if ($ this ->settingsManager ->replyToInitiator ()) {
565762 $ senderName = $ this ->l ->t (
0 commit comments