diff --git a/doc/notification.md b/doc/notification.md
index f9fb1e0..db0e48d 100644
--- a/doc/notification.md
+++ b/doc/notification.md
@@ -17,6 +17,7 @@
 Notified users are as follows:
 
 * individual repository's owner
+* group members of group repository
 * collaborators
 * participants
 
diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
index 596d99e..4029ff0 100644
--- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
+++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
@@ -281,11 +281,6 @@
             // call hooks
             PluginRegistry().getPullRequestHooks.foreach(_.merged(issue, repository))
 
-            // notifications  TODO move to plugin
-            Notifier().toNotify(repository, issue, "merge"){
-              Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
-            }
-
             redirect(s"/${owner}/${name}/pull/${issueId}")
           }
         }
@@ -490,11 +485,6 @@
 
         // call hooks
         PluginRegistry().getPullRequestHooks.foreach(_.created(issue, repository))
-
-        // notifications  TODO move to plugin
-        Notifier().toNotify(repository, issue, form.content.getOrElse("")) {
-          Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
-        }
       }
 
       redirect(s"/${owner}/${name}/pull/${issueId}")
diff --git a/src/main/scala/gitbucket/core/service/HandleCommentService.scala b/src/main/scala/gitbucket/core/service/HandleCommentService.scala
index 7fc0fc6..f7ce6ff 100644
--- a/src/main/scala/gitbucket/core/service/HandleCommentService.scala
+++ b/src/main/scala/gitbucket/core/service/HandleCommentService.scala
@@ -6,7 +6,6 @@
 import gitbucket.core.plugin.PluginRegistry
 import gitbucket.core.util.SyntaxSugars._
 import gitbucket.core.util.Implicits._
-import gitbucket.core.util.Notifier
 
 trait HandleCommentService {
   self: RepositoryService with IssuesService with ActivityService
@@ -87,22 +86,6 @@
               PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository))
         }
 
-        // notifications  // TODO move to plugin
-        Notifier() match {
-          case f =>
-            content foreach {
-              f.toNotify(repository, issue, _){
-                Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
-                  if(issue.isPullRequest) "pull" else "issues"}/${issue.issueId}#comment-${commentId.get}")
-              }
-            }
-            action foreach {
-              f.toNotify(repository, issue, _){
-                Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issue.issueId}")
-              }
-            }
-        }
-
         commentId.map( issue -> _ )
       }
     }
diff --git a/src/main/scala/gitbucket/core/service/IssueCreationService.scala b/src/main/scala/gitbucket/core/service/IssueCreationService.scala
index 1439c04..09e7dfb 100644
--- a/src/main/scala/gitbucket/core/service/IssueCreationService.scala
+++ b/src/main/scala/gitbucket/core/service/IssueCreationService.scala
@@ -5,7 +5,6 @@
 import gitbucket.core.model.Profile.profile.blockingApi._
 import gitbucket.core.plugin.PluginRegistry
 import gitbucket.core.service.RepositoryService.RepositoryInfo
-import gitbucket.core.util.Notifier
 import gitbucket.core.util.Implicits._
 
 trait IssueCreationService {
@@ -49,10 +48,6 @@
     // call hooks
     PluginRegistry().getIssueHooks.foreach(_.created(issue, repository))
 
-    // notifications TODO move to plugin
-    Notifier().toNotify(repository, issue, body.getOrElse("")) {
-      Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
-    }
     issue
   }
 
diff --git a/src/main/scala/gitbucket/core/util/Notifier.scala b/src/main/scala/gitbucket/core/util/Notifier.scala
index 3c8dba5..0c7d861 100644
--- a/src/main/scala/gitbucket/core/util/Notifier.scala
+++ b/src/main/scala/gitbucket/core/util/Notifier.scala
@@ -1,10 +1,9 @@
 package gitbucket.core.util
 
-import gitbucket.core.model.{Session, Issue, Account}
+import gitbucket.core.model.{Session, Account}
 import gitbucket.core.model.Profile.profile.blockingApi._
-import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, SystemSettingsService}
+import gitbucket.core.service.SystemSettingsService
 import gitbucket.core.servlet.Database
-import gitbucket.core.view.Markdown
 
 import scala.concurrent._
 import scala.util.{Success, Failure}
@@ -13,87 +12,38 @@
 import org.slf4j.LoggerFactory
 import gitbucket.core.controller.Context
 import SystemSettingsService.Smtp
-import SyntaxSugars.defining
 
-trait Notifier extends RepositoryService with AccountService with IssuesService {
+/**
+ * The trait for notifications.
+ * This is used by notifications plugin, which provides notifications feature on GitBucket.
+ * Please see the plugin for details.
+ */
+trait Notifier {
 
-  def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String)
-      (msg: String => String)(implicit context: Context): Unit
+  def toNotify(subject: String, msg: String)
+              (recipients: Account => Session => Seq[String])(implicit context: Context): Unit
 
-  protected def recipients(issue: Issue, loginAccount: Account)(notify: String => Unit)(implicit session: Session) =
-    (
-        // individual repository's owner
-        issue.userName ::
-        // group members of group repository
-        getGroupMembers(issue.userName).map(_.userName) :::
-        // collaborators
-        getCollaboratorUserNames(issue.userName, issue.repositoryName) :::
-        // participants
-        issue.openedUserName ::
-        getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
-    )
-    .distinct
-    .withFilter ( _ != loginAccount.userName )  // the operation in person is excluded
-    .foreach (
-      getAccountByUserName(_)
-        .filterNot (_.isGroupAccount)
-        .filterNot (LDAPUtil.isDummyMailAddress(_))
-        .foreach (x => notify(x.mailAddress))
-    )
 }
 
 object Notifier {
-  // TODO We want to be able to switch to mock.
   def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match {
     case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get)
     case _ => new MockMailer
   }
-
-  def msgIssue(url: String) = (content: String) => s"""
-    |${content}<br/>
-    |--<br/>
-    |<a href="${url}">View it on GitBucket</a>
-    """.stripMargin
-
-  def msgPullRequest(url: String) = (content: String) => s"""
-    |${content}<hr/>
-    |View, comment on, or merge it at:<br/>
-    |<a href="${url}">${url}</a>
-    """.stripMargin
-
-  def msgComment(url: String) = (content: String) => s"""
-    |${content}<br/>
-    |--<br/>
-    |<a href="${url}">View it on GitBucket</a>
-    """.stripMargin
-
-  def msgStatus(url: String) = (content: String) => s"""
-    |${content} <a href="${url}">#${url split('/') last}</a>
-    """.stripMargin
 }
 
 class Mailer(private val smtp: Smtp) extends Notifier {
   private val logger = LoggerFactory.getLogger(classOf[Mailer])
 
-  def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String)
-      (msg: String => String)(implicit context: Context): Unit = {
+  def toNotify(subject: String, msg: String)
+              (recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
     context.loginAccount.foreach { loginAccount =>
       val database = Database()
 
       val f = Future {
-        database withSession { implicit session =>
-          defining(
-            s"[${r.owner}/${r.name}] ${issue.title} (#${issue.issueId})" ->
-              msg(Markdown.toHtml(
-                markdown         = content,
-                repository       = r,
-                enableWikiLink   = false,
-                enableRefsLink   = true,
-                enableAnchor     = false,
-                enableLineBreaks = false
-              ))
-          ) { case (subject, msg) =>
-            recipients(issue, loginAccount) { to => send(to, subject, msg, loginAccount) }
+        database withSession { session =>
+          recipients(loginAccount)(session) foreach { to =>
+            send(to, subject, msg, loginAccount)
           }
         }
         "Notifications Successful."
@@ -137,6 +87,6 @@
 
 }
 class MockMailer extends Notifier {
-  def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String)
-      (msg: String => String)(implicit context: Context): Unit = {}
+  def toNotify(subject: String, msg: String)
+              (recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
 }