diff --git a/apps/comments/appinfo/app.php b/apps/comments/appinfo/app.php
index df41bdfa32d1d57115ca1c1c1c39757b987d5fc7..771b35d9c6a1b2dc5575f80e590a60e9276d7ceb 100644
--- a/apps/comments/appinfo/app.php
+++ b/apps/comments/appinfo/app.php
@@ -41,22 +41,33 @@ $activityManager = \OC::$server->getActivityManager();
 $activityManager->registerExtension(function() {
 	$application = new \OCP\AppFramework\App('comments');
 	/** @var \OCA\Comments\Activity\Extension $extension */
-	$extension = $application->getContainer()->query('OCA\Comments\Activity\Extension');
+	$extension = $application->getContainer()->query(\OCA\Comments\Activity\Extension::class);
 	return $extension;
 });
 
-$managerListener = function(\OCP\Comments\CommentsEvent $event) use ($activityManager) {
-	$application = new \OCP\AppFramework\App('comments');
-	/** @var \OCA\Comments\Activity\Listener $listener */
-	$listener = $application->getContainer()->query('OCA\Comments\Activity\Listener');
-	$listener->commentEvent($event);
-};
-
-$eventDispatcher->addListener(\OCP\Comments\CommentsEvent::EVENT_ADD, $managerListener);
-
 $eventDispatcher->addListener(\OCP\Comments\CommentsEntityEvent::EVENT_ENTITY, function(\OCP\Comments\CommentsEntityEvent $event) {
 	$event->addEntityCollection('files', function($name) {
 		$nodes = \OC::$server->getUserFolder()->getById(intval($name));
 		return !empty($nodes);
 	});
 });
+
+$notificationManager = \OC::$server->getNotificationManager();
+$notificationManager->registerNotifier(
+	function() {
+		$application = new \OCP\AppFramework\App('comments');
+		return $application->getContainer()->query(\OCA\Comments\Notification\Notifier::class);
+	},
+	function () {
+		$l = \OC::$server->getL10N('comments');
+		return ['id' => 'comments', 'name' => $l->t('Comments')];
+	}
+);
+
+$commentsManager = \OC::$server->getCommentsManager();
+$commentsManager->registerEventHandler(function () {
+	$application = new \OCP\AppFramework\App('comments');
+	/** @var \OCA\Comments\Activity\Extension $extension */
+	$handler = $application->getContainer()->query(\OCA\Comments\EventHandler::class);
+	return $handler;
+});
diff --git a/apps/comments/appinfo/routes.php b/apps/comments/appinfo/routes.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab751ddb2a2472d403dc1d5a8e72278b66d17762
--- /dev/null
+++ b/apps/comments/appinfo/routes.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+use \OCA\Comments\AppInfo\Application;
+
+$application = new Application();
+$application->registerRoutes($this, ['routes' => [
+	['name' => 'Notifications#view', 'url' => '/notifications/view/{id}', 'verb' => 'GET'],
+]]);
diff --git a/apps/comments/lib/AppInfo/Application.php b/apps/comments/lib/AppInfo/Application.php
new file mode 100644
index 0000000000000000000000000000000000000000..f168779cd0d6dbf9341a7ca3dfe6378026c8752f
--- /dev/null
+++ b/apps/comments/lib/AppInfo/Application.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Comments\AppInfo;
+
+use OCA\Comments\Controller\Notifications;
+use OCP\AppFramework\App;
+
+class Application extends App {
+
+	public function __construct (array $urlParams = array()) {
+		parent::__construct('comments', $urlParams);
+		$container = $this->getContainer();
+
+		$container->registerAlias('NotificationsController', Notifications::class);
+	}
+}
diff --git a/apps/comments/lib/Controller/Notifications.php b/apps/comments/lib/Controller/Notifications.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2a8175d17a036c1f78bfe387ef0017903c4655c
--- /dev/null
+++ b/apps/comments/lib/Controller/Notifications.php
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Comments\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
+use OCP\Files\Folder;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IUserSession;
+use OCP\Notification\IManager;
+
+/**
+ * Class Notifications
+ *
+ * @package OCA\Comments\Controller
+ */
+class Notifications extends Controller {
+	/** @var Folder  */
+	protected $folder;
+
+	/** @var ICommentsManager  */
+	protected $commentsManager;
+
+	/** @var IURLGenerator  */
+	protected $urlGenerator;
+
+	/** @var IManager  */
+	protected $notificationManager;
+
+	/** @var IUserSession  */
+	protected $userSession;
+
+	/**
+	 * Notifications constructor.
+	 *
+	 * @param string $appName
+	 * @param IRequest $request
+	 * @param ICommentsManager $commentsManager
+	 * @param Folder $folder
+	 * @param IURLGenerator $urlGenerator
+	 * @param IManager $notificationManager
+	 * @param IUserSession $userSession
+	 */
+	public function __construct(
+		$appName,
+		IRequest $request,
+		ICommentsManager $commentsManager,
+		Folder $folder,
+		IURLGenerator $urlGenerator,
+		IManager $notificationManager,
+		IUserSession $userSession
+	) {
+		parent::__construct($appName, $request);
+		$this->commentsManager = $commentsManager;
+		$this->folder = $folder;
+		$this->urlGenerator = $urlGenerator;
+		$this->notificationManager = $notificationManager;
+		$this->userSession = $userSession;
+	}
+
+	/**
+	 * @NoAdminRequired
+	 * @NoCSRFRequired
+	 *
+	 * @param string $id		the comment ID
+	 * @return Response
+	 */
+	public function view($id) {
+		try {
+			$comment = $this->commentsManager->get($id);
+			if($comment->getObjectType() !== 'files') {
+				return new NotFoundResponse();
+			}
+			$files = $this->folder->getById($comment->getObjectId());
+			if(count($files) === 0) {
+				$this->markProcessed($comment);
+				return new NotFoundResponse();
+			}
+
+			$url = $this->urlGenerator->linkToRouteAbsolute(
+				'files.viewcontroller.showFile',
+				[ 'fileid' => $comment->getObjectId() ]
+			);
+
+			$this->markProcessed($comment);
+
+			return new RedirectResponse($url);
+		} catch (\Exception $e) {
+			return new NotFoundResponse();
+		}
+	}
+
+	/**
+	 * Marks the notification about a comment as processed
+	 * @param IComment $comment
+	 */
+	protected function markProcessed(IComment $comment) {
+		$user = $this->userSession->getUser();
+		if(is_null($user)) {
+			return;
+		}
+		$notification = $this->notificationManager->createNotification();
+		$notification->setApp('comments')
+			->setObject('comment', $comment->getId())
+			->setSubject('mention')
+			->setUser($user->getUID());
+		$this->notificationManager->markProcessed($notification);
+	}
+}
diff --git a/apps/comments/lib/EventHandler.php b/apps/comments/lib/EventHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..8c20c698371267a33a7c733ab5c1f868fc6887d5
--- /dev/null
+++ b/apps/comments/lib/EventHandler.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Comments;
+
+use OCA\Comments\Activity\Listener as ActivityListener;
+use OCA\Comments\AppInfo\Application;
+use OCA\Comments\Notification\Listener as NotificationListener;
+use OCP\Comments\CommentsEvent;
+use OCP\Comments\ICommentsEventHandler;
+
+/**
+ * Class EventHandler
+ *
+ * @package OCA\Comments
+ */
+class EventHandler implements ICommentsEventHandler {
+	/** @var ActivityListener */
+	private $activityListener;
+
+	/** @var NotificationListener */
+	private $notificationListener;
+
+	public function __construct(ActivityListener $activityListener, NotificationListener $notificationListener) {
+		$this->activityListener     = $activityListener;
+		$this->notificationListener = $notificationListener;
+	}
+
+	/**
+	 * @param CommentsEvent $event
+	 */
+	public function handle(CommentsEvent $event) {
+		if($event->getComment()->getObjectType() !== 'files') {
+			// this is a 'files'-specific Handler
+			return;
+		}
+
+		$eventType = $event->getEvent();
+		if( $eventType === CommentsEvent::EVENT_ADD
+		) {
+			$this->notificationHandler($event);
+			$this->activityHandler($event);
+			return;
+		}
+
+		$applicableEvents = [
+			CommentsEvent::EVENT_PRE_UPDATE,
+			CommentsEvent::EVENT_UPDATE,
+			CommentsEvent::EVENT_DELETE,
+		];
+		if(in_array($eventType, $applicableEvents)) {
+			$this->notificationHandler($event);
+			return;
+		}
+	}
+
+	/**
+	 * @param CommentsEvent $event
+	 */
+	private function activityHandler(CommentsEvent $event) {
+		$this->activityListener->commentEvent($event);
+	}
+
+	/**
+	 * @param CommentsEvent $event
+	 */
+	private function notificationHandler(CommentsEvent $event) {
+		$this->notificationListener->evaluate($event);
+	}
+}
diff --git a/apps/comments/lib/Notification/Listener.php b/apps/comments/lib/Notification/Listener.php
new file mode 100644
index 0000000000000000000000000000000000000000..426e85cac83957521eca7820abcfe96a19f96961
--- /dev/null
+++ b/apps/comments/lib/Notification/Listener.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Comments\Notification;
+
+use OCP\Comments\CommentsEvent;
+use OCP\Comments\IComment;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
+use OCP\Notification\IManager;
+
+class Listener {
+	/** @var IManager */
+	protected $notificationManager;
+
+	/** @var IUserManager */
+	protected $userManager;
+
+	/** @var IURLGenerator */
+	protected $urlGenerator;
+
+	/**
+	 * Listener constructor.
+	 *
+	 * @param IManager $notificationManager
+	 * @param IUserManager $userManager
+	 * @param IURLGenerator $urlGenerator
+	 */
+	public function __construct(
+		IManager $notificationManager,
+		IUserManager $userManager,
+		IURLGenerator $urlGenerator
+	) {
+
+		$this->notificationManager = $notificationManager;
+		$this->userManager = $userManager;
+		$this->urlGenerator = $urlGenerator;
+	}
+
+	/**
+	 * @param CommentsEvent $event
+	 */
+	public function evaluate(CommentsEvent $event) {
+		$comment = $event->getComment();
+
+		$mentions = $this->extractMentions($comment->getMessage());
+		if(empty($mentions)) {
+			// no one to notify
+			return;
+		}
+
+		$notification = $this->instantiateNotification($comment);
+
+		foreach($mentions as $mention) {
+			$user = substr($mention, 1); // @username → username
+			if( ($comment->getActorType() === 'users' && $user === $comment->getActorId())
+				|| !$this->userManager->userExists($user)
+			) {
+				// do not notify unknown users or yourself
+				continue;
+			}
+
+			$notification->setUser($user);
+			if(    $event->getEvent() === CommentsEvent::EVENT_DELETE
+				|| $event->getEvent() === CommentsEvent::EVENT_PRE_UPDATE)
+			{
+				$this->notificationManager->markProcessed($notification);
+			} else {
+				$this->notificationManager->notify($notification);
+			}
+		}
+	}
+
+	/**
+	 * creates a notification instance and fills it with comment data
+	 *
+	 * @param IComment $comment
+	 * @return \OCP\Notification\INotification
+	 */
+	public function instantiateNotification(IComment $comment) {
+		$notification = $this->notificationManager->createNotification();
+		$notification
+			->setApp('comments')
+			->setObject('comment', $comment->getId())
+			->setSubject('mention', [ $comment->getObjectType(), $comment->getObjectId() ])
+			->setDateTime($comment->getCreationDateTime())
+			->setLink($this->urlGenerator->linkToRouteAbsolute(
+				'comments.Notifications.view',
+				['id' => $comment->getId()])
+			);
+
+		return $notification;
+	}
+
+	/**
+	 * extracts @-mentions out of a message body.
+	 *
+	 * @param string $message
+	 * @return string[] containing the mentions, e.g. ['@alice', '@bob']
+	 */
+	public function extractMentions($message) {
+		$ok = preg_match_all('/\B@[a-z0-9_\-@\.\']+/i', $message, $mentions);
+		if(!$ok || !isset($mentions[0]) || !is_array($mentions[0])) {
+			return [];
+		}
+		return array_unique($mentions[0]);
+	}
+}
diff --git a/apps/comments/lib/Notification/Notifier.php b/apps/comments/lib/Notification/Notifier.php
new file mode 100644
index 0000000000000000000000000000000000000000..3838a18f9f140710bce6a5755197e467d92984af
--- /dev/null
+++ b/apps/comments/lib/Notification/Notifier.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Comments\Notification;
+
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\NotFoundException;
+use OCP\Files\Folder;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use OCP\Notification\INotification;
+use OCP\Notification\INotifier;
+
+class Notifier implements INotifier {
+
+	/** @var IFactory */
+	protected $l10nFactory;
+
+	/** @var Folder  */
+	protected $userFolder;
+
+	/** @var ICommentsManager  */
+	protected $commentsManager;
+
+	/** @var IUserManager  */
+	protected $userManager;
+
+	public function __construct(
+		IFactory $l10nFactory,
+		Folder $userFolder,
+		ICommentsManager $commentsManager,
+		IUserManager $userManager
+	) {
+		$this->l10nFactory = $l10nFactory;
+		$this->userFolder = $userFolder;
+		$this->commentsManager = $commentsManager;
+		$this->userManager = $userManager;
+	}
+
+	/**
+	 * @param INotification $notification
+	 * @param string $languageCode The code of the language that should be used to prepare the notification
+	 * @return INotification
+	 * @throws \InvalidArgumentException When the notification was not prepared by a notifier
+	 */
+	public function prepare(INotification $notification, $languageCode) {
+		if($notification->getApp() !== 'comments') {
+			throw  new \InvalidArgumentException();
+		}
+		try {
+			$comment = $this->commentsManager->get($notification->getObjectId());
+		} catch(NotFoundException $e) {
+			// needs to be converted to InvalidArgumentException, otherwise none Notifications will be shown at all
+			throw new \InvalidArgumentException('Comment not found', 0, $e);
+		}
+		$l = $this->l10nFactory->get('comments', $languageCode);
+		$displayName = $comment->getActorId();
+		$isDeletedActor = $comment->getActorType() === ICommentsManager::DELETED_USER;
+		if($comment->getActorType() === 'users') {
+			$commenter = $this->userManager->get($comment->getActorId());
+			if(!is_null($commenter)) {
+				$displayName = $commenter->getDisplayName();
+			}
+		}
+		switch($notification->getSubject()) {
+			case 'mention':
+				$parameters = $notification->getSubjectParameters();
+				if($parameters[0] !== 'files') {
+					throw new \InvalidArgumentException('Unsupported comment object');
+				}
+				$nodes = $this->userFolder->getById($parameters[1]);
+				if(empty($nodes)) {
+					throw new \InvalidArgumentException('Cannot resolve file id to Node instance');
+				}
+				$fileName = $nodes[0]->getName();
+				if($isDeletedActor) {
+					$subject = (string) $l->t(
+						'A (now) deleted user mentioned you in a comment on "%s".',
+						[ $fileName ]
+					);
+				} else {
+					$subject = (string) $l->t(
+						'%s mentioned you in a comment on "%s".',
+						[ $displayName, $fileName ]
+					);
+				}
+				$notification->setParsedSubject($subject);
+
+				return $notification;
+				break;
+
+			default:
+				throw new \InvalidArgumentException('Invalid subject');
+		}
+
+	}
+}
diff --git a/apps/comments/tests/Unit/AppInfo/ApplicationTest.php b/apps/comments/tests/Unit/AppInfo/ApplicationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c11d7857496d683d0cdb606a93b9f5a1796538d9
--- /dev/null
+++ b/apps/comments/tests/Unit/AppInfo/ApplicationTest.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Comments\Tests\Unit\AppInfo;
+
+use OCA\Comments\AppInfo\Application;
+use Test\TestCase;
+
+/**
+ * Class ApplicationTest
+ *
+ * @group DB
+ *
+ * @package OCA\Comments\Tests\Unit\AppInfo
+ */
+class ApplicationTest extends TestCase {
+	protected function setUp() {
+		parent::setUp();
+		\OC::$server->getUserManager()->createUser('dummy', '456');
+		\OC::$server->getUserSession()->setUser(\OC::$server->getUserManager()->get('dummy'));
+	}
+
+	protected function tearDown() {
+		\OC::$server->getUserManager()->get('dummy')->delete();
+		parent::tearDown();
+	}
+
+	public function test() {
+		$app = new Application();
+		$c = $app->getContainer();
+
+		// assert service instances in the container are properly setup
+		$s = $c->query('NotificationsController');
+		$this->assertInstanceOf('OCA\Comments\Controller\Notifications', $s);
+
+		$services = [
+			'OCA\Comments\Activity\Extension',
+			'OCA\Comments\Activity\Listener',
+			'OCA\Comments\Notification\Listener'
+		];
+
+		foreach($services as $service) {
+			$s = $c->query($service);
+			$this->assertInstanceOf($service, $s);
+		}
+	}
+}
diff --git a/apps/comments/tests/Unit/Controller/NotificationsTest.php b/apps/comments/tests/Unit/Controller/NotificationsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e887900a61540143683834e6eb73d7b8fb3adb4f
--- /dev/null
+++ b/apps/comments/tests/Unit/Controller/NotificationsTest.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Comments\Tests\Unit\Controller;
+
+use OCA\Comments\Controller\Notifications;
+use OCP\Comments\NotFoundException;
+use Test\TestCase;
+
+class NotificationsTest extends TestCase {
+	/** @var  \OCA\Comments\Controller\Notifications */
+	protected $notificationsController;
+
+	/** @var  \OCP\Comments\ICommentsManager|\PHPUnit_Framework_MockObject_MockObject */
+	protected $commentsManager;
+
+	/** @var  \OCP\Files\Folder|\PHPUnit_Framework_MockObject_MockObject */
+	protected $folder;
+
+	/** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+	protected $session;
+
+	/** @var \OCP\Notification\IManager|\PHPUnit_Framework_MockObject_MockObject */
+	protected $notificationManager;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->commentsManager = $this->getMockBuilder('\OCP\Comments\ICommentsManager')->getMock();
+		$this->folder = $this->getMockBuilder('\OCP\Files\Folder')->getMock();
+		$this->session = $this->getMockBuilder('\OCP\IUserSession')->getMock();
+		$this->notificationManager = $this->getMockBuilder('\OCP\Notification\IManager')->getMock();
+
+		$this->notificationsController = new Notifications(
+			'comments',
+			$this->getMockBuilder('\OCP\IRequest')->getMock(),
+			$this->commentsManager,
+			$this->folder,
+			$this->getMockBuilder('\OCP\IURLGenerator')->getMock(),
+			$this->notificationManager,
+			$this->session
+		);
+	}
+	
+	public function testViewSuccess() {
+		$comment = $this->getMockBuilder('\OCP\Comments\IComment')->getMock();
+		$comment->expects($this->any())
+			->method('getObjectType')
+			->will($this->returnValue('files'));
+
+		$this->commentsManager->expects($this->any())
+			->method('get')
+			->with('42')
+			->will($this->returnValue($comment));
+
+		$file = $this->getMockBuilder('\OCP\Files\Node')->getMock();
+
+		$this->folder->expects($this->once())
+			->method('getById')
+			->will($this->returnValue([$file]));
+
+		$this->session->expects($this->once())
+			->method('getUser')
+			->will($this->returnValue($this->getMockBuilder('\OCP\IUser')->getMock()));
+
+		$notification = $this->getMockBuilder('\OCP\Notification\INotification')->getMock();
+		$notification->expects($this->any())
+			->method($this->anything())
+			->will($this->returnValue($notification));
+
+		$this->notificationManager->expects($this->once())
+			->method('createNotification')
+			->will($this->returnValue($notification));
+		$this->notificationManager->expects($this->once())
+			->method('markProcessed')
+			->with($notification);
+
+		$response = $this->notificationsController->view('42');
+		$this->assertInstanceOf('\OCP\AppFramework\Http\RedirectResponse', $response);
+	}
+
+	public function testViewInvalidComment() {
+		$this->commentsManager->expects($this->any())
+			->method('get')
+			->with('42')
+			->will($this->throwException(new NotFoundException()));
+
+		$this->folder->expects($this->never())
+			->method('getById');
+
+		$this->session->expects($this->never())
+			->method('getUser');
+
+		$this->notificationManager->expects($this->never())
+			->method('createNotification');
+		$this->notificationManager->expects($this->never())
+			->method('markProcessed');
+
+		$response = $this->notificationsController->view('42');
+		$this->assertInstanceOf('\OCP\AppFramework\Http\NotFoundResponse', $response);
+	}
+
+	public function testViewNoFile() {
+		$comment = $this->getMockBuilder('\OCP\Comments\IComment')->getMock();
+		$comment->expects($this->any())
+			->method('getObjectType')
+			->will($this->returnValue('files'));
+
+		$this->commentsManager->expects($this->any())
+			->method('get')
+			->with('42')
+			->will($this->returnValue($comment));
+
+		$this->folder->expects($this->once())
+			->method('getById')
+			->will($this->returnValue([]));
+
+		$this->session->expects($this->once())
+			->method('getUser')
+			->will($this->returnValue($this->getMockBuilder('\OCP\IUser')->getMock()));
+
+		$notification = $this->getMockBuilder('\OCP\Notification\INotification')->getMock();
+		$notification->expects($this->any())
+			->method($this->anything())
+			->will($this->returnValue($notification));
+
+		$this->notificationManager->expects($this->once())
+			->method('createNotification')
+			->will($this->returnValue($notification));
+		$this->notificationManager->expects($this->once())
+			->method('markProcessed')
+			->with($notification);
+
+		$response = $this->notificationsController->view('42');
+		$this->assertInstanceOf('\OCP\AppFramework\Http\NotFoundResponse', $response);
+	}
+}
diff --git a/apps/comments/tests/Unit/EventHandlerTest.php b/apps/comments/tests/Unit/EventHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb714993f7ac358d1c6467377b077a8dd3d2d0ea
--- /dev/null
+++ b/apps/comments/tests/Unit/EventHandlerTest.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Comments\Tests\Unit\Notification;
+
+use OCA\Comments\EventHandler;
+use OCP\Comments\CommentsEvent;
+use OCP\Comments\IComment;
+use OCA\Comments\Activity\Listener as ActivityListener;
+use OCA\Comments\Notification\Listener as NotificationListener;
+use Test\TestCase;
+
+class EventHandlerTest extends TestCase {
+	/** @var  EventHandler */
+	protected $eventHandler;
+
+	/** @var ActivityListener|\PHPUnit_Framework_MockObject_MockObject */
+	protected $activityListener;
+
+	/** @var NotificationListener|\PHPUnit_Framework_MockObject_MockObject */
+	protected $notificationListener;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->activityListener = $this->getMockBuilder(ActivityListener::class)
+			->disableOriginalConstructor()
+			->getMock();
+
+		$this->notificationListener = $this->getMockBuilder(NotificationListener::class)
+			->disableOriginalConstructor()
+			->getMock();
+
+		$this->eventHandler = new EventHandler($this->activityListener, $this->notificationListener);
+	}
+
+	public function testNotFiles() {
+		/** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */
+		$comment = $this->getMockBuilder(IComment::class)->getMock();
+		$comment->expects($this->once())
+			->method('getObjectType')
+			->willReturn('smiles');
+
+		/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
+		$event = $this->getMockBuilder(CommentsEvent::class)
+			->disableOriginalConstructor()
+			->getMock();
+		$event->expects($this->once())
+			->method('getComment')
+			->willReturn($comment);
+		$event->expects($this->never())
+			->method('getEvent');
+
+		$this->eventHandler->handle($event);
+	}
+
+	public function handledProvider() {
+		return [
+			[CommentsEvent::EVENT_DELETE],
+			[CommentsEvent::EVENT_UPDATE],
+			[CommentsEvent::EVENT_PRE_UPDATE],
+			[CommentsEvent::EVENT_ADD]
+		];
+	}
+
+	/**
+	 * @dataProvider handledProvider
+	 * @param string $eventType
+	 */
+	public function testHandled($eventType) {
+		/** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */
+		$comment = $this->getMockBuilder(IComment::class)->getMock();
+		$comment->expects($this->once())
+			->method('getObjectType')
+			->willReturn('files');
+
+		/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
+		$event = $this->getMockBuilder(CommentsEvent::class)
+			->disableOriginalConstructor()
+			->getMock();
+		$event->expects($this->atLeastOnce())
+			->method('getComment')
+			->willReturn($comment);
+		$event->expects($this->atLeastOnce())
+			->method('getEvent')
+			->willReturn($eventType);
+
+		$this->notificationListener->expects($this->once())
+			->method('evaluate')
+			->with($event);
+
+		$this->activityListener->expects($this->any())
+			->method('commentEvent')
+			->with($event);
+
+		$this->eventHandler->handle($event);
+	}
+
+}
diff --git a/apps/comments/tests/Unit/Notification/ListenerTest.php b/apps/comments/tests/Unit/Notification/ListenerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..12f388fcff927917486c8797d15949f8c2716a50
--- /dev/null
+++ b/apps/comments/tests/Unit/Notification/ListenerTest.php
@@ -0,0 +1,339 @@
+<?php
+/**
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Comments\Tests\Unit\Notification;
+
+use OCA\Comments\Notification\Listener;
+use OCP\Comments\CommentsEvent;
+use OCP\Comments\IComment;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
+use OCP\Notification\IManager;
+use OCP\Notification\INotification;
+use Test\TestCase;
+
+class ListenerTest extends TestCase {
+	/** @var IManager|\PHPUnit_Framework_MockObject_MockObject */
+	protected $notificationManager;
+
+	/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
+	protected $userManager;
+
+	/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
+	protected $urlGenerator;
+
+	/** @var  Listener */
+	protected $listener;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->notificationManager = $this->getMockBuilder('\OCP\Notification\IManager')->getMock();
+		$this->userManager = $this->getMockBuilder('\OCP\IUserManager')->getMock();
+		$this->urlGenerator = $this->getMockBuilder('OCP\IURLGenerator')->getMock();
+
+		$this->listener = new Listener(
+			$this->notificationManager,
+			$this->userManager,
+			$this->urlGenerator
+		);
+	}
+
+	public function eventProvider() {
+		return [
+			[CommentsEvent::EVENT_ADD, 'notify'],
+			[CommentsEvent::EVENT_UPDATE, 'notify'],
+			[CommentsEvent::EVENT_PRE_UPDATE, 'markProcessed'],
+			[CommentsEvent::EVENT_DELETE, 'markProcessed']
+		];
+	}
+
+	/**
+	 * @dataProvider eventProvider
+	 * @param string $eventType
+	 * @param string $notificationMethod
+	 */
+	public function testEvaluate($eventType, $notificationMethod) {
+		$message = '@foobar and @barfoo you should know, @foo@bar.com is valid' .
+			' and so is @bar@foo.org@foobar.io I hope that clarifies everything.' .
+			' cc @23452-4333-54353-2342 @yolo!';
+
+		/** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */
+		$comment = $this->getMockBuilder('\OCP\Comments\IComment')->getMock();
+		$comment->expects($this->any())
+			->method('getObjectType')
+			->will($this->returnValue('files'));
+		$comment->expects($this->any())
+			->method('getCreationDateTime')
+			->will($this->returnValue(new \DateTime()));
+		$comment->expects($this->once())
+			->method('getMessage')
+			->will($this->returnValue($message));
+
+		/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
+		$event = $this->getMockBuilder('\OCP\Comments\CommentsEvent')
+			->disableOriginalConstructor()
+			->getMock();
+		$event->expects($this->once())
+			->method('getComment')
+			->will($this->returnValue($comment));
+		$event->expects(($this->any()))
+			->method(('getEvent'))
+			->will($this->returnValue($eventType));
+
+		/** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */
+		$notification = $this->getMockBuilder('\OCP\Notification\INotification')->getMock();
+		$notification->expects($this->any())
+			->method($this->anything())
+			->will($this->returnValue($notification));
+		$notification->expects($this->exactly(6))
+			->method('setUser');
+
+		$this->notificationManager->expects($this->once())
+			->method('createNotification')
+			->will($this->returnValue($notification));
+		$this->notificationManager->expects($this->exactly(6))
+			->method($notificationMethod)
+			->with($this->isInstanceOf('\OCP\Notification\INotification'));
+
+		$this->userManager->expects($this->exactly(6))
+			->method('userExists')
+			->withConsecutive(
+				['foobar'],
+				['barfoo'],
+				['foo@bar.com'],
+				['bar@foo.org@foobar.io'],
+				['23452-4333-54353-2342'],
+				['yolo']
+			)
+			->will($this->returnValue(true));
+
+		$this->listener->evaluate($event);
+	}
+
+	/**
+	 * @dataProvider eventProvider
+	 * @param string $eventType
+	 */
+	public function testEvaluateNoMentions($eventType) {
+		$message = 'a boring comment without mentions';
+
+		/** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */
+		$comment = $this->getMockBuilder('\OCP\Comments\IComment')->getMock();
+		$comment->expects($this->any())
+			->method('getObjectType')
+			->will($this->returnValue('files'));
+		$comment->expects($this->any())
+			->method('getCreationDateTime')
+			->will($this->returnValue(new \DateTime()));
+		$comment->expects($this->once())
+			->method('getMessage')
+			->will($this->returnValue($message));
+
+		/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
+		$event = $this->getMockBuilder('\OCP\Comments\CommentsEvent')
+			->disableOriginalConstructor()
+			->getMock();
+		$event->expects($this->once())
+			->method('getComment')
+			->will($this->returnValue($comment));
+		$event->expects(($this->any()))
+			->method(('getEvent'))
+			->will($this->returnValue($eventType));
+
+		$this->notificationManager->expects($this->never())
+			->method('createNotification');
+		$this->notificationManager->expects($this->never())
+			->method('notify');
+		$this->notificationManager->expects($this->never())
+			->method('markProcessed');
+
+		$this->userManager->expects($this->never())
+			->method('userExists');
+
+		$this->listener->evaluate($event);
+	}
+
+	public function testEvaluateUserDoesNotExist() {
+		$message = '@foobar bla bla bla';
+
+		/** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */
+		$comment = $this->getMockBuilder('\OCP\Comments\IComment')->getMock();
+		$comment->expects($this->any())
+			->method('getObjectType')
+			->will($this->returnValue('files'));
+		$comment->expects($this->any())
+			->method('getCreationDateTime')
+			->will($this->returnValue(new \DateTime()));
+		$comment->expects($this->once())
+			->method('getMessage')
+			->will($this->returnValue($message));
+
+		/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
+		$event = $this->getMockBuilder('\OCP\Comments\CommentsEvent')
+			->disableOriginalConstructor()
+			->getMock();
+		$event->expects($this->once())
+			->method('getComment')
+			->will($this->returnValue($comment));
+		$event->expects(($this->any()))
+			->method(('getEvent'))
+			->will($this->returnValue(CommentsEvent::EVENT_ADD));
+
+		/** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */
+		$notification = $this->getMockBuilder('\OCP\Notification\INotification')->getMock();
+		$notification->expects($this->any())
+			->method($this->anything())
+			->will($this->returnValue($notification));
+		$notification->expects($this->never())
+			->method('setUser');
+
+		$this->notificationManager->expects($this->once())
+			->method('createNotification')
+			->will($this->returnValue($notification));
+		$this->notificationManager->expects($this->never())
+			->method('notify');
+
+		$this->userManager->expects($this->once())
+			->method('userExists')
+			->withConsecutive(
+				['foobar']
+			)
+			->will($this->returnValue(false));
+
+		$this->listener->evaluate($event);
+	}
+
+	/**
+	 * @dataProvider eventProvider
+	 * @param string $eventType
+	 * @param string $notificationMethod
+	 */
+	public function testEvaluateOneMentionPerUser($eventType, $notificationMethod) {
+		$message = '@foobar bla bla bla @foobar';
+
+		/** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */
+		$comment = $this->getMockBuilder('\OCP\Comments\IComment')->getMock();
+		$comment->expects($this->any())
+			->method('getObjectType')
+			->will($this->returnValue('files'));
+		$comment->expects($this->any())
+			->method('getCreationDateTime')
+			->will($this->returnValue(new \DateTime()));
+		$comment->expects($this->once())
+			->method('getMessage')
+			->will($this->returnValue($message));
+
+		/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
+		$event = $this->getMockBuilder('\OCP\Comments\CommentsEvent')
+			->disableOriginalConstructor()
+			->getMock();
+		$event->expects($this->once())
+			->method('getComment')
+			->will($this->returnValue($comment));
+		$event->expects(($this->any()))
+			->method(('getEvent'))
+			->will($this->returnValue($eventType));
+
+		/** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */
+		$notification = $this->getMockBuilder('\OCP\Notification\INotification')->getMock();
+		$notification->expects($this->any())
+			->method($this->anything())
+			->will($this->returnValue($notification));
+		$notification->expects($this->once())
+			->method('setUser');
+
+		$this->notificationManager->expects($this->once())
+			->method('createNotification')
+			->will($this->returnValue($notification));
+		$this->notificationManager->expects($this->once())
+			->method($notificationMethod)
+			->with($this->isInstanceOf('\OCP\Notification\INotification'));
+
+		$this->userManager->expects($this->once())
+			->method('userExists')
+			->withConsecutive(
+				['foobar']
+			)
+			->will($this->returnValue(true));
+
+		$this->listener->evaluate($event);
+	}
+
+	/**
+	 * @dataProvider eventProvider
+	 * @param string $eventType
+	 */
+	public function testEvaluateNoSelfMention($eventType) {
+		$message = '@foobar bla bla bla';
+
+		/** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */
+		$comment = $this->getMockBuilder('\OCP\Comments\IComment')->getMock();
+		$comment->expects($this->any())
+			->method('getObjectType')
+			->will($this->returnValue('files'));
+		$comment->expects($this->any())
+			->method('getActorType')
+			->will($this->returnValue('users'));
+		$comment->expects($this->any())
+			->method('getActorId')
+			->will($this->returnValue('foobar'));
+		$comment->expects($this->any())
+			->method('getCreationDateTime')
+			->will($this->returnValue(new \DateTime()));
+		$comment->expects($this->once())
+			->method('getMessage')
+			->will($this->returnValue($message));
+
+		/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
+		$event = $this->getMockBuilder('\OCP\Comments\CommentsEvent')
+			->disableOriginalConstructor()
+			->getMock();
+		$event->expects($this->once())
+			->method('getComment')
+			->will($this->returnValue($comment));
+		$event->expects(($this->any()))
+			->method(('getEvent'))
+			->will($this->returnValue($eventType));
+
+		/** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */
+		$notification = $this->getMockBuilder('\OCP\Notification\INotification')->getMock();
+		$notification->expects($this->any())
+			->method($this->anything())
+			->will($this->returnValue($notification));
+		$notification->expects($this->never())
+			->method('setUser');
+
+		$this->notificationManager->expects($this->once())
+			->method('createNotification')
+			->will($this->returnValue($notification));
+		$this->notificationManager->expects($this->never())
+			->method('notify');
+		$this->notificationManager->expects($this->never())
+			->method('markProcessed');
+
+		$this->userManager->expects($this->never())
+			->method('userExists');
+
+		$this->listener->evaluate($event);
+	}
+
+}
diff --git a/apps/comments/tests/Unit/Notification/NotifierTest.php b/apps/comments/tests/Unit/Notification/NotifierTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b297611308da392544aa76e80bdd5416f2b7402f
--- /dev/null
+++ b/apps/comments/tests/Unit/Notification/NotifierTest.php
@@ -0,0 +1,498 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Comments\Tests\Unit\Notification;
+
+use OCA\Comments\Notification\Notifier;
+use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\NotFoundException;
+use OCP\Files\Folder;
+use OCP\Files\Node;
+use OCP\IL10N;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use OCP\Notification\INotification;
+use Test\TestCase;
+
+class NotifierTest extends TestCase {
+
+	/** @var Notifier */
+	protected $notifier;
+
+	/** @var IFactory|\PHPUnit_Framework_MockObject_MockObject */
+	protected $l10nFactory;
+
+	/** @var  Folder|\PHPUnit_Framework_MockObject_MockObject */
+	protected $folder;
+
+	/** @var  ICommentsManager|\PHPUnit_Framework_MockObject_MockObject */
+	protected $commentsManager;
+
+	/** @var  IUserManager|\PHPUnit_Framework_MockObject_MockObject */
+	protected $userManager;
+
+
+	/** @var string */
+	protected $lc = 'tlh_KX';
+
+	/** @var  INotification|\PHPUnit_Framework_MockObject_MockObject */
+	protected $notification;
+
+	/** @var  IL10N|\PHPUnit_Framework_MockObject_MockObject */
+	protected $l;
+
+	/** @var  IComment|\PHPUnit_Framework_MockObject_MockObject */
+	protected $comment;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->l10nFactory = $this->getMockBuilder('OCP\L10N\IFactory')->getMock();
+		$this->folder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
+		$this->commentsManager = $this->getMockBuilder('OCP\Comments\ICommentsManager')->getMock();
+		$this->userManager = $this->getMockBuilder('OCP\IUserManager')->getMock();
+
+		$this->notifier = new Notifier(
+			$this->l10nFactory,
+			$this->folder,
+			$this->commentsManager,
+			$this->userManager
+		);
+
+		$this->l = $this->getMockBuilder('OCP\IL10N')->getMock();
+		$this->notification = $this->getMockBuilder('OCP\Notification\INotification')->getMock();
+		$this->comment = $this->getMockBuilder('OCP\Comments\IComment')->getMock();
+	}
+
+	public function testPrepareSuccess() {
+		$fileName = 'Gre\'thor.odp';
+		$displayName = 'Huraga';
+		$message = 'Hurage mentioned you in a comment on "Gre\'thor.odp".';
+
+		/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
+		$user = $this->getMockBuilder('OCP\IUser')->getMock();
+		$user->expects($this->once())
+			->method('getDisplayName')
+			->willReturn($displayName);
+
+		/** @var Node|\PHPUnit_Framework_MockObject_MockObject */
+		$node = $this->getMockBuilder('OCP\Files\Node')->getMock();
+		$node
+			->expects($this->once())
+			->method('getName')
+			->willReturn($fileName);
+
+		$this->folder
+			->expects($this->once())
+			->method('getById')
+			->with('678')
+			->willReturn([$node]);
+
+		$this->notification
+			->expects($this->once())
+			->method('getApp')
+			->willReturn('comments');
+		$this->notification
+			->expects($this->once())
+			->method('getSubject')
+			->willReturn('mention');
+		$this->notification
+			->expects($this->once())
+			->method('getSubjectParameters')
+			->willReturn(['files', '678']);
+		$this->notification
+			->expects($this->once())
+			->method('setParsedSubject')
+			->with($message);
+
+		$this->l
+			->expects($this->once())
+			->method('t')
+			->with('%s mentioned you in a comment on "%s".', [$displayName, $fileName])
+			->willReturn($message);
+
+		$this->l10nFactory
+			->expects($this->once())
+			->method('get')
+			->willReturn($this->l);
+
+		$this->comment
+			->expects($this->any())
+			->method('getActorId')
+			->willReturn('huraga');
+		$this->comment
+			->expects($this->any())
+			->method('getActorType')
+			->willReturn('users');
+
+		$this->commentsManager
+			->expects(($this->once()))
+			->method('get')
+			->willReturn($this->comment);
+
+		$this->userManager
+			->expects($this->once())
+			->method('get')
+			->with('huraga')
+			->willReturn($user);
+
+		$this->notifier->prepare($this->notification, $this->lc);
+	}
+
+	public function testPrepareSuccessDeletedUser() {
+		$fileName = 'Gre\'thor.odp';
+		$message = 'A (now) deleted user mentioned you in a comment on "Gre\'thor.odp".';
+
+		/** @var Node|\PHPUnit_Framework_MockObject_MockObject */
+		$node = $this->getMockBuilder('OCP\Files\Node')->getMock();
+		$node
+			->expects($this->once())
+			->method('getName')
+			->willReturn($fileName);
+
+		$this->folder
+			->expects($this->once())
+			->method('getById')
+			->with('678')
+			->willReturn([$node]);
+
+		$this->notification
+			->expects($this->once())
+			->method('getApp')
+			->willReturn('comments');
+		$this->notification
+			->expects($this->once())
+			->method('getSubject')
+			->willReturn('mention');
+		$this->notification
+			->expects($this->once())
+			->method('getSubjectParameters')
+			->willReturn(['files', '678']);
+		$this->notification
+			->expects($this->once())
+			->method('setParsedSubject')
+			->with($message);
+
+		$this->l
+			->expects($this->once())
+			->method('t')
+			->with('A (now) deleted user mentioned you in a comment on "%s".', [ $fileName ])
+			->willReturn($message);
+
+		$this->l10nFactory
+			->expects($this->once())
+			->method('get')
+			->willReturn($this->l);
+
+		$this->comment
+			->expects($this->any())
+			->method('getActorId')
+			->willReturn('huraga');
+		$this->comment
+			->expects($this->any())
+			->method('getActorType')
+			->willReturn(ICommentsManager::DELETED_USER);
+
+		$this->commentsManager
+			->expects(($this->once()))
+			->method('get')
+			->willReturn($this->comment);
+
+		$this->userManager
+			->expects($this->never())
+			->method('get');
+
+		$this->notifier->prepare($this->notification, $this->lc);
+	}
+
+	/**
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testPrepareDifferentApp() {
+		$this->folder
+			->expects($this->never())
+			->method('getById');
+
+		$this->notification
+			->expects($this->once())
+			->method('getApp')
+			->willReturn('constructions');
+		$this->notification
+			->expects($this->never())
+			->method('getSubject');
+		$this->notification
+			->expects($this->never())
+			->method('getSubjectParameters');
+		$this->notification
+			->expects($this->never())
+			->method('setParsedSubject');
+
+		$this->l10nFactory
+			->expects($this->never())
+			->method('get');
+
+		$this->commentsManager
+			->expects(($this->never()))
+			->method('get');
+
+		$this->userManager
+			->expects($this->never())
+			->method('get');
+
+		$this->notifier->prepare($this->notification, $this->lc);
+	}
+
+	/**
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testPrepareNotFound() {
+		$this->folder
+			->expects($this->never())
+			->method('getById');
+
+		$this->notification
+			->expects($this->once())
+			->method('getApp')
+			->willReturn('comments');
+		$this->notification
+			->expects($this->never())
+			->method('getSubject');
+		$this->notification
+			->expects($this->never())
+			->method('getSubjectParameters');
+		$this->notification
+			->expects($this->never())
+			->method('setParsedSubject');
+
+		$this->l10nFactory
+			->expects($this->never())
+			->method('get');
+
+		$this->commentsManager
+			->expects(($this->once()))
+			->method('get')
+			->willThrowException(new NotFoundException());
+
+		$this->userManager
+			->expects($this->never())
+			->method('get');
+
+		$this->notifier->prepare($this->notification, $this->lc);
+	}
+
+	/**
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testPrepareDifferentSubject() {
+		$displayName = 'Huraga';
+
+		/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
+		$user = $this->getMockBuilder('OCP\IUser')->getMock();
+		$user->expects($this->once())
+			->method('getDisplayName')
+			->willReturn($displayName);
+
+		$this->folder
+			->expects($this->never())
+			->method('getById');
+
+		$this->notification
+			->expects($this->once())
+			->method('getApp')
+			->willReturn('comments');
+		$this->notification
+			->expects($this->once())
+			->method('getSubject')
+			->willReturn('unlike');
+		$this->notification
+			->expects($this->never())
+			->method('getSubjectParameters');
+		$this->notification
+			->expects($this->never())
+			->method('setParsedSubject');
+
+		$this->l
+			->expects($this->never())
+			->method('t');
+
+		$this->l10nFactory
+			->expects($this->once())
+			->method('get')
+			->willReturn($this->l);
+
+		$this->comment
+			->expects($this->any())
+			->method('getActorId')
+			->willReturn('huraga');
+		$this->comment
+			->expects($this->any())
+			->method('getActorType')
+			->willReturn('users');
+
+		$this->commentsManager
+			->expects(($this->once()))
+			->method('get')
+			->willReturn($this->comment);
+
+		$this->userManager
+			->expects($this->once())
+			->method('get')
+			->with('huraga')
+			->willReturn($user);
+
+		$this->notifier->prepare($this->notification, $this->lc);
+	}
+
+	/**
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testPrepareNotFiles() {
+		$displayName = 'Huraga';
+
+		/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
+		$user = $this->getMockBuilder('OCP\IUser')->getMock();
+		$user->expects($this->once())
+			->method('getDisplayName')
+			->willReturn($displayName);
+
+		$this->folder
+			->expects($this->never())
+			->method('getById');
+
+		$this->notification
+			->expects($this->once())
+			->method('getApp')
+			->willReturn('comments');
+		$this->notification
+			->expects($this->once())
+			->method('getSubject')
+			->willReturn('mention');
+		$this->notification
+			->expects($this->once())
+			->method('getSubjectParameters')
+			->willReturn(['ships', '678']);
+		$this->notification
+			->expects($this->never())
+			->method('setParsedSubject');
+
+		$this->l
+			->expects($this->never())
+			->method('t');
+
+		$this->l10nFactory
+			->expects($this->once())
+			->method('get')
+			->willReturn($this->l);
+
+		$this->comment
+			->expects($this->any())
+			->method('getActorId')
+			->willReturn('huraga');
+		$this->comment
+			->expects($this->any())
+			->method('getActorType')
+			->willReturn('users');
+
+		$this->commentsManager
+			->expects(($this->once()))
+			->method('get')
+			->willReturn($this->comment);
+
+		$this->userManager
+			->expects($this->once())
+			->method('get')
+			->with('huraga')
+			->willReturn($user);
+
+		$this->notifier->prepare($this->notification, $this->lc);
+	}
+
+	/**
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testPrepareUnresolvableFileID() {
+		$displayName = 'Huraga';
+
+		/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
+		$user = $this->getMockBuilder('OCP\IUser')->getMock();
+		$user->expects($this->once())
+			->method('getDisplayName')
+			->willReturn($displayName);
+
+		$this->folder
+			->expects($this->once())
+			->method('getById')
+			->with('678')
+			->willReturn([]);
+
+		$this->notification
+			->expects($this->once())
+			->method('getApp')
+			->willReturn('comments');
+		$this->notification
+			->expects($this->once())
+			->method('getSubject')
+			->willReturn('mention');
+		$this->notification
+			->expects($this->once())
+			->method('getSubjectParameters')
+			->willReturn(['files', '678']);
+		$this->notification
+			->expects($this->never())
+			->method('setParsedSubject');
+
+		$this->l
+			->expects($this->never())
+			->method('t');
+
+		$this->l10nFactory
+			->expects($this->once())
+			->method('get')
+			->willReturn($this->l);
+
+		$this->comment
+			->expects($this->any())
+			->method('getActorId')
+			->willReturn('huraga');
+		$this->comment
+			->expects($this->any())
+			->method('getActorType')
+			->willReturn('users');
+
+		$this->commentsManager
+			->expects(($this->once()))
+			->method('get')
+			->willReturn($this->comment);
+
+		$this->userManager
+			->expects($this->once())
+			->method('get')
+			->with('huraga')
+			->willReturn($user);
+
+		$this->notifier->prepare($this->notification, $this->lc);
+	}
+
+}
diff --git a/apps/dav/lib/Comments/CommentNode.php b/apps/dav/lib/Comments/CommentNode.php
index 101e3d8772175d76b0ec7ab1c51671e2de569dd9..f247921be792f8e0f0ef237dea11eb14cb257dfc 100644
--- a/apps/dav/lib/Comments/CommentNode.php
+++ b/apps/dav/lib/Comments/CommentNode.php
@@ -173,7 +173,7 @@ class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties {
 	 * @param $propertyValue
 	 * @return bool
 	 * @throws BadRequest
-	 * @throws Forbidden
+	 * @throws \Exception
 	 */
 	public function updateComment($propertyValue) {
 		$this->checkWriteAccessOnComment();
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 0ff46de07de0fab91a05edd499245c5b1e90d518..5cedef83c615c385c61639139eff9df3efd258f6 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -61,6 +61,7 @@ return array(
     'OCP\\Comments\\CommentsEntityEvent' => $baseDir . '/lib/public/Comments/CommentsEntityEvent.php',
     'OCP\\Comments\\CommentsEvent' => $baseDir . '/lib/public/Comments/CommentsEvent.php',
     'OCP\\Comments\\IComment' => $baseDir . '/lib/public/Comments/IComment.php',
+    'OCP\\Comments\\ICommentsEventHandler' => $baseDir . '/lib/public/Comments/ICommentsEventHandler.php',
     'OCP\\Comments\\ICommentsManager' => $baseDir . '/lib/public/Comments/ICommentsManager.php',
     'OCP\\Comments\\ICommentsManagerFactory' => $baseDir . '/lib/public/Comments/ICommentsManagerFactory.php',
     'OCP\\Comments\\IllegalIDChangeException' => $baseDir . '/lib/public/Comments/IllegalIDChangeException.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 24058a22edb3aed820963fdbe2efa49de26a9e70..bce3916c8a0f984c079567b720733ed17ebc36af 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -91,6 +91,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\Comments\\CommentsEntityEvent' => __DIR__ . '/../../..' . '/lib/public/Comments/CommentsEntityEvent.php',
         'OCP\\Comments\\CommentsEvent' => __DIR__ . '/../../..' . '/lib/public/Comments/CommentsEvent.php',
         'OCP\\Comments\\IComment' => __DIR__ . '/../../..' . '/lib/public/Comments/IComment.php',
+        'OCP\\Comments\\ICommentsEventHandler' => __DIR__ . '/../../..' . '/lib/public/Comments/ICommentsEventHandler.php',
         'OCP\\Comments\\ICommentsManager' => __DIR__ . '/../../..' . '/lib/public/Comments/ICommentsManager.php',
         'OCP\\Comments\\ICommentsManagerFactory' => __DIR__ . '/../../..' . '/lib/public/Comments/ICommentsManagerFactory.php',
         'OCP\\Comments\\IllegalIDChangeException' => __DIR__ . '/../../..' . '/lib/public/Comments/IllegalIDChangeException.php',
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 5fc200a1bce1462f36edb175f823b51263ff3f83..21d5eaa9503d436fcdc3766e245a4b7413325e0f 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -161,6 +161,10 @@ class DIContainer extends SimpleContainer implements IAppContainer {
 			return $this->getServer()->getRootFolder();
 		});
 
+		$this->registerService('OCP\\Files\\Folder', function() {
+			return $this->getServer()->getUserFolder();
+		});
+
 		$this->registerService('OCP\\Http\\Client\\IClientService', function($c) {
 			return $this->getServer()->getHTTPClientService();
 		});
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php
index 59678775d88d00627fa55f9384c1eda3523840ab..b3ecab731e1825d93441cd87bc4721d5715e107a 100644
--- a/lib/private/Comments/Manager.php
+++ b/lib/private/Comments/Manager.php
@@ -26,6 +26,7 @@ namespace OC\Comments;
 use Doctrine\DBAL\Exception\DriverException;
 use OCP\Comments\CommentsEvent;
 use OCP\Comments\IComment;
+use OCP\Comments\ICommentsEventHandler;
 use OCP\Comments\ICommentsManager;
 use OCP\Comments\NotFoundException;
 use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -33,7 +34,6 @@ use OCP\IDBConnection;
 use OCP\IConfig;
 use OCP\ILogger;
 use OCP\IUser;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 class Manager implements ICommentsManager {
 
@@ -46,30 +46,30 @@ class Manager implements ICommentsManager {
 	/** @var IConfig */
 	protected $config;
 
-	/** @var EventDispatcherInterface */
-	protected $dispatcher;
-
 	/** @var IComment[]  */
 	protected $commentsCache = [];
 
+	/** @var  \Closure[] */
+	protected $eventHandlerClosures = [];
+
+	/** @var  ICommentsEventHandler[] */
+	protected $eventHandlers = [];
+
 	/**
 	 * Manager constructor.
 	 *
 	 * @param IDBConnection $dbConn
 	 * @param ILogger $logger
 	 * @param IConfig $config
-	 * @param EventDispatcherInterface $dispatcher
 	 */
 	public function __construct(
 		IDBConnection $dbConn,
 		ILogger $logger,
-		IConfig $config,
-		EventDispatcherInterface $dispatcher
+		IConfig $config
 	) {
 		$this->dbConn = $dbConn;
 		$this->logger = $logger;
 		$this->config = $config;
-		$this->dispatcher = $dispatcher;
 	}
 
 	/**
@@ -455,10 +455,7 @@ class Manager implements ICommentsManager {
 		}
 
 		if ($affectedRows > 0 && $comment instanceof IComment) {
-			$this->dispatcher->dispatch(CommentsEvent::EVENT_DELETE, new CommentsEvent(
-				CommentsEvent::EVENT_DELETE,
-				$comment
-			));
+			$this->sendEvent(CommentsEvent::EVENT_DELETE, $comment);
 		}
 
 		return ($affectedRows > 0);
@@ -525,13 +522,9 @@ class Manager implements ICommentsManager {
 
 		if ($affectedRows > 0) {
 			$comment->setId(strval($qb->getLastInsertId()));
+			$this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
 		}
 
-		$this->dispatcher->dispatch(CommentsEvent::EVENT_ADD, new CommentsEvent(
-			CommentsEvent::EVENT_ADD,
-			$comment
-		));
-
 		return $affectedRows > 0;
 	}
 
@@ -543,6 +536,12 @@ class Manager implements ICommentsManager {
 	 * @throws NotFoundException
 	 */
 	protected function update(IComment $comment) {
+		// for properly working preUpdate Events we need the old comments as is
+		// in the DB and overcome caching. Also avoid that outdated information stays.
+		$this->uncache($comment->getId());
+		$this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
+		$this->uncache($comment->getId());
+
 		$qb = $this->dbConn->getQueryBuilder();
 		$affectedRows = $qb
 			->update('comments')
@@ -565,10 +564,7 @@ class Manager implements ICommentsManager {
 			throw new NotFoundException('Comment to update does ceased to exist');
 		}
 
-		$this->dispatcher->dispatch(CommentsEvent::EVENT_UPDATE, new CommentsEvent(
-			CommentsEvent::EVENT_UPDATE,
-			$comment
-		));
+		$this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment);
 
 		return $affectedRows > 0;
 	}
@@ -751,4 +747,51 @@ class Manager implements ICommentsManager {
 		}
 		return ($affectedRows > 0);
 	}
+
+	/**
+	 * registers an Entity to the manager, so event notifications can be send
+	 * to consumers of the comments infrastructure
+	 *
+	 * @param \Closure $closure
+	 */
+	public function registerEventHandler(\Closure $closure) {
+		$this->eventHandlerClosures[] = $closure;
+		$this->eventHandlers = [];
+	}
+
+	/**
+	 * returns valid, registered entities
+	 *
+	 * @return \OCP\Comments\ICommentsEventHandler[]
+	 */
+	private function getEventHandlers() {
+		if(!empty($this->eventHandlers)) {
+			return $this->eventHandlers;
+		}
+
+		$this->eventHandlers = [];
+		foreach ($this->eventHandlerClosures as $name => $closure) {
+			$entity = $closure();
+			if (!($entity instanceof ICommentsEventHandler)) {
+				throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface');
+			}
+			$this->eventHandlers[$name] = $entity;
+		}
+
+		return $this->eventHandlers;
+	}
+
+	/**
+	 * sends notifications to the registered entities
+	 *
+	 * @param $eventType
+	 * @param IComment $comment
+	 */
+	private function sendEvent($eventType, IComment $comment) {
+		$entities = $this->getEventHandlers();
+		$event = new CommentsEvent($eventType, $comment);
+		foreach ($entities as $entity) {
+			$entity->handle($event);
+		}
+	}
 }
diff --git a/lib/public/Comments/CommentsEvent.php b/lib/public/Comments/CommentsEvent.php
index a0bff349fb4edd1e02c1d85228766e010105e16a..0d8a783c107fd46d27eb53ab637f2e962aeadc04 100644
--- a/lib/public/Comments/CommentsEvent.php
+++ b/lib/public/Comments/CommentsEvent.php
@@ -32,9 +32,10 @@ use Symfony\Component\EventDispatcher\Event;
  */
 class CommentsEvent extends Event {
 
-	const EVENT_ADD = 'OCP\Comments\ICommentsManager::addComment';
-	const EVENT_UPDATE = 'OCP\Comments\ICommentsManager::updateComment';
-	const EVENT_DELETE = 'OCP\Comments\ICommentsManager::deleteComment';
+	const EVENT_ADD        = 'OCP\Comments\ICommentsManager::addComment';
+	const EVENT_PRE_UPDATE = 'OCP\Comments\ICommentsManager::preUpdateComment';
+	const EVENT_UPDATE     = 'OCP\Comments\ICommentsManager::updateComment';
+	const EVENT_DELETE     = 'OCP\Comments\ICommentsManager::deleteComment';
 
 	/** @var string */
 	protected $event;
diff --git a/lib/public/Comments/ICommentsEventHandler.php b/lib/public/Comments/ICommentsEventHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..335241990122b32473763ea47a0cbb8d0fec99f3
--- /dev/null
+++ b/lib/public/Comments/ICommentsEventHandler.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Comments;
+
+/**
+ * Interface ICommentsEventHandler
+ *
+ * @package OCP\Comments
+ * @since 9.2.0
+ */
+interface ICommentsEventHandler {
+
+	/**
+	 * @param CommentsEvent $event
+	 * @since 9.2.0
+	 */
+	public function handle(CommentsEvent $event);
+}
diff --git a/lib/public/Comments/ICommentsManager.php b/lib/public/Comments/ICommentsManager.php
index f2edaaa5eaf77c1130130bff98ed96bf9267b91b..98169fb335f2e4c3cd1cd7158d7039b4b8fd09e7 100644
--- a/lib/public/Comments/ICommentsManager.php
+++ b/lib/public/Comments/ICommentsManager.php
@@ -237,4 +237,13 @@ interface ICommentsManager {
 	 */
 	public function deleteReadMarksOnObject($objectType, $objectId);
 
+	/**
+	 * registers an Entity to the manager, so event notifications can be send
+	 * to consumers of the comments infrastructure
+	 *
+	 * @param \Closure $closure
+	 * @since 9.2.0
+	 */
+	public function registerEventHandler(\Closure $closure);
+
 }
diff --git a/tests/lib/Comments/FakeManager.php b/tests/lib/Comments/FakeManager.php
index 7186529e7181d25dc2cb4abb00d460c7520347fa..7cd146e7cb25b37dc3097c8309d26196b154fc46 100644
--- a/tests/lib/Comments/FakeManager.php
+++ b/tests/lib/Comments/FakeManager.php
@@ -38,4 +38,6 @@ class FakeManager implements \OCP\Comments\ICommentsManager {
 	public function deleteReadMarksFromUser(\OCP\IUser $user) {}
 
 	public function deleteReadMarksOnObject($objectType, $objectId) {}
+
+	public function registerEventHandler(\Closure $closure) {}
 }
diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php
index 730d82d9d0db41d0d62f1d391f2e2686bada83ac..71c918af6c7deb85ae4b1455990dd12f052ed0f1 100644
--- a/tests/lib/Comments/ManagerTest.php
+++ b/tests/lib/Comments/ManagerTest.php
@@ -2,6 +2,9 @@
 
 namespace Test\Comments;
 
+use OC\Comments\Comment;
+use OCP\Comments\CommentsEvent;
+use OCP\Comments\ICommentsEventHandler;
 use OCP\Comments\ICommentsManager;
 use OCP\IUser;
 use Test\TestCase;
@@ -355,7 +358,7 @@ class ManagerTest extends TestCase {
 
 	public function testSaveNew() {
 		$manager = $this->getManager();
-		$comment = new \OC\Comments\Comment();
+		$comment = new Comment();
 		$comment
 			->setActor('users', 'alice')
 			->setObject('files', 'file64')
@@ -375,7 +378,7 @@ class ManagerTest extends TestCase {
 
 	public function testSaveUpdate() {
 		$manager = $this->getManager();
-		$comment = new \OC\Comments\Comment();
+		$comment = new Comment();
 		$comment
 				->setActor('users', 'alice')
 				->setObject('files', 'file64')
@@ -396,7 +399,7 @@ class ManagerTest extends TestCase {
 	 */
 	public function testSaveUpdateException() {
 		$manager = $this->getManager();
-		$comment = new \OC\Comments\Comment();
+		$comment = new Comment();
 		$comment
 				->setActor('users', 'alice')
 				->setObject('files', 'file64')
@@ -415,7 +418,7 @@ class ManagerTest extends TestCase {
 	 */
 	public function testSaveIncomplete() {
 		$manager = $this->getManager();
-		$comment = new \OC\Comments\Comment();
+		$comment = new Comment();
 		$comment->setMessage('from no one to nothing');
 		$manager->save($comment);
 	}
@@ -426,7 +429,7 @@ class ManagerTest extends TestCase {
 		$manager = $this->getManager();
 
 		for($i = 0; $i < 3; $i++) {
-			$comment = new \OC\Comments\Comment();
+			$comment = new Comment();
 			$comment
 					->setActor('users', 'alice')
 					->setObject('files', 'file64')
@@ -630,4 +633,35 @@ class ManagerTest extends TestCase {
 		$this->assertNull($dateTimeGet);
 	}
 
+	public function testSendEvent() {
+		$handler1 = $this->getMockBuilder(ICommentsEventHandler::class)->getMock();
+		$handler1->expects($this->exactly(4))
+			->method('handle');
+
+		$handler2 = $this->getMockBuilder(ICommentsEventHandler::class)->getMock();
+		$handler1->expects($this->exactly(4))
+			->method('handle');
+
+		$manager = $this->getManager();
+		$manager->registerEventHandler(function () use ($handler1) {return $handler1; });
+		$manager->registerEventHandler(function () use ($handler2) {return $handler2; });
+
+		$comment = new Comment();
+		$comment
+			->setActor('users', 'alice')
+			->setObject('files', 'file64')
+			->setMessage('very beautiful, I am impressed!')
+			->setVerb('comment');
+
+		// Add event
+		$manager->save($comment);
+
+		// Update event
+		$comment->setMessage('Different topic');
+		$manager->save($comment);
+
+		// Delete event
+		$manager->delete($comment->getId());
+	}
+
 }