diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt
new file mode 100644
index 0000000000000000000000000000000000000000..07459942d0faa95351228df91e192eaf7c01e696
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt
@@ -0,0 +1,173 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2018 Janne Koschinski
+ * Copyright (c) 2018 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.service
+
+import android.content.Context
+import android.text.SpannableStringBuilder
+import de.kuschku.libquassel.protocol.*
+import de.kuschku.libquassel.quassel.BufferInfo
+import de.kuschku.libquassel.quassel.syncables.IgnoreListManager
+import de.kuschku.libquassel.session.NotificationManager
+import de.kuschku.libquassel.session.Session
+import de.kuschku.libquassel.util.IrcUserUtils
+import de.kuschku.libquassel.util.flag.hasFlag
+import de.kuschku.libquassel.util.irc.HostmaskHelper
+import de.kuschku.quasseldroid.GlideApp
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+import de.kuschku.quasseldroid.settings.MessageSettings
+import de.kuschku.quasseldroid.util.AvatarHelper
+import de.kuschku.quasseldroid.util.NotificationMessage
+import de.kuschku.quasseldroid.util.QuasseldroidNotificationManager
+import de.kuschku.quasseldroid.util.helper.getColorCompat
+import de.kuschku.quasseldroid.util.helper.loadWithFallbacks
+import de.kuschku.quasseldroid.util.irc.format.ContentFormatter
+import de.kuschku.quasseldroid.util.ui.TextDrawable
+import javax.inject.Inject
+
+class QuasselNotificationBackend @Inject constructor(
+  private val context: Context,
+  private val database: QuasselDatabase,
+  private val messageSettings: MessageSettings,
+  private val contentFormatter: ContentFormatter,
+  private val notificationHandler: QuasseldroidNotificationManager
+) : NotificationManager {
+  private val senderColors = listOf(
+    R.color.senderColor0, R.color.senderColor1, R.color.senderColor2, R.color.senderColor3,
+    R.color.senderColor4, R.color.senderColor5, R.color.senderColor6, R.color.senderColor7,
+    R.color.senderColor8, R.color.senderColor9, R.color.senderColorA, R.color.senderColorB,
+    R.color.senderColorC, R.color.senderColorD, R.color.senderColorE, R.color.senderColorF
+  ).map(context::getColorCompat).toIntArray()
+
+
+  private val selfColor = context.getColorCompat(android.R.color.background_dark)
+
+  override fun init(session: Session) {
+    // TODO
+  }
+
+  @Synchronized
+  override fun processMessages(session: Session, vararg messages: Message) {
+    val results = messages.filter {
+      it.flag.hasFlag(Message_Flag.Highlight) ||
+      it.bufferInfo.type.hasFlag(Buffer_Type.QueryBuffer)
+    }.filter {
+      val bufferName = it.bufferInfo.bufferName ?: ""
+      val networkId = it.bufferInfo.networkId
+      val networkName = session.network(networkId)?.networkName() ?: ""
+
+      session.ignoreListManager.match(
+        it.content, it.sender, it.type, networkName, bufferName
+      ) == IgnoreListManager.StrictnessType.UnmatchedStrictness
+    }.filter {
+      it.type.hasFlag(Message_Type.Plain) ||
+      it.type.hasFlag(Message_Type.Notice) ||
+      it.type.hasFlag(Message_Type.Action)
+    }.filter {
+      !it.flag.hasFlag(Message_Flag.Self)
+    }.map {
+      QuasselDatabase.NotificationData(
+        messageId = it.messageId,
+        time = it.time,
+        type = it.type,
+        flag = it.flag,
+        bufferId = it.bufferInfo.bufferId,
+        bufferName = it.bufferInfo.bufferName ?: "",
+        bufferType = it.bufferInfo.type,
+        networkId = it.bufferInfo.networkId,
+        sender = it.sender,
+        senderPrefixes = it.senderPrefixes,
+        realName = it.realName,
+        avatarUrl = it.avatarUrl,
+        content = it.content
+      )
+    }
+    database.notifications().save(*results.toTypedArray())
+    results.map(QuasselDatabase.NotificationData::bufferId).distinct().forEach(this::showNotification)
+  }
+
+  @Synchronized
+  private fun showNotification(buffer: BufferId) {
+    val data = database.notifications().all(buffer)
+    data.lastOrNull()?.let {
+      val bufferInfo = BufferInfo(
+        bufferId = it.bufferId,
+        bufferName = it.bufferName,
+        type = it.bufferType,
+        networkId = it.networkId,
+        groupId = 0
+      )
+      val notification = notificationHandler.notificationGroup(bufferInfo, data.map {
+        val nick = SpannableStringBuilder().apply {
+          append(contentFormatter.formatPrefix(it.senderPrefixes))
+          append(contentFormatter.formatNick(
+            it.sender,
+            senderColors = senderColors
+          ))
+        }
+        val content = contentFormatter.formatContent(it.content, false)
+
+        val nickName = HostmaskHelper.nick(it.sender)
+        val senderColorIndex = IrcUserUtils.senderColor(nickName)
+        val rawInitial = nickName.trimStart('-',
+                                            '_',
+                                            '[',
+                                            ']',
+                                            '{',
+                                            '}',
+                                            '|',
+                                            '`',
+                                            '^',
+                                            '.',
+                                            '\\')
+                           .firstOrNull() ?: nickName.firstOrNull()
+        val initial = rawInitial?.toUpperCase().toString()
+        val senderColor = if (it.flag.hasFlag(Message_Flag.Self))
+          selfColor
+        else
+          senderColors[senderColorIndex]
+
+        val size = context.resources.getDimensionPixelSize(R.dimen.notification_avatar_width)
+        val avatarList = AvatarHelper.avatar(messageSettings, it)
+        val avatarResult = GlideApp.with(context).loadWithFallbacks(avatarList)
+          ?.optionalCircleCrop()
+          ?.placeholder(TextDrawable.builder().buildRound(initial, senderColor))
+          ?.submit(size, size)
+          ?.get()
+        val avatar = avatarResult ?: TextDrawable.builder().buildRound(initial, senderColor)
+
+        NotificationMessage(
+          messageId = it.messageId,
+          sender = nick,
+          content = content,
+          time = it.time,
+          avatar = avatar
+        )
+      })
+      notificationHandler.notify(notification)
+    } ?: notificationHandler.remove(buffer)
+  }
+
+  @Synchronized
+  override fun clear(buffer: BufferId, lastRead: MsgId) {
+    database.notifications().markRead(buffer, lastRead)
+    showNotification(buffer)
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
index 9143fa3bf9839b02964401eaf988a2baa0fde87f..1f18744fd4ac872871f3ff9ca1252c06eca33d2f 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
@@ -25,17 +25,18 @@ package de.kuschku.quasseldroid.service
 import android.arch.lifecycle.Observer
 import android.content.*
 import android.net.ConnectivityManager
+import android.support.v4.app.RemoteInput
+import android.text.SpannableString
 import de.kuschku.libquassel.connection.ConnectionState
 import de.kuschku.libquassel.connection.HostnameVerifier
 import de.kuschku.libquassel.connection.SocketAddress
-import de.kuschku.libquassel.protocol.ClientData
-import de.kuschku.libquassel.protocol.Protocol
-import de.kuschku.libquassel.protocol.Protocol_Feature
-import de.kuschku.libquassel.protocol.Protocol_Features
+import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.quassel.QuasselFeatures
+import de.kuschku.libquassel.quassel.syncables.interfaces.IAliasManager
 import de.kuschku.libquassel.session.Backend
 import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.session.SessionManager
+import de.kuschku.libquassel.util.helpers.value
 import de.kuschku.malheur.CrashHandler
 import de.kuschku.quasseldroid.BuildConfig
 import de.kuschku.quasseldroid.Keys
@@ -44,6 +45,7 @@ import de.kuschku.quasseldroid.persistence.AccountDatabase
 import de.kuschku.quasseldroid.persistence.QuasselBacklogStorage
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.ConnectionSettings
+import de.kuschku.quasseldroid.settings.MessageSettings
 import de.kuschku.quasseldroid.settings.Settings
 import de.kuschku.quasseldroid.ssl.QuasselHostnameVerifier
 import de.kuschku.quasseldroid.ssl.QuasselTrustManager
@@ -52,10 +54,9 @@ import de.kuschku.quasseldroid.ssl.custom.QuasselHostnameManager
 import de.kuschku.quasseldroid.util.QuasseldroidNotificationManager
 import de.kuschku.quasseldroid.util.backport.DaggerLifecycleService
 import de.kuschku.quasseldroid.util.compatibility.AndroidHandlerService
-import de.kuschku.quasseldroid.util.helper.editApply
-import de.kuschku.quasseldroid.util.helper.editCommit
-import de.kuschku.quasseldroid.util.helper.sharedPreferences
-import de.kuschku.quasseldroid.util.helper.toLiveData
+import de.kuschku.quasseldroid.util.helper.*
+import de.kuschku.quasseldroid.util.irc.format.ContentFormatter
+import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer
 import io.reactivex.subjects.PublishSubject
 import org.threeten.bp.Instant
 import java.util.concurrent.TimeUnit
@@ -96,7 +97,15 @@ class QuasselService : DaggerLifecycleService(),
   private var accountId: Long = -1
   private var reconnect: Boolean = false
 
-  private lateinit var notificationManager: QuasseldroidNotificationManager
+  @Inject
+  lateinit var notificationManager: QuasseldroidNotificationManager
+
+  @Inject
+  lateinit var notificationBackend: QuasselNotificationBackend
+
+  @Inject
+  lateinit var ircFormatSerializer: IrcFormatSerializer
+
   private var notificationHandle: QuasseldroidNotificationManager.Handle? = null
   private var progress = Triple(ConnectionState.DISCONNECTED, 0, 0)
 
@@ -114,18 +123,46 @@ class QuasselService : DaggerLifecycleService(),
 
   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
     val result = super.onStartCommand(intent, flags, startId)
-    handleIntent(intent)
+    intent?.let(this@QuasselService::handleIntent)
     return result
   }
 
-  private fun handleIntent(intent: Intent?) {
-    if (intent?.getBooleanExtra("disconnect", false) == true) {
+  private fun handleIntent(intent: Intent) {
+    if (intent.getBooleanExtra("disconnect", false)) {
       sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) {
         editApply {
           putBoolean(Keys.Status.reconnect, false)
         }
       }
     }
+
+    val bufferId = intent.getIntExtra("buffer", -1)
+
+    val inputResults = RemoteInput.getResultsFromIntent(intent)?.getCharSequence("reply_content")
+    if (inputResults != null && bufferId != -1) {
+      if (inputResults.isNotBlank()) {
+        val lines = inputResults.lineSequence().map {
+          it.toString() to ircFormatSerializer.toEscapeCodes(SpannableString(it))
+        }
+
+        sessionManager.session.value?.let { session ->
+          session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
+            val output = mutableListOf<IAliasManager.Command>()
+            for ((_, formatted) in lines) {
+              session.aliasManager?.processInput(bufferInfo, formatted, output)
+            }
+            for (command in output) {
+              session.rpcHandler?.sendInput(command.buffer, command.message)
+            }
+          }
+        }
+      }
+    }
+
+    val clearMessageId = intent.getIntExtra("mark_read_message", -1)
+    if (bufferId != -1 && clearMessageId != -1) {
+      sessionManager.session.value?.bufferSyncer?.requestSetLastSeenMsg(bufferId, clearMessageId)
+    }
   }
 
   private fun updateNotification(handle: QuasseldroidNotificationManager.Handle,
@@ -236,7 +273,13 @@ class QuasselService : DaggerLifecycleService(),
   @Inject
   lateinit var accountDatabase: AccountDatabase
 
-  private val receiver = object : BroadcastReceiver() {
+  @Inject
+  lateinit var contentFormatter: ContentFormatter
+
+  @Inject
+  lateinit var messageSettings: MessageSettings
+
+  private val connectivityReceiver = object : BroadcastReceiver() {
     override fun onReceive(context: Context?, intent: Intent?) {
       if (context != null && intent != null) {
         connectivity.onNext(Unit)
@@ -261,6 +304,7 @@ class QuasselService : DaggerLifecycleService(),
     sessionManager = SessionManager(
       ISession.NULL,
       QuasselBacklogStorage(database),
+      notificationBackend,
       handlerService,
       ::disconnectFromCore,
       CrashHandler::handle
@@ -328,9 +372,8 @@ class QuasselService : DaggerLifecycleService(),
       registerOnSharedPreferenceChangeListener(this@QuasselService)
     }
 
-    registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
+    registerReceiver(connectivityReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
 
-    notificationManager = QuasseldroidNotificationManager(this)
     notificationManager.init()
 
     update()
@@ -345,7 +388,7 @@ class QuasselService : DaggerLifecycleService(),
       unregisterOnSharedPreferenceChangeListener(this@QuasselService)
     }
 
-    unregisterReceiver(receiver)
+    unregisterReceiver(connectivityReceiver)
 
     notificationHandle?.let { notificationManager.remove(it) }
     super.onDestroy()
@@ -359,16 +402,26 @@ class QuasselService : DaggerLifecycleService(),
   companion object {
     fun launch(
       context: Context,
-      disconnect: Boolean? = null
-    ): ComponentName = context.startService(intent(context, disconnect))
+      disconnect: Boolean? = null,
+      markRead: BufferId? = null,
+      markReadMessage: MsgId? = null
+    ): ComponentName = context.startService(intent(context, disconnect, markRead, markReadMessage))
 
     fun intent(
       context: Context,
-      disconnect: Boolean? = null
+      disconnect: Boolean? = null,
+      bufferId: BufferId? = null,
+      markReadMessage: MsgId? = null
     ) = Intent(context, QuasselService::class.java).apply {
       if (disconnect != null) {
         putExtra("disconnect", disconnect)
       }
+      if (bufferId != null) {
+        putExtra("buffer", bufferId)
+      }
+      if (markReadMessage != null) {
+        putExtra("mark_read_message", markReadMessage)
+      }
     }
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt
index ba91d912dc7ce2cb8d9ee8eadf91d2c9795ab012..1808480238f69efa0d39de6b178b366d72be3d44 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselServiceModule.kt
@@ -25,9 +25,14 @@ package de.kuschku.quasseldroid.service
 import android.content.Context
 import dagger.Binds
 import dagger.Module
+import de.kuschku.quasseldroid.ui.chat.messages.MessageRenderer
+import de.kuschku.quasseldroid.ui.chat.messages.QuasselMessageRenderer
 
 @Module
 abstract class QuasselServiceModule {
   @Binds
   abstract fun bindContext(service: QuasselService): Context
+
+  @Binds
+  abstract fun bindMessageRenderer(messageRenderer: QuasselMessageRenderer): MessageRenderer
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt
index 027161385ba2ebb6a236570b34ee180eba1f9eae..efa92b8ac2de06001a932cf936724134bd697748 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/channel/ChannelInfoFragment.kt
@@ -92,7 +92,7 @@ class ChannelInfoFragment : ServiceBoundFragment() {
     }.switchMap(IrcChannel::updates).toLiveData().observe(this, Observer { channel ->
       if (channel != null) {
         name.text = channel.name()
-        topic.text = contentFormatter.format(channel.topic())
+        topic.text = contentFormatter.formatContent(channel.topic())
 
         actionEditTopic.setOnClickListener {
           TopicActivity.launch(requireContext(), buffer = arguments?.getInt("bufferId") ?: -1)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt
index fff72805b84b7dabb48480e970895729af8ccab6..ba471e87fdd96baab464f5366ba6a379e881258b 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt
@@ -156,7 +156,7 @@ class UserInfoFragment : ServiceBoundFragment() {
         )
 
         nick.text = user.nick()
-        realName.text = contentFormatter.format(user.realName())
+        realName.text = contentFormatter.formatContent(user.realName())
         realName.visibleIf(user.realName().isNotBlank() && user.realName() != user.nick())
 
         awayMessage.text = user.awayMessage()
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt
index f7c8e3aee31e883178b747441015b7476fce8020..ef858f3bbd70d84c8fdb64be438ce891de686b1f 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/EditorHelper.kt
@@ -32,6 +32,7 @@ import android.view.inputmethod.EditorInfo
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.AutoCompleteSettings
+import de.kuschku.quasseldroid.util.helper.getColorCompat
 import de.kuschku.quasseldroid.util.helper.lastWordIndices
 import de.kuschku.quasseldroid.util.helper.styledAttributes
 import de.kuschku.quasseldroid.util.ui.ColorChooserDialog
@@ -47,14 +48,12 @@ class EditorHelper(
 ) {
   private var enterListener: (() -> Unit)? = null
 
-  private val mircColors = editText.context.theme.styledAttributes(
-    R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03,
-    R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07,
-    R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11,
-    R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15
-  ) {
-    IntArray(length(), { getColor(it, 0) })
-  }
+  private val mircColors = listOf(
+    R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03,
+    R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07,
+    R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11,
+    R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15
+  ).map(activity::getColorCompat).toIntArray()
 
   private val defaultForegroundColor = editText.context.theme.styledAttributes(R.attr.colorForeground) {
     getColor(0, 0)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt
index 4f6cb65691b636bf96119f4ca651391b4bfdb614..a0b16801bee636c99ecd2f518afe4fea5aee19ec 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt
@@ -36,35 +36,33 @@ import de.kuschku.quasseldroid.util.ui.DoubleClickHelper
 import de.kuschku.quasseldroid.util.ui.EditTextSelectionChange
 
 class RichEditText : EditTextSelectionChange {
-  private val mircColors = this.context.theme.styledAttributes(
-    R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03,
-    R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07,
-    R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11,
-    R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15,
-    R.attr.mircColor16, R.attr.mircColor17, R.attr.mircColor18, R.attr.mircColor19,
-    R.attr.mircColor20, R.attr.mircColor21, R.attr.mircColor22, R.attr.mircColor23,
-    R.attr.mircColor24, R.attr.mircColor25, R.attr.mircColor26, R.attr.mircColor27,
-    R.attr.mircColor28, R.attr.mircColor29, R.attr.mircColor30, R.attr.mircColor31,
-    R.attr.mircColor32, R.attr.mircColor33, R.attr.mircColor34, R.attr.mircColor35,
-    R.attr.mircColor36, R.attr.mircColor37, R.attr.mircColor38, R.attr.mircColor39,
-    R.attr.mircColor40, R.attr.mircColor41, R.attr.mircColor42, R.attr.mircColor43,
-    R.attr.mircColor44, R.attr.mircColor45, R.attr.mircColor46, R.attr.mircColor47,
-    R.attr.mircColor48, R.attr.mircColor49, R.attr.mircColor50, R.attr.mircColor51,
-    R.attr.mircColor52, R.attr.mircColor53, R.attr.mircColor54, R.attr.mircColor55,
-    R.attr.mircColor56, R.attr.mircColor57, R.attr.mircColor58, R.attr.mircColor59,
-    R.attr.mircColor60, R.attr.mircColor61, R.attr.mircColor62, R.attr.mircColor63,
-    R.attr.mircColor64, R.attr.mircColor65, R.attr.mircColor66, R.attr.mircColor67,
-    R.attr.mircColor68, R.attr.mircColor69, R.attr.mircColor70, R.attr.mircColor71,
-    R.attr.mircColor72, R.attr.mircColor73, R.attr.mircColor74, R.attr.mircColor75,
-    R.attr.mircColor76, R.attr.mircColor77, R.attr.mircColor78, R.attr.mircColor79,
-    R.attr.mircColor80, R.attr.mircColor81, R.attr.mircColor82, R.attr.mircColor83,
-    R.attr.mircColor84, R.attr.mircColor85, R.attr.mircColor86, R.attr.mircColor87,
-    R.attr.mircColor88, R.attr.mircColor89, R.attr.mircColor90, R.attr.mircColor91,
-    R.attr.mircColor92, R.attr.mircColor93, R.attr.mircColor94, R.attr.mircColor95,
-    R.attr.mircColor96, R.attr.mircColor97, R.attr.mircColor98
-  ) {
-    IntArray(length(), { getColor(it, 0) })
-  }
+  val mircColors = listOf(
+    R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03,
+    R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07,
+    R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11,
+    R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15,
+    R.color.mircColor16, R.color.mircColor17, R.color.mircColor18, R.color.mircColor19,
+    R.color.mircColor20, R.color.mircColor21, R.color.mircColor22, R.color.mircColor23,
+    R.color.mircColor24, R.color.mircColor25, R.color.mircColor26, R.color.mircColor27,
+    R.color.mircColor28, R.color.mircColor29, R.color.mircColor30, R.color.mircColor31,
+    R.color.mircColor32, R.color.mircColor33, R.color.mircColor34, R.color.mircColor35,
+    R.color.mircColor36, R.color.mircColor37, R.color.mircColor38, R.color.mircColor39,
+    R.color.mircColor40, R.color.mircColor41, R.color.mircColor42, R.color.mircColor43,
+    R.color.mircColor44, R.color.mircColor45, R.color.mircColor46, R.color.mircColor47,
+    R.color.mircColor48, R.color.mircColor49, R.color.mircColor50, R.color.mircColor51,
+    R.color.mircColor52, R.color.mircColor53, R.color.mircColor54, R.color.mircColor55,
+    R.color.mircColor56, R.color.mircColor57, R.color.mircColor58, R.color.mircColor59,
+    R.color.mircColor60, R.color.mircColor61, R.color.mircColor62, R.color.mircColor63,
+    R.color.mircColor64, R.color.mircColor65, R.color.mircColor66, R.color.mircColor67,
+    R.color.mircColor68, R.color.mircColor69, R.color.mircColor70, R.color.mircColor71,
+    R.color.mircColor72, R.color.mircColor73, R.color.mircColor74, R.color.mircColor75,
+    R.color.mircColor76, R.color.mircColor77, R.color.mircColor78, R.color.mircColor79,
+    R.color.mircColor80, R.color.mircColor81, R.color.mircColor82, R.color.mircColor83,
+    R.color.mircColor84, R.color.mircColor85, R.color.mircColor86, R.color.mircColor87,
+    R.color.mircColor88, R.color.mircColor89, R.color.mircColor90, R.color.mircColor91,
+    R.color.mircColor92, R.color.mircColor93, R.color.mircColor94, R.color.mircColor95,
+    R.color.mircColor96, R.color.mircColor97, R.color.mircColor98
+  ).map(context::getColorCompat).toIntArray()
   private val mircColorMap = mircColors.withIndex().map { (key, value) -> key to value }.toMap()
 
   private var formattingListener: ((Boolean, Boolean, Boolean, Boolean, Boolean, Int?, Int?) -> Unit)? = null
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt
index 82ecdbd852c728d03d8a58fe3c2e3b3cd0a92702..7f500dba82d9b58de8b72938541ed6ea19d3ad0e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt
@@ -26,7 +26,7 @@ import de.kuschku.libquassel.protocol.MsgId
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 
 data class DisplayMessage(
-  val content: QuasselDatabase.DatabaseMessage,
+  val content: QuasselDatabase.MessageData,
   val hasDayChange: Boolean,
   val isFollowUp: Boolean,
   val isSelected: Boolean,
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
index a1b33bdb48b678ba7be2f23ab0a42aab123d9dc8..c631e7e908aa406bbb86c4b327fa423117f8aca1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
@@ -58,8 +58,8 @@ class MessageAdapter @Inject constructor(
   private val movementMethod = BetterLinkMovementMethod.newInstance()
   private var clickListener: ((FormattedMessage) -> Unit)? = null
   private var longClickListener: ((FormattedMessage) -> Unit)? = null
-  private var doubleClickListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null
-  private var expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null
+  private var doubleClickListener: ((QuasselDatabase.MessageData) -> Unit)? = null
+  private var expansionListener: ((QuasselDatabase.MessageData) -> Unit)? = null
   private var urlLongClickListener: ((TextView, String) -> Boolean)? = null
 
   fun setOnClickListener(listener: ((FormattedMessage) -> Unit)?) {
@@ -70,11 +70,11 @@ class MessageAdapter @Inject constructor(
     this.longClickListener = listener
   }
 
-  fun setOnDoubleClickListener(listener: ((QuasselDatabase.DatabaseMessage) -> Unit)?) {
+  fun setOnDoubleClickListener(listener: ((QuasselDatabase.MessageData) -> Unit)?) {
     this.doubleClickListener = listener
   }
 
-  fun setOnExpansionListener(listener: ((QuasselDatabase.DatabaseMessage) -> Unit)?) {
+  fun setOnExpansionListener(listener: ((QuasselDatabase.MessageData) -> Unit)?) {
     this.expansionListener = listener
   }
 
@@ -107,8 +107,8 @@ class MessageAdapter @Inject constructor(
   }
 
   override fun getItemViewType(position: Int) = getItem(position)?.let {
-    Message_Flag.of(it.content.type).value or
-      (if (Message_Flag.of(it.content.flag).hasFlag(Message_Flag.Highlight)) MASK_HIGHLIGHT else 0x00) or
+    it.content.type.value or
+      (if (it.content.flag.hasFlag(Message_Flag.Highlight)) MASK_HIGHLIGHT else 0x00) or
       (if (it.isFollowUp) MASK_FOLLOWUP else 0x00) or
       (if (it.isEmoji) MASK_EMOJI else 0x00)
   } ?: 0
@@ -167,8 +167,8 @@ class MessageAdapter @Inject constructor(
     itemView: View,
     clickListener: ((FormattedMessage) -> Unit)? = null,
     longClickListener: ((FormattedMessage) -> Unit)? = null,
-    doubleClickListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null,
-    expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null,
+    doubleClickListener: ((QuasselDatabase.MessageData) -> Unit)? = null,
+    expansionListener: ((QuasselDatabase.MessageData) -> Unit)? = null,
     movementMethod: BetterLinkMovementMethod
   ) : RecyclerView.ViewHolder(itemView) {
     @BindView(R.id.time_left)
@@ -200,7 +200,7 @@ class MessageAdapter @Inject constructor(
     var combined: TextView? = null
 
     private var message: FormattedMessage? = null
-    private var original: QuasselDatabase.DatabaseMessage? = null
+    private var original: QuasselDatabase.MessageData? = null
     private var selectable: Boolean = false
     private var clickable: Boolean = false
 
@@ -241,7 +241,7 @@ class MessageAdapter @Inject constructor(
       })
     }
 
-    fun bind(message: FormattedMessage, original: QuasselDatabase.DatabaseMessage,
+    fun bind(message: FormattedMessage, original: QuasselDatabase.MessageData,
              selectable: Boolean = true, clickable: Boolean = true) {
       this.message = message
       this.original = original
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
index d32b5ec04a04645bf5cee8b37b5d18c19c2b7758..2f45fac3a38d6d891b8cc2e83ad9fee64f5314b1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
@@ -266,9 +266,9 @@ class MessageListFragment : ServiceBoundFragment() {
         }
       })
 
-    fun processMessages(list: List<QuasselDatabase.DatabaseMessage>, selected: Set<MsgId>,
+    fun processMessages(list: List<QuasselDatabase.MessageData>, selected: Set<MsgId>,
                         expanded: Set<MsgId>, markerLine: MsgId?): List<DisplayMessage> {
-      var previous: QuasselDatabase.DatabaseMessage? = null
+      var previous: QuasselDatabase.MessageData? = null
       var previousDate: ZonedDateTime? = null
       return list.mapReverse {
         val date = it.time.atZone(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt
index 5c04e7070163bc2991d9ca4ec9d6317c64484513..75d87e0286bb33ae0d163914604e37a205d12596 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt
@@ -33,7 +33,7 @@ interface MessageRenderer {
   fun layout(type: Message_Type?, hasHighlight: Boolean, isFollowUp: Boolean, isEmoji: Boolean): Int
 
   fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage,
-           original: QuasselDatabase.DatabaseMessage)
+           original: QuasselDatabase.MessageData)
 
   fun render(context: Context, message: DisplayMessage): FormattedMessage
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
index 6e749e16fdb5cf24917f2d2de103437592108cbd..aa6db697ebe360c0688f5127b30d9435a53f7072 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
@@ -27,10 +27,7 @@ import android.graphics.Typeface
 import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Build
-import android.text.SpannableString
 import android.text.SpannableStringBuilder
-import android.text.style.ForegroundColorSpan
-import android.text.style.StyleSpan
 import android.util.TypedValue
 import android.view.View
 import android.widget.FrameLayout
@@ -44,8 +41,6 @@ import de.kuschku.libquassel.util.irc.HostmaskHelper
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.MessageSettings
-import de.kuschku.quasseldroid.settings.MessageSettings.ColorizeNicknamesMode
-import de.kuschku.quasseldroid.settings.MessageSettings.ShowPrefixMode
 import de.kuschku.quasseldroid.util.AvatarHelper
 import de.kuschku.quasseldroid.util.helper.styledAttributes
 import de.kuschku.quasseldroid.util.helper.visibleIf
@@ -190,8 +185,8 @@ class QuasselMessageRenderer @Inject constructor(
   }
 
   override fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage,
-                    original: QuasselDatabase.DatabaseMessage) =
-    Message_Type.of(original.type).hasFlag(DayChange).let { isDayChange ->
+                    original: QuasselDatabase.MessageData) =
+    original.type.hasFlag(DayChange).let { isDayChange ->
       holder.bind(message, original, !isDayChange, !isDayChange)
     }
 
@@ -202,27 +197,27 @@ class QuasselMessageRenderer @Inject constructor(
       context.resources.displayMetrics
     ).roundToInt()
 
-    val self = Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Self)
-    val highlight = Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Highlight)
-    return when (Message_Type.of(message.content.type).enabledValues().firstOrNull()) {
+    val self = message.content.flag.hasFlag(Message_Flag.Self)
+    val highlight = message.content.flag.hasFlag(Message_Flag.Highlight)
+    return when (message.content.type.enabledValues().firstOrNull()) {
       Message_Type.Plain        -> {
-        val realName = contentFormatter.format(message.content.realName, highlight)
+        val realName = contentFormatter.formatContent(message.content.realName, highlight)
         val nick = SpannableStringBuilder().apply {
-          append(formatPrefix(message.content.senderPrefixes, highlight))
-          append(formatNick(
+          append(contentFormatter.formatPrefix(message.content.senderPrefixes))
+          append(contentFormatter.formatNick(
             message.content.sender,
             self,
             highlight,
             messageSettings.showHostmaskPlain && messageSettings.nicksOnNewLine
           ))
         }
-        val content = contentFormatter.format(message.content.content, highlight)
+        val content = contentFormatter.formatContent(message.content.content, highlight)
         val nickName = HostmaskHelper.nick(message.content.sender)
         val senderColorIndex = IrcUserUtils.senderColor(nickName)
         val rawInitial = nickName.trimStart('-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\')
                            .firstOrNull() ?: nickName.firstOrNull()
         val initial = rawInitial?.toUpperCase().toString()
-        val senderColor = if (Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Self))
+        val senderColor = if (message.content.flag.hasFlag(Message_Flag.Self))
           selfColor
         else
           senderColors[senderColorIndex]
@@ -250,9 +245,9 @@ class QuasselMessageRenderer @Inject constructor(
         time = timeFormatter.format(message.content.time.atZone(zoneId)),
         combined = SpanFormatter.format(
           context.getString(R.string.message_format_action),
-          formatPrefix(message.content.senderPrefixes, highlight),
-          formatNick(message.content.sender, self, highlight, false),
-          contentFormatter.format(message.content.content, highlight)
+          contentFormatter.formatPrefix(message.content.senderPrefixes),
+          contentFormatter.formatNick(message.content.sender, self, highlight, false),
+          contentFormatter.formatContent(message.content.content, highlight)
         ),
         isMarkerLine = message.isMarkerLine,
         isExpanded = message.isExpanded,
@@ -263,9 +258,9 @@ class QuasselMessageRenderer @Inject constructor(
         time = timeFormatter.format(message.content.time.atZone(zoneId)),
         combined = SpanFormatter.format(
           context.getString(R.string.message_format_notice),
-          formatPrefix(message.content.senderPrefixes, highlight),
-          formatNick(message.content.sender, self, highlight, false),
-          contentFormatter.format(message.content.content, highlight)
+          contentFormatter.formatPrefix(message.content.senderPrefixes),
+          contentFormatter.formatNick(message.content.sender, self, highlight, false),
+          contentFormatter.formatContent(message.content.content, highlight)
         ),
         isMarkerLine = message.isMarkerLine,
         isExpanded = message.isExpanded,
@@ -279,16 +274,16 @@ class QuasselMessageRenderer @Inject constructor(
           combined = if (nickSelf) {
             SpanFormatter.format(
               context.getString(R.string.message_format_nick_self),
-              formatPrefix(message.content.senderPrefixes, highlight),
-              formatNick(message.content.sender, nickSelf, highlight, false)
+              contentFormatter.formatPrefix(message.content.senderPrefixes),
+              contentFormatter.formatNick(message.content.sender, nickSelf, highlight, false)
             )
           } else {
             SpanFormatter.format(
               context.getString(R.string.message_format_nick),
-              formatPrefix(message.content.senderPrefixes, highlight),
-              formatNick(message.content.sender, nickSelf, highlight, false),
-              formatPrefix(message.content.senderPrefixes, highlight),
-              formatNick(message.content.content, nickSelf, highlight, false)
+              contentFormatter.formatPrefix(message.content.senderPrefixes),
+              contentFormatter.formatNick(message.content.sender, nickSelf, highlight, false),
+              contentFormatter.formatPrefix(message.content.senderPrefixes),
+              contentFormatter.formatNick(message.content.content, nickSelf, highlight, false)
             )
           },
           isMarkerLine = message.isMarkerLine,
@@ -302,8 +297,8 @@ class QuasselMessageRenderer @Inject constructor(
         combined = SpanFormatter.format(
           context.getString(R.string.message_format_mode),
           message.content.content,
-          formatPrefix(message.content.senderPrefixes, highlight),
-          formatNick(message.content.sender, self, highlight, false)
+          contentFormatter.formatPrefix(message.content.senderPrefixes),
+          contentFormatter.formatNick(message.content.sender, self, highlight, false)
         ),
         isMarkerLine = message.isMarkerLine,
         isExpanded = message.isExpanded,
@@ -314,8 +309,11 @@ class QuasselMessageRenderer @Inject constructor(
         time = timeFormatter.format(message.content.time.atZone(zoneId)),
         combined = SpanFormatter.format(
           context.getString(R.string.message_format_join),
-          formatPrefix(message.content.senderPrefixes, highlight),
-          formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions),
+          contentFormatter.formatPrefix(message.content.senderPrefixes),
+          contentFormatter.formatNick(message.content.sender,
+                                      self,
+                                      highlight,
+                                      messageSettings.showHostmaskActions),
           message.content.content
         ),
         isMarkerLine = message.isMarkerLine,
@@ -328,18 +326,21 @@ class QuasselMessageRenderer @Inject constructor(
         combined = if (message.content.content.isBlank()) {
           SpanFormatter.format(
             context.getString(R.string.message_format_part_1),
-            formatPrefix(message.content.senderPrefixes, highlight),
-            formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions)
+            contentFormatter.formatPrefix(message.content.senderPrefixes),
+            contentFormatter.formatNick(message.content.sender,
+                                        self,
+                                        highlight,
+                                        messageSettings.showHostmaskActions)
           )
         } else {
           SpanFormatter.format(
             context.getString(R.string.message_format_part_2),
-            formatPrefix(message.content.senderPrefixes, highlight),
-            formatNick(message.content.sender,
+            contentFormatter.formatPrefix(message.content.senderPrefixes),
+            contentFormatter.formatNick(message.content.sender,
                        self,
                        highlight,
                        messageSettings.showHostmaskActions),
-            contentFormatter.format(message.content.content, highlight)
+            contentFormatter.formatContent(message.content.content, highlight)
           )
         },
         isMarkerLine = message.isMarkerLine,
@@ -352,18 +353,21 @@ class QuasselMessageRenderer @Inject constructor(
         combined = if (message.content.content.isBlank()) {
           SpanFormatter.format(
             context.getString(R.string.message_format_quit_1),
-            formatPrefix(message.content.senderPrefixes, highlight),
-            formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions)
+            contentFormatter.formatPrefix(message.content.senderPrefixes),
+            contentFormatter.formatNick(message.content.sender,
+                                        self,
+                                        highlight,
+                                        messageSettings.showHostmaskActions)
           )
         } else {
           SpanFormatter.format(
             context.getString(R.string.message_format_quit_2),
-            formatPrefix(message.content.senderPrefixes, highlight),
-            formatNick(message.content.sender,
+            contentFormatter.formatPrefix(message.content.senderPrefixes),
+            contentFormatter.formatNick(message.content.sender,
                        self,
                        highlight,
                        messageSettings.showHostmaskActions),
-            contentFormatter.format(message.content.content, highlight)
+            contentFormatter.formatContent(message.content.content, highlight)
           )
         },
         isMarkerLine = message.isMarkerLine,
@@ -378,9 +382,9 @@ class QuasselMessageRenderer @Inject constructor(
           combined = if (reason.isBlank()) {
             SpanFormatter.format(
               context.getString(R.string.message_format_kick_1),
-              formatNick(user, false, highlight, false),
-              formatPrefix(message.content.senderPrefixes, highlight),
-              formatNick(message.content.sender,
+              contentFormatter.formatNick(user, false, highlight, false),
+              contentFormatter.formatPrefix(message.content.senderPrefixes),
+              contentFormatter.formatNick(message.content.sender,
                          self,
                          highlight,
                          messageSettings.showHostmaskActions)
@@ -388,13 +392,13 @@ class QuasselMessageRenderer @Inject constructor(
           } else {
             SpanFormatter.format(
               context.getString(R.string.message_format_kick_2),
-              formatNick(user, false, highlight, false),
-              formatPrefix(message.content.senderPrefixes, highlight),
-              formatNick(message.content.sender,
+              contentFormatter.formatNick(user, false, highlight, false),
+              contentFormatter.formatPrefix(message.content.senderPrefixes),
+              contentFormatter.formatNick(message.content.sender,
                          self,
                          highlight,
                          messageSettings.showHostmaskActions),
-              contentFormatter.format(reason, highlight)
+              contentFormatter.formatContent(reason, highlight)
             )
           },
           isMarkerLine = message.isMarkerLine,
@@ -410,9 +414,9 @@ class QuasselMessageRenderer @Inject constructor(
           combined = if (reason.isBlank()) {
             SpanFormatter.format(
               context.getString(R.string.message_format_kill_1),
-              formatNick(user, false, highlight, false),
-              formatPrefix(message.content.senderPrefixes, highlight),
-              formatNick(message.content.sender,
+              contentFormatter.formatNick(user, false, highlight, false),
+              contentFormatter.formatPrefix(message.content.senderPrefixes),
+              contentFormatter.formatNick(message.content.sender,
                          self,
                          highlight,
                          messageSettings.showHostmaskActions)
@@ -420,13 +424,13 @@ class QuasselMessageRenderer @Inject constructor(
           } else {
             SpanFormatter.format(
               context.getString(R.string.message_format_kill_2),
-              formatNick(user, false, highlight, false),
-              formatPrefix(message.content.senderPrefixes, highlight),
-              formatNick(message.content.sender,
+              contentFormatter.formatNick(user, false, highlight, false),
+              contentFormatter.formatPrefix(message.content.senderPrefixes),
+              contentFormatter.formatNick(message.content.sender,
                          self,
                          highlight,
                          messageSettings.showHostmaskActions),
-              contentFormatter.format(reason, highlight)
+              contentFormatter.formatContent(reason, highlight)
             )
           },
           isMarkerLine = message.isMarkerLine,
@@ -471,7 +475,7 @@ class QuasselMessageRenderer @Inject constructor(
       Message_Type.Error        -> FormattedMessage(
         id = message.content.messageId,
         time = timeFormatter.format(message.content.time.atZone(zoneId)),
-        combined = contentFormatter.format(message.content.content, highlight),
+        combined = contentFormatter.formatContent(message.content.content, highlight),
         isMarkerLine = message.isMarkerLine,
         isExpanded = message.isExpanded,
         isSelected = message.isSelected
@@ -479,7 +483,7 @@ class QuasselMessageRenderer @Inject constructor(
       Message_Type.Topic        -> FormattedMessage(
         id = message.content.messageId,
         time = timeFormatter.format(message.content.time.atZone(zoneId)),
-        combined = contentFormatter.format(message.content.content, highlight),
+        combined = contentFormatter.formatContent(message.content.content, highlight),
         isMarkerLine = message.isMarkerLine,
         isExpanded = message.isExpanded,
         isSelected = message.isSelected
@@ -498,8 +502,11 @@ class QuasselMessageRenderer @Inject constructor(
         combined = SpanFormatter.format(
           "[%d] %s%s: %s",
           message.content.type,
-          formatPrefix(message.content.senderPrefixes, highlight),
-          formatNick(message.content.sender, self, highlight, messageSettings.showHostmaskActions),
+          contentFormatter.formatPrefix(message.content.senderPrefixes),
+          contentFormatter.formatNick(message.content.sender,
+                                      self,
+                                      highlight,
+                                      messageSettings.showHostmaskActions),
           message.content.content
         ),
         isMarkerLine = message.isMarkerLine,
@@ -508,52 +515,4 @@ class QuasselMessageRenderer @Inject constructor(
       )
     }
   }
-
-  private fun formatNickNickImpl(nick: String, colorize: Boolean): CharSequence {
-    val spannableString = SpannableString(nick)
-    if (colorize) {
-      val senderColor = IrcUserUtils.senderColor(nick)
-      spannableString.setSpan(
-        ForegroundColorSpan(senderColors[(senderColor + senderColors.size) % senderColors.size]),
-        0,
-        nick.length,
-        SpannableString.SPAN_INCLUSIVE_EXCLUSIVE
-      )
-    }
-    spannableString.setSpan(
-      StyleSpan(Typeface.BOLD),
-      0,
-      nick.length,
-      SpannableString.SPAN_INCLUSIVE_EXCLUSIVE
-    )
-    return spannableString
-  }
-
-  private fun formatNickImpl(sender: String, colorize: Boolean, hostmask: Boolean): CharSequence {
-    val nick = IrcUserUtils.nick(sender)
-    val mask = IrcUserUtils.mask(sender)
-    val formattedNick = formatNickNickImpl(nick, colorize)
-
-    return if (hostmask) {
-      SpanFormatter.format("%s (%s)", formattedNick, mask)
-    } else {
-      formattedNick
-    }
-  }
-
-  private fun formatNick(sender: String, self: Boolean, highlight: Boolean, showHostmask: Boolean) =
-    when (messageSettings.colorizeNicknames) {
-      ColorizeNicknamesMode.ALL          ->
-        formatNickImpl(sender, !highlight, showHostmask)
-      ColorizeNicknamesMode.ALL_BUT_MINE ->
-        formatNickImpl(sender, !self && !highlight, showHostmask)
-      ColorizeNicknamesMode.NONE         ->
-        formatNickImpl(sender, false, showHostmask)
-    }
-
-  private fun formatPrefix(prefix: String, highlight: Boolean) = when (messageSettings.showPrefix) {
-    ShowPrefixMode.ALL     -> prefix
-    ShowPrefixMode.HIGHEST -> prefix.substring(0, Math.min(prefix.length, 1))
-    ShowPrefixMode.NONE    -> ""
-  }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt
index 7bc4535e9569dfcba8ffcb72e154eff8416ef83c..b2a8af08ac03334b28b2d5ab92c0bebd50a719a5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt
@@ -34,7 +34,18 @@ import de.kuschku.quasseldroid.viewmodel.data.IrcUserItem
 import org.apache.commons.codec.digest.DigestUtils
 
 object AvatarHelper {
-  fun avatar(settings: MessageSettings, message: QuasselDatabase.DatabaseMessage,
+  fun avatar(settings: MessageSettings, message: QuasselDatabase.NotificationData,
+             size: Int? = null) = listOfNotNull(
+    message.avatarUrl.notBlank()?.let { listOf(it) },
+    settings.showIRCCloudAvatars.letIf {
+      ircCloudFallback(HostmaskHelper.user(message.sender), size)
+    },
+    settings.showGravatarAvatars.letIf {
+      gravatarFallback(message.realName, size)
+    }
+  ).flatten()
+
+  fun avatar(settings: MessageSettings, message: QuasselDatabase.MessageData,
              size: Int? = null) = listOfNotNull(
     message.avatarUrl.notBlank()?.let { listOf(it) },
     settings.showIRCCloudAvatars.letIf {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/NotificationMessage.kt b/app/src/main/java/de/kuschku/quasseldroid/util/NotificationMessage.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3d056375831e91a0659104cae1453d6b65af9128
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/NotificationMessage.kt
@@ -0,0 +1,32 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2018 Janne Koschinski
+ * Copyright (c) 2018 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.util
+
+import android.graphics.drawable.Drawable
+import de.kuschku.libquassel.protocol.MsgId
+import org.threeten.bp.Instant
+
+data class NotificationMessage(
+  val messageId: MsgId,
+  val sender: CharSequence,
+  val content: CharSequence,
+  val time: Instant,
+  val avatar: Drawable?
+)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt b/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt
index a75a08ecf8775e2eabd9b0534ea1ad323e6870b4..b59d49ef698b480e2fd91e3d3ad3ecf62d181410 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/QuasseldroidNotificationManager.kt
@@ -28,17 +28,25 @@ import android.app.NotificationManager
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
 import android.os.Build
 import android.support.v4.app.NotificationCompat
 import android.support.v4.app.NotificationManagerCompat
+import android.support.v4.app.RemoteInput
+import de.kuschku.libquassel.protocol.Buffer_Type
+import de.kuschku.libquassel.quassel.BufferInfo
+import de.kuschku.libquassel.util.flag.hasFlag
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.service.QuasselService
 import de.kuschku.quasseldroid.ui.chat.ChatActivity
-import de.kuschku.quasseldroid.util.helper.editApply
 import de.kuschku.quasseldroid.util.helper.getColorCompat
-import de.kuschku.quasseldroid.util.helper.sharedPreferences
+import javax.inject.Inject
+
+class QuasseldroidNotificationManager @Inject constructor(private val context: Context) {
+  private val notificationManagerCompat = NotificationManagerCompat.from(context)
 
-class QuasseldroidNotificationManager(private val context: Context) {
   fun init() {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
       prepareChannels()
@@ -63,19 +71,107 @@ class QuasseldroidNotificationManager(private val context: Context) {
     )
   }
 
-  private fun id(): Int = context.sharedPreferences {
-    val key = context.getString(R.string.preference_notification_id_key)
-    val id = getInt(key, 1) + 1
-    editApply {
-      putInt(key, id)
-    }
-    id
+  private fun bitmapFromDrawable(drawable: Drawable): Bitmap {
+    val bitmap = Bitmap.createBitmap(
+      context.resources.getDimensionPixelSize(R.dimen.notification_avatar_width),
+      context.resources.getDimensionPixelSize(R.dimen.notification_avatar_height),
+      Bitmap.Config.ARGB_8888
+    )
+
+    val canvas = Canvas(bitmap)
+    drawable.setBounds(0, 0, canvas.width, canvas.height)
+    drawable.draw(canvas)
+    return bitmap
+  }
+
+  fun notificationGroup(bufferInfo: BufferInfo, notifications: List<NotificationMessage>): Handle {
+    val pendingIntentOpen = PendingIntent.getActivity(
+      context.applicationContext,
+      System.currentTimeMillis().toInt(),
+      ChatActivity.intent(context.applicationContext).apply {
+        flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+      },
+      0
+    )
+
+    val remoteInput = RemoteInput.Builder("reply_content")
+      .setLabel("Reply")
+      .build()
+
+    val replyPendingIntent = PendingIntent.getService(
+      context.applicationContext,
+      System.currentTimeMillis().toInt(),
+      QuasselService.intent(
+        context,
+        bufferId = bufferInfo.bufferId,
+        markReadMessage = notifications.last().messageId
+      ),
+      0
+    )
+
+    val markReadPendingIntent = PendingIntent.getService(
+      context.applicationContext,
+      System.currentTimeMillis().toInt(),
+      QuasselService.intent(
+        context,
+        bufferId = bufferInfo.bufferId,
+        markReadMessage = notifications.last().messageId
+      ),
+      0
+    )
+
+    val deletePendingIntent = PendingIntent.getService(
+      context.applicationContext,
+      System.currentTimeMillis().toInt(),
+      QuasselService.intent(
+        context,
+        bufferId = bufferInfo.bufferId,
+        markReadMessage = notifications.last().messageId
+      ),
+      0
+    )
+
+    val notification = NotificationCompat.Builder(
+      context.applicationContext,
+      context.getString(R.string.notification_channel_highlight)
+    )
+      .setContentIntent(pendingIntentOpen)
+      .setDeleteIntent(deletePendingIntent)
+      .setSmallIcon(R.mipmap.ic_logo)
+      .setColor(context.getColorCompat(R.color.colorPrimary))
+      .setPriority(NotificationCompat.PRIORITY_HIGH)
+      .setStyle(NotificationCompat.MessagingStyle("")
+                  .setConversationTitle(bufferInfo.bufferName)
+                  .also {
+                    for (notification in notifications) {
+                      it.addMessage(
+                        notification.content,
+                        notification.time.toEpochMilli(),
+                        notification.sender
+                      )
+                    }
+                  }
+      )
+      .addAction(0, "Mark Read", markReadPendingIntent)
+      .addAction(
+        NotificationCompat.Action.Builder(0, "Reply", replyPendingIntent)
+          .addRemoteInput(remoteInput)
+          .build()
+      )
+      .apply {
+        if (bufferInfo.type.hasFlag(Buffer_Type.QueryBuffer)) {
+          notifications.lastOrNull()?.avatar?.let {
+            setLargeIcon(bitmapFromDrawable(it))
+          }
+        }
+      }
+    return Handle(bufferInfo.bufferId, notification)
   }
 
   fun notificationBackground(): Handle {
     val pendingIntentOpen = PendingIntent.getActivity(
       context.applicationContext,
-      0,
+      System.currentTimeMillis().toInt(),
       ChatActivity.intent(context.applicationContext).apply {
         flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
       },
@@ -84,9 +180,9 @@ class QuasseldroidNotificationManager(private val context: Context) {
 
     val pendingIntentDisconnect = PendingIntent.getService(
       context,
-      0,
+      System.currentTimeMillis().toInt(),
       QuasselService.intent(context.applicationContext, disconnect = true),
-      PendingIntent.FLAG_UPDATE_CURRENT
+      0
     )
 
     val notification = NotificationCompat.Builder(
@@ -103,11 +199,15 @@ class QuasseldroidNotificationManager(private val context: Context) {
   }
 
   fun notify(handle: Handle) {
-    NotificationManagerCompat.from(context).notify(handle.id, handle.builder.build())
+    notificationManagerCompat.notify(handle.id, handle.builder.build())
   }
 
   fun remove(handle: Handle) {
-    NotificationManagerCompat.from(context).cancel(handle.id)
+    notificationManagerCompat.cancel(handle.id)
+  }
+
+  fun remove(id: Int) {
+    notificationManagerCompat.cancel(id)
   }
 
   companion object {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt
index b869d97033a0e4c7afc0fa13742c268529b9f827..f1ca02767edebb2a47fb032b5c7a34a18d62c10b 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/ContentFormatter.kt
@@ -22,15 +22,24 @@
 
 package de.kuschku.quasseldroid.util.irc.format
 
+import android.content.Context
+import android.graphics.Typeface
 import android.text.SpannableString
 import android.text.Spanned
 import android.text.TextPaint
+import android.text.style.ForegroundColorSpan
+import android.text.style.StyleSpan
 import android.text.style.URLSpan
+import de.kuschku.libquassel.util.IrcUserUtils
+import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.MessageSettings
+import de.kuschku.quasseldroid.util.helper.styledAttributes
+import de.kuschku.quasseldroid.util.ui.SpanFormatter
 import org.intellij.lang.annotations.Language
 import javax.inject.Inject
 
 class ContentFormatter @Inject constructor(
+  private val context: Context,
   private val ircFormatDeserializer: IrcFormatDeserializer,
   private val messageSettings: MessageSettings
 ) {
@@ -53,6 +62,17 @@ class ContentFormatter @Inject constructor(
     RegexOption.IGNORE_CASE
   )
 
+  private val senderColors: IntArray = context.theme.styledAttributes(
+    R.attr.senderColor0, R.attr.senderColor1, R.attr.senderColor2, R.attr.senderColor3,
+    R.attr.senderColor4, R.attr.senderColor5, R.attr.senderColor6, R.attr.senderColor7,
+    R.attr.senderColor8, R.attr.senderColor9, R.attr.senderColorA, R.attr.senderColorB,
+    R.attr.senderColorC, R.attr.senderColorD, R.attr.senderColorE, R.attr.senderColorF
+  ) {
+    IntArray(16) {
+      getColor(it, 0)
+    }
+  }
+
   class QuasselURLSpan(text: String, private val highlight: Boolean) : URLSpan(text) {
     override fun updateDrawState(ds: TextPaint?) {
       if (ds != null) {
@@ -63,7 +83,7 @@ class ContentFormatter @Inject constructor(
     }
   }
 
-  fun format(content: String, highlight: Boolean = false): CharSequence {
+  fun formatContent(content: String, highlight: Boolean = false): CharSequence {
     val formattedText = ircFormatDeserializer.formatString(content, messageSettings.colorizeMirc)
     val text = SpannableString(formattedText)
 
@@ -84,4 +104,55 @@ class ContentFormatter @Inject constructor(
 
     return text
   }
+
+  private fun formatNickNickImpl(nick: String, colorize: Boolean,
+                                 senderColors: IntArray): CharSequence {
+    val spannableString = SpannableString(nick)
+    if (colorize) {
+      val senderColor = IrcUserUtils.senderColor(nick)
+      spannableString.setSpan(
+        ForegroundColorSpan(senderColors[(senderColor + senderColors.size) % senderColors.size]),
+        0,
+        nick.length,
+        SpannableString.SPAN_INCLUSIVE_EXCLUSIVE
+      )
+    }
+    spannableString.setSpan(
+      StyleSpan(Typeface.BOLD),
+      0,
+      nick.length,
+      SpannableString.SPAN_INCLUSIVE_EXCLUSIVE
+    )
+    return spannableString
+  }
+
+  private fun formatNickImpl(sender: String, colorize: Boolean, hostmask: Boolean,
+                             senderColors: IntArray): CharSequence {
+    val nick = IrcUserUtils.nick(sender)
+    val mask = IrcUserUtils.mask(sender)
+    val formattedNick = formatNickNickImpl(nick, colorize, senderColors)
+
+    return if (hostmask) {
+      SpanFormatter.format("%s (%s)", formattedNick, mask)
+    } else {
+      formattedNick
+    }
+  }
+
+  fun formatNick(sender: String, self: Boolean = false, highlight: Boolean = false,
+                 showHostmask: Boolean = false, senderColors: IntArray = this.senderColors) =
+    when (messageSettings.colorizeNicknames) {
+      MessageSettings.ColorizeNicknamesMode.ALL          ->
+        formatNickImpl(sender, !highlight, showHostmask, senderColors)
+      MessageSettings.ColorizeNicknamesMode.ALL_BUT_MINE ->
+        formatNickImpl(sender, !self && !highlight, showHostmask, senderColors)
+      MessageSettings.ColorizeNicknamesMode.NONE         ->
+        formatNickImpl(sender, false, showHostmask, senderColors)
+    }
+
+  fun formatPrefix(prefix: String) = when (messageSettings.showPrefix) {
+    MessageSettings.ShowPrefixMode.ALL     -> prefix
+    MessageSettings.ShowPrefixMode.HIGHEST -> prefix.substring(0, Math.min(prefix.length, 1))
+    MessageSettings.ShowPrefixMode.NONE    -> ""
+  }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt
index c986c7c9f4492e2cd464f768d2f512ed61a04e25..d666f98288e3e5c25411d1781cc6eb4f9da80ae5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt
@@ -26,7 +26,7 @@ import android.content.Context
 import android.text.SpannableStringBuilder
 import android.text.Spanned
 import de.kuschku.quasseldroid.R
-import de.kuschku.quasseldroid.util.helper.styledAttributes
+import de.kuschku.quasseldroid.util.helper.getColorCompat
 import de.kuschku.quasseldroid.util.irc.format.spans.*
 import javax.inject.Inject
 
@@ -35,37 +35,33 @@ import javax.inject.Inject
  * color and format codes
  */
 class IrcFormatDeserializer @Inject constructor(context: Context) {
-  val mircColors = context.theme.styledAttributes(
-    R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03,
-    R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07,
-    R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11,
-    R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15,
-    R.attr.mircColor16, R.attr.mircColor17, R.attr.mircColor18, R.attr.mircColor19,
-    R.attr.mircColor20, R.attr.mircColor21, R.attr.mircColor22, R.attr.mircColor23,
-    R.attr.mircColor24, R.attr.mircColor25, R.attr.mircColor26, R.attr.mircColor27,
-    R.attr.mircColor28, R.attr.mircColor29, R.attr.mircColor30, R.attr.mircColor31,
-    R.attr.mircColor32, R.attr.mircColor33, R.attr.mircColor34, R.attr.mircColor35,
-    R.attr.mircColor36, R.attr.mircColor37, R.attr.mircColor38, R.attr.mircColor39,
-    R.attr.mircColor40, R.attr.mircColor41, R.attr.mircColor42, R.attr.mircColor43,
-    R.attr.mircColor44, R.attr.mircColor45, R.attr.mircColor46, R.attr.mircColor47,
-    R.attr.mircColor48, R.attr.mircColor49, R.attr.mircColor50, R.attr.mircColor51,
-    R.attr.mircColor52, R.attr.mircColor53, R.attr.mircColor54, R.attr.mircColor55,
-    R.attr.mircColor56, R.attr.mircColor57, R.attr.mircColor58, R.attr.mircColor59,
-    R.attr.mircColor60, R.attr.mircColor61, R.attr.mircColor62, R.attr.mircColor63,
-    R.attr.mircColor64, R.attr.mircColor65, R.attr.mircColor66, R.attr.mircColor67,
-    R.attr.mircColor68, R.attr.mircColor69, R.attr.mircColor70, R.attr.mircColor71,
-    R.attr.mircColor72, R.attr.mircColor73, R.attr.mircColor74, R.attr.mircColor75,
-    R.attr.mircColor76, R.attr.mircColor77, R.attr.mircColor78, R.attr.mircColor79,
-    R.attr.mircColor80, R.attr.mircColor81, R.attr.mircColor82, R.attr.mircColor83,
-    R.attr.mircColor84, R.attr.mircColor85, R.attr.mircColor86, R.attr.mircColor87,
-    R.attr.mircColor88, R.attr.mircColor89, R.attr.mircColor90, R.attr.mircColor91,
-    R.attr.mircColor92, R.attr.mircColor93, R.attr.mircColor94, R.attr.mircColor95,
-    R.attr.mircColor96, R.attr.mircColor97, R.attr.mircColor98
-  ) {
-    IntArray(99) {
-      getColor(it, 0)
-    }
-  }
+  val mircColors = listOf(
+    R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03,
+    R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07,
+    R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11,
+    R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15,
+    R.color.mircColor16, R.color.mircColor17, R.color.mircColor18, R.color.mircColor19,
+    R.color.mircColor20, R.color.mircColor21, R.color.mircColor22, R.color.mircColor23,
+    R.color.mircColor24, R.color.mircColor25, R.color.mircColor26, R.color.mircColor27,
+    R.color.mircColor28, R.color.mircColor29, R.color.mircColor30, R.color.mircColor31,
+    R.color.mircColor32, R.color.mircColor33, R.color.mircColor34, R.color.mircColor35,
+    R.color.mircColor36, R.color.mircColor37, R.color.mircColor38, R.color.mircColor39,
+    R.color.mircColor40, R.color.mircColor41, R.color.mircColor42, R.color.mircColor43,
+    R.color.mircColor44, R.color.mircColor45, R.color.mircColor46, R.color.mircColor47,
+    R.color.mircColor48, R.color.mircColor49, R.color.mircColor50, R.color.mircColor51,
+    R.color.mircColor52, R.color.mircColor53, R.color.mircColor54, R.color.mircColor55,
+    R.color.mircColor56, R.color.mircColor57, R.color.mircColor58, R.color.mircColor59,
+    R.color.mircColor60, R.color.mircColor61, R.color.mircColor62, R.color.mircColor63,
+    R.color.mircColor64, R.color.mircColor65, R.color.mircColor66, R.color.mircColor67,
+    R.color.mircColor68, R.color.mircColor69, R.color.mircColor70, R.color.mircColor71,
+    R.color.mircColor72, R.color.mircColor73, R.color.mircColor74, R.color.mircColor75,
+    R.color.mircColor76, R.color.mircColor77, R.color.mircColor78, R.color.mircColor79,
+    R.color.mircColor80, R.color.mircColor81, R.color.mircColor82, R.color.mircColor83,
+    R.color.mircColor84, R.color.mircColor85, R.color.mircColor86, R.color.mircColor87,
+    R.color.mircColor88, R.color.mircColor89, R.color.mircColor90, R.color.mircColor91,
+    R.color.mircColor92, R.color.mircColor93, R.color.mircColor94, R.color.mircColor95,
+    R.color.mircColor96, R.color.mircColor97, R.color.mircColor98
+  ).map(context::getColorCompat).toIntArray()
 
   /**
    * Function to handle mIRC formatted strings
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt
index 897b736309dee086d4e5976ce8ab3da8430b80f7..952d589cda41a3225797dd69e214fd0ef4efbc36 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatSerializer.kt
@@ -27,42 +27,39 @@ import android.graphics.Typeface
 import android.text.Spanned
 import android.text.style.*
 import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.util.helper.getColorCompat
 import de.kuschku.quasseldroid.util.helper.styledAttributes
 import java.util.*
 import javax.inject.Inject
 
 class IrcFormatSerializer @Inject constructor(context: Context) {
-  private val mircColors = context.theme.styledAttributes(
-    R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03,
-    R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07,
-    R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11,
-    R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15,
-    R.attr.mircColor16, R.attr.mircColor17, R.attr.mircColor18, R.attr.mircColor19,
-    R.attr.mircColor20, R.attr.mircColor21, R.attr.mircColor22, R.attr.mircColor23,
-    R.attr.mircColor24, R.attr.mircColor25, R.attr.mircColor26, R.attr.mircColor27,
-    R.attr.mircColor28, R.attr.mircColor29, R.attr.mircColor30, R.attr.mircColor31,
-    R.attr.mircColor32, R.attr.mircColor33, R.attr.mircColor34, R.attr.mircColor35,
-    R.attr.mircColor36, R.attr.mircColor37, R.attr.mircColor38, R.attr.mircColor39,
-    R.attr.mircColor40, R.attr.mircColor41, R.attr.mircColor42, R.attr.mircColor43,
-    R.attr.mircColor44, R.attr.mircColor45, R.attr.mircColor46, R.attr.mircColor47,
-    R.attr.mircColor48, R.attr.mircColor49, R.attr.mircColor50, R.attr.mircColor51,
-    R.attr.mircColor52, R.attr.mircColor53, R.attr.mircColor54, R.attr.mircColor55,
-    R.attr.mircColor56, R.attr.mircColor57, R.attr.mircColor58, R.attr.mircColor59,
-    R.attr.mircColor60, R.attr.mircColor61, R.attr.mircColor62, R.attr.mircColor63,
-    R.attr.mircColor64, R.attr.mircColor65, R.attr.mircColor66, R.attr.mircColor67,
-    R.attr.mircColor68, R.attr.mircColor69, R.attr.mircColor70, R.attr.mircColor71,
-    R.attr.mircColor72, R.attr.mircColor73, R.attr.mircColor74, R.attr.mircColor75,
-    R.attr.mircColor76, R.attr.mircColor77, R.attr.mircColor78, R.attr.mircColor79,
-    R.attr.mircColor80, R.attr.mircColor81, R.attr.mircColor82, R.attr.mircColor83,
-    R.attr.mircColor84, R.attr.mircColor85, R.attr.mircColor86, R.attr.mircColor87,
-    R.attr.mircColor88, R.attr.mircColor89, R.attr.mircColor90, R.attr.mircColor91,
-    R.attr.mircColor92, R.attr.mircColor93, R.attr.mircColor94, R.attr.mircColor95,
-    R.attr.mircColor96, R.attr.mircColor97, R.attr.mircColor98
-  ) {
-    IntArray(99) {
-      getColor(it, 0)
-    }
-  }
+  val mircColors = listOf(
+    R.color.mircColor00, R.color.mircColor01, R.color.mircColor02, R.color.mircColor03,
+    R.color.mircColor04, R.color.mircColor05, R.color.mircColor06, R.color.mircColor07,
+    R.color.mircColor08, R.color.mircColor09, R.color.mircColor10, R.color.mircColor11,
+    R.color.mircColor12, R.color.mircColor13, R.color.mircColor14, R.color.mircColor15,
+    R.color.mircColor16, R.color.mircColor17, R.color.mircColor18, R.color.mircColor19,
+    R.color.mircColor20, R.color.mircColor21, R.color.mircColor22, R.color.mircColor23,
+    R.color.mircColor24, R.color.mircColor25, R.color.mircColor26, R.color.mircColor27,
+    R.color.mircColor28, R.color.mircColor29, R.color.mircColor30, R.color.mircColor31,
+    R.color.mircColor32, R.color.mircColor33, R.color.mircColor34, R.color.mircColor35,
+    R.color.mircColor36, R.color.mircColor37, R.color.mircColor38, R.color.mircColor39,
+    R.color.mircColor40, R.color.mircColor41, R.color.mircColor42, R.color.mircColor43,
+    R.color.mircColor44, R.color.mircColor45, R.color.mircColor46, R.color.mircColor47,
+    R.color.mircColor48, R.color.mircColor49, R.color.mircColor50, R.color.mircColor51,
+    R.color.mircColor52, R.color.mircColor53, R.color.mircColor54, R.color.mircColor55,
+    R.color.mircColor56, R.color.mircColor57, R.color.mircColor58, R.color.mircColor59,
+    R.color.mircColor60, R.color.mircColor61, R.color.mircColor62, R.color.mircColor63,
+    R.color.mircColor64, R.color.mircColor65, R.color.mircColor66, R.color.mircColor67,
+    R.color.mircColor68, R.color.mircColor69, R.color.mircColor70, R.color.mircColor71,
+    R.color.mircColor72, R.color.mircColor73, R.color.mircColor74, R.color.mircColor75,
+    R.color.mircColor76, R.color.mircColor77, R.color.mircColor78, R.color.mircColor79,
+    R.color.mircColor80, R.color.mircColor81, R.color.mircColor82, R.color.mircColor83,
+    R.color.mircColor84, R.color.mircColor85, R.color.mircColor86, R.color.mircColor87,
+    R.color.mircColor88, R.color.mircColor89, R.color.mircColor90, R.color.mircColor91,
+    R.color.mircColor92, R.color.mircColor93, R.color.mircColor94, R.color.mircColor95,
+    R.color.mircColor96, R.color.mircColor97, R.color.mircColor98
+  ).map(context::getColorCompat).toIntArray()
 
   private val mircColorMap = mircColors.take(16).mapIndexed { index: Int, color: Int ->
     color to index
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 69aeb9d267b3c9f480e7704fe9757102c40329bb..8483fc96c565596d28ef714a2b86f41c8af7ef19 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -39,107 +39,6 @@
   <attr name="senderColorE" format="color" />
   <attr name="senderColorF" format="color" />
 
-  <!-- mirc colors -->
-  <attr name="mircColor00" format="color" />
-  <attr name="mircColor01" format="color" />
-  <attr name="mircColor02" format="color" />
-  <attr name="mircColor03" format="color" />
-  <attr name="mircColor04" format="color" />
-  <attr name="mircColor05" format="color" />
-  <attr name="mircColor06" format="color" />
-  <attr name="mircColor07" format="color" />
-  <attr name="mircColor08" format="color" />
-  <attr name="mircColor09" format="color" />
-  <attr name="mircColor10" format="color" />
-  <attr name="mircColor11" format="color" />
-  <attr name="mircColor12" format="color" />
-  <attr name="mircColor13" format="color" />
-  <attr name="mircColor14" format="color" />
-  <attr name="mircColor15" format="color" />
-  <attr name="mircColor16" format="color" />
-  <attr name="mircColor17" format="color" />
-  <attr name="mircColor18" format="color" />
-  <attr name="mircColor19" format="color" />
-  <attr name="mircColor20" format="color" />
-  <attr name="mircColor21" format="color" />
-  <attr name="mircColor22" format="color" />
-  <attr name="mircColor23" format="color" />
-  <attr name="mircColor24" format="color" />
-  <attr name="mircColor25" format="color" />
-  <attr name="mircColor26" format="color" />
-  <attr name="mircColor27" format="color" />
-  <attr name="mircColor28" format="color" />
-  <attr name="mircColor29" format="color" />
-  <attr name="mircColor30" format="color" />
-  <attr name="mircColor31" format="color" />
-  <attr name="mircColor32" format="color" />
-  <attr name="mircColor33" format="color" />
-  <attr name="mircColor34" format="color" />
-  <attr name="mircColor35" format="color" />
-  <attr name="mircColor36" format="color" />
-  <attr name="mircColor37" format="color" />
-  <attr name="mircColor38" format="color" />
-  <attr name="mircColor39" format="color" />
-  <attr name="mircColor40" format="color" />
-  <attr name="mircColor41" format="color" />
-  <attr name="mircColor42" format="color" />
-  <attr name="mircColor43" format="color" />
-  <attr name="mircColor44" format="color" />
-  <attr name="mircColor45" format="color" />
-  <attr name="mircColor46" format="color" />
-  <attr name="mircColor47" format="color" />
-  <attr name="mircColor48" format="color" />
-  <attr name="mircColor49" format="color" />
-  <attr name="mircColor50" format="color" />
-  <attr name="mircColor51" format="color" />
-  <attr name="mircColor52" format="color" />
-  <attr name="mircColor53" format="color" />
-  <attr name="mircColor54" format="color" />
-  <attr name="mircColor55" format="color" />
-  <attr name="mircColor56" format="color" />
-  <attr name="mircColor57" format="color" />
-  <attr name="mircColor58" format="color" />
-  <attr name="mircColor59" format="color" />
-  <attr name="mircColor60" format="color" />
-  <attr name="mircColor61" format="color" />
-  <attr name="mircColor62" format="color" />
-  <attr name="mircColor63" format="color" />
-  <attr name="mircColor64" format="color" />
-  <attr name="mircColor65" format="color" />
-  <attr name="mircColor66" format="color" />
-  <attr name="mircColor67" format="color" />
-  <attr name="mircColor68" format="color" />
-  <attr name="mircColor69" format="color" />
-  <attr name="mircColor70" format="color" />
-  <attr name="mircColor71" format="color" />
-  <attr name="mircColor72" format="color" />
-  <attr name="mircColor73" format="color" />
-  <attr name="mircColor74" format="color" />
-  <attr name="mircColor75" format="color" />
-  <attr name="mircColor76" format="color" />
-  <attr name="mircColor77" format="color" />
-  <attr name="mircColor78" format="color" />
-  <attr name="mircColor79" format="color" />
-  <attr name="mircColor80" format="color" />
-  <attr name="mircColor81" format="color" />
-  <attr name="mircColor82" format="color" />
-  <attr name="mircColor83" format="color" />
-  <attr name="mircColor84" format="color" />
-  <attr name="mircColor85" format="color" />
-  <attr name="mircColor86" format="color" />
-  <attr name="mircColor87" format="color" />
-  <attr name="mircColor88" format="color" />
-  <attr name="mircColor89" format="color" />
-  <attr name="mircColor90" format="color" />
-  <attr name="mircColor91" format="color" />
-  <attr name="mircColor92" format="color" />
-  <attr name="mircColor93" format="color" />
-  <attr name="mircColor94" format="color" />
-  <attr name="mircColor95" format="color" />
-  <attr name="mircColor96" format="color" />
-  <attr name="mircColor97" format="color" />
-  <attr name="mircColor98" format="color" />
-
   <!-- Background and foreground colors for UI -->
   <attr name="colorForeground" format="color" />
   <attr name="colorForegroundHighlight" format="color" />
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 58bf27b77dddaf113a7a9613bd668a55d35d8412..994f06dc2c88edcff675fba9a908b7c37240ce26 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -39,4 +39,135 @@
 
   <color name="ripple_dark">#33ffffff</color>
   <color name="ripple_light">#1f000000</color>
+
+  <color name="senderColor0">#F44336</color>
+  <color name="senderColor1">#2196F3</color>
+  <color name="senderColor2">#7CB342</color>
+  <color name="senderColor3">#7B1FA2</color>
+  <color name="senderColor4">#DA8E00</color>
+  <color name="senderColor5">#4CAF50</color>
+  <color name="senderColor6">#3F51B5</color>
+  <color name="senderColor7">#E91E63</color>
+  <color name="senderColor8">#b94600</color>
+  <color name="senderColor9">#9E9D24</color>
+  <color name="senderColorA">#558B2F</color>
+  <color name="senderColorB">#009688</color>
+  <color name="senderColorC">#0277BD</color>
+  <color name="senderColorD">#00838F</color>
+  <color name="senderColorE">#9C27B0</color>
+  <color name="senderColorF">#C51162</color>
+
+  <!-- mirc colors -->
+  <color name="mircColor00">#ffffff</color>
+  <color name="mircColor01">#000000</color>
+  <color name="mircColor02">#000080</color>
+  <color name="mircColor03">#008000</color>
+  <color name="mircColor04">#ff0000</color>
+  <color name="mircColor05">#800000</color>
+  <color name="mircColor06">#800080</color>
+  <color name="mircColor07">#ffa500</color>
+  <color name="mircColor08">#ffff00</color>
+  <color name="mircColor09">#00ff00</color>
+  <color name="mircColor10">#008080</color>
+  <color name="mircColor11">#00ffff</color>
+  <color name="mircColor12">#4169e1</color>
+  <color name="mircColor13">#ff00ff</color>
+  <color name="mircColor14">#808080</color>
+  <color name="mircColor15">#c0c0c0</color>
+
+  <color name="mircColor16">#470000</color>
+  <color name="mircColor28">#740000</color>
+  <color name="mircColor40">#b50000</color>
+  <color name="mircColor52">#ff0000</color>
+  <color name="mircColor64">#ff5959</color>
+  <color name="mircColor76">#ff9c9c</color>
+
+  <color name="mircColor17">#472100</color>
+  <color name="mircColor29">#743a00</color>
+  <color name="mircColor41">#b56300</color>
+  <color name="mircColor53">#ff8c00</color>
+  <color name="mircColor65">#ffb459</color>
+  <color name="mircColor77">#ffd39c</color>
+
+  <color name="mircColor18">#474700</color>
+  <color name="mircColor30">#747400</color>
+  <color name="mircColor42">#b5b500</color>
+  <color name="mircColor54">#ffff00</color>
+  <color name="mircColor66">#ffff71</color>
+  <color name="mircColor78">#ffff9c</color>
+
+  <color name="mircColor19">#324700</color>
+  <color name="mircColor31">#517400</color>
+  <color name="mircColor43">#7db500</color>
+  <color name="mircColor55">#b2ff00</color>
+  <color name="mircColor67">#cfff60</color>
+  <color name="mircColor79">#e2ff9c</color>
+
+  <color name="mircColor20">#004700</color>
+  <color name="mircColor32">#007400</color>
+  <color name="mircColor44">#00b500</color>
+  <color name="mircColor56">#00ff00</color>
+  <color name="mircColor68">#6fff6f</color>
+  <color name="mircColor80">#9cff9c</color>
+
+  <color name="mircColor21">#00472c</color>
+  <color name="mircColor33">#007449</color>
+  <color name="mircColor45">#00b571</color>
+  <color name="mircColor57">#00ffa0</color>
+  <color name="mircColor69">#65ffc9</color>
+  <color name="mircColor81">#9cffdb</color>
+
+  <color name="mircColor22">#004747</color>
+  <color name="mircColor34">#007474</color>
+  <color name="mircColor46">#00b5b5</color>
+  <color name="mircColor58">#00ffff</color>
+  <color name="mircColor70">#6dffff</color>
+  <color name="mircColor82">#9cffff</color>
+
+  <color name="mircColor23">#002747</color>
+  <color name="mircColor35">#004074</color>
+  <color name="mircColor47">#0063b5</color>
+  <color name="mircColor59">#008cff</color>
+  <color name="mircColor71">#59b4ff</color>
+  <color name="mircColor83">#9cd3ff</color>
+
+  <color name="mircColor24">#000047</color>
+  <color name="mircColor36">#000074</color>
+  <color name="mircColor48">#0000b5</color>
+  <color name="mircColor60">#0000ff</color>
+  <color name="mircColor72">#5959ff</color>
+  <color name="mircColor84">#9c9cff</color>
+
+  <color name="mircColor25">#2e0047</color>
+  <color name="mircColor37">#4b0074</color>
+  <color name="mircColor49">#7500b5</color>
+  <color name="mircColor61">#a500ff</color>
+  <color name="mircColor73">#c459ff</color>
+  <color name="mircColor85">#dc9cff</color>
+
+  <color name="mircColor26">#470047</color>
+  <color name="mircColor38">#740074</color>
+  <color name="mircColor50">#b500b5</color>
+  <color name="mircColor62">#ff00ff</color>
+  <color name="mircColor74">#ff66ff</color>
+  <color name="mircColor86">#ff9cff</color>
+
+  <color name="mircColor27">#47002a</color>
+  <color name="mircColor39">#740045</color>
+  <color name="mircColor51">#b5006b</color>
+  <color name="mircColor63">#ff0098</color>
+  <color name="mircColor75">#ff59bc</color>
+  <color name="mircColor87">#ff94d3</color>
+
+  <color name="mircColor88">#000000</color>
+  <color name="mircColor89">#131313</color>
+  <color name="mircColor90">#282828</color>
+  <color name="mircColor91">#363636</color>
+  <color name="mircColor92">#4d4d4d</color>
+  <color name="mircColor93">#656565</color>
+  <color name="mircColor94">#818181</color>
+  <color name="mircColor95">#9f9f9f</color>
+  <color name="mircColor96">#bcbcbc</color>
+  <color name="mircColor97">#e2e2e2</color>
+  <color name="mircColor98">#ffffff</color>
 </resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 7bb53ff86389c18c044e5c65a9deea5209422dfe..9fd5754e9881ef691b011c633525c1c73d5f22bf 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -37,4 +37,7 @@
 
   <dimen name="colorchooser_circlesize">56dp</dimen>
   <dimen name="avatar_size">35sp</dimen>
+
+  <dimen name="notification_avatar_width">64dp</dimen>
+  <dimen name="notification_avatar_height">64dp</dimen>
 </resources>
diff --git a/app/src/main/res/values/themes_base.xml b/app/src/main/res/values/themes_base.xml
index 4b7973c1a3d388e7876a4f82cc194ec76a978d13..18b370fb81c5a7d7c3dac4e9876e854a3dbe6a05 100644
--- a/app/src/main/res/values/themes_base.xml
+++ b/app/src/main/res/values/themes_base.xml
@@ -91,119 +91,6 @@
     <item name="colorAway">@color/colorAwayDark</item>
 
     <item name="cardStyle">@style/CardView.Dark</item>
-
-    <item name="mircColor00">#ffffff</item>
-    <item name="mircColor01">#000000</item>
-    <item name="mircColor02">#000080</item>
-    <item name="mircColor03">#008000</item>
-    <item name="mircColor04">#ff0000</item>
-    <item name="mircColor05">#800000</item>
-    <item name="mircColor06">#800080</item>
-    <item name="mircColor07">#ffa500</item>
-    <item name="mircColor08">#ffff00</item>
-    <item name="mircColor09">#00ff00</item>
-    <item name="mircColor10">#008080</item>
-    <item name="mircColor11">#00ffff</item>
-    <item name="mircColor12">#4169e1</item>
-    <item name="mircColor13">#ff00ff</item>
-    <item name="mircColor14">#808080</item>
-    <item name="mircColor15">#c0c0c0</item>
-
-    <item name="mircColor16">#470000</item>
-    <item name="mircColor28">#740000</item>
-    <item name="mircColor40">#b50000</item>
-    <item name="mircColor52">#ff0000</item>
-    <item name="mircColor64">#ff5959</item>
-    <item name="mircColor76">#ff9c9c</item>
-
-    <item name="mircColor17">#472100</item>
-    <item name="mircColor29">#743a00</item>
-    <item name="mircColor41">#b56300</item>
-    <item name="mircColor53">#ff8c00</item>
-    <item name="mircColor65">#ffb459</item>
-    <item name="mircColor77">#ffd39c</item>
-
-    <item name="mircColor18">#474700</item>
-    <item name="mircColor30">#747400</item>
-    <item name="mircColor42">#b5b500</item>
-    <item name="mircColor54">#ffff00</item>
-    <item name="mircColor66">#ffff71</item>
-    <item name="mircColor78">#ffff9c</item>
-
-    <item name="mircColor19">#324700</item>
-    <item name="mircColor31">#517400</item>
-    <item name="mircColor43">#7db500</item>
-    <item name="mircColor55">#b2ff00</item>
-    <item name="mircColor67">#cfff60</item>
-    <item name="mircColor79">#e2ff9c</item>
-
-    <item name="mircColor20">#004700</item>
-    <item name="mircColor32">#007400</item>
-    <item name="mircColor44">#00b500</item>
-    <item name="mircColor56">#00ff00</item>
-    <item name="mircColor68">#6fff6f</item>
-    <item name="mircColor80">#9cff9c</item>
-
-    <item name="mircColor21">#00472c</item>
-    <item name="mircColor33">#007449</item>
-    <item name="mircColor45">#00b571</item>
-    <item name="mircColor57">#00ffa0</item>
-    <item name="mircColor69">#65ffc9</item>
-    <item name="mircColor81">#9cffdb</item>
-
-    <item name="mircColor22">#004747</item>
-    <item name="mircColor34">#007474</item>
-    <item name="mircColor46">#00b5b5</item>
-    <item name="mircColor58">#00ffff</item>
-    <item name="mircColor70">#6dffff</item>
-    <item name="mircColor82">#9cffff</item>
-
-    <item name="mircColor23">#002747</item>
-    <item name="mircColor35">#004074</item>
-    <item name="mircColor47">#0063b5</item>
-    <item name="mircColor59">#008cff</item>
-    <item name="mircColor71">#59b4ff</item>
-    <item name="mircColor83">#9cd3ff</item>
-
-    <item name="mircColor24">#000047</item>
-    <item name="mircColor36">#000074</item>
-    <item name="mircColor48">#0000b5</item>
-    <item name="mircColor60">#0000ff</item>
-    <item name="mircColor72">#5959ff</item>
-    <item name="mircColor84">#9c9cff</item>
-
-    <item name="mircColor25">#2e0047</item>
-    <item name="mircColor37">#4b0074</item>
-    <item name="mircColor49">#7500b5</item>
-    <item name="mircColor61">#a500ff</item>
-    <item name="mircColor73">#c459ff</item>
-    <item name="mircColor85">#dc9cff</item>
-
-    <item name="mircColor26">#470047</item>
-    <item name="mircColor38">#740074</item>
-    <item name="mircColor50">#b500b5</item>
-    <item name="mircColor62">#ff00ff</item>
-    <item name="mircColor74">#ff66ff</item>
-    <item name="mircColor86">#ff9cff</item>
-
-    <item name="mircColor27">#47002a</item>
-    <item name="mircColor39">#740045</item>
-    <item name="mircColor51">#b5006b</item>
-    <item name="mircColor63">#ff0098</item>
-    <item name="mircColor75">#ff59bc</item>
-    <item name="mircColor87">#ff94d3</item>
-
-    <item name="mircColor88">#000000</item>
-    <item name="mircColor89">#131313</item>
-    <item name="mircColor90">#282828</item>
-    <item name="mircColor91">#363636</item>
-    <item name="mircColor92">#4d4d4d</item>
-    <item name="mircColor93">#656565</item>
-    <item name="mircColor94">#818181</item>
-    <item name="mircColor95">#9f9f9f</item>
-    <item name="mircColor96">#bcbcbc</item>
-    <item name="mircColor97">#e2e2e2</item>
-    <item name="mircColor98">#ffffff</item>
   </style>
 
   <style name="Theme.ChatTheme.Auto" parent="Theme.ChatTheme">
@@ -238,119 +125,6 @@
     <item name="colorAway">@color/colorAwayLight</item>
 
     <item name="cardStyle">@style/CardView.Light</item>
-
-    <item name="mircColor00">#ffffff</item>
-    <item name="mircColor01">#000000</item>
-    <item name="mircColor02">#000080</item>
-    <item name="mircColor03">#008000</item>
-    <item name="mircColor04">#ff0000</item>
-    <item name="mircColor05">#800000</item>
-    <item name="mircColor06">#800080</item>
-    <item name="mircColor07">#ffa500</item>
-    <item name="mircColor08">#ffff00</item>
-    <item name="mircColor09">#00ff00</item>
-    <item name="mircColor10">#008080</item>
-    <item name="mircColor11">#00ffff</item>
-    <item name="mircColor12">#4169e1</item>
-    <item name="mircColor13">#ff00ff</item>
-    <item name="mircColor14">#808080</item>
-    <item name="mircColor15">#c0c0c0</item>
-
-    <item name="mircColor16">#470000</item>
-    <item name="mircColor28">#740000</item>
-    <item name="mircColor40">#b50000</item>
-    <item name="mircColor52">#ff0000</item>
-    <item name="mircColor64">#ff5959</item>
-    <item name="mircColor76">#ff9c9c</item>
-
-    <item name="mircColor17">#472100</item>
-    <item name="mircColor29">#743a00</item>
-    <item name="mircColor41">#b56300</item>
-    <item name="mircColor53">#ff8c00</item>
-    <item name="mircColor65">#ffb459</item>
-    <item name="mircColor77">#ffd39c</item>
-
-    <item name="mircColor18">#474700</item>
-    <item name="mircColor30">#747400</item>
-    <item name="mircColor42">#b5b500</item>
-    <item name="mircColor54">#ffff00</item>
-    <item name="mircColor66">#ffff71</item>
-    <item name="mircColor78">#ffff9c</item>
-
-    <item name="mircColor19">#324700</item>
-    <item name="mircColor31">#517400</item>
-    <item name="mircColor43">#7db500</item>
-    <item name="mircColor55">#b2ff00</item>
-    <item name="mircColor67">#cfff60</item>
-    <item name="mircColor79">#e2ff9c</item>
-
-    <item name="mircColor20">#004700</item>
-    <item name="mircColor32">#007400</item>
-    <item name="mircColor44">#00b500</item>
-    <item name="mircColor56">#00ff00</item>
-    <item name="mircColor68">#6fff6f</item>
-    <item name="mircColor80">#9cff9c</item>
-
-    <item name="mircColor21">#00472c</item>
-    <item name="mircColor33">#007449</item>
-    <item name="mircColor45">#00b571</item>
-    <item name="mircColor57">#00ffa0</item>
-    <item name="mircColor69">#65ffc9</item>
-    <item name="mircColor81">#9cffdb</item>
-
-    <item name="mircColor22">#004747</item>
-    <item name="mircColor34">#007474</item>
-    <item name="mircColor46">#00b5b5</item>
-    <item name="mircColor58">#00ffff</item>
-    <item name="mircColor70">#6dffff</item>
-    <item name="mircColor82">#9cffff</item>
-
-    <item name="mircColor23">#002747</item>
-    <item name="mircColor35">#004074</item>
-    <item name="mircColor47">#0063b5</item>
-    <item name="mircColor59">#008cff</item>
-    <item name="mircColor71">#59b4ff</item>
-    <item name="mircColor83">#9cd3ff</item>
-
-    <item name="mircColor24">#000047</item>
-    <item name="mircColor36">#000074</item>
-    <item name="mircColor48">#0000b5</item>
-    <item name="mircColor60">#0000ff</item>
-    <item name="mircColor72">#5959ff</item>
-    <item name="mircColor84">#9c9cff</item>
-
-    <item name="mircColor25">#2e0047</item>
-    <item name="mircColor37">#4b0074</item>
-    <item name="mircColor49">#7500b5</item>
-    <item name="mircColor61">#a500ff</item>
-    <item name="mircColor73">#c459ff</item>
-    <item name="mircColor85">#dc9cff</item>
-
-    <item name="mircColor26">#470047</item>
-    <item name="mircColor38">#740074</item>
-    <item name="mircColor50">#b500b5</item>
-    <item name="mircColor62">#ff00ff</item>
-    <item name="mircColor74">#ff66ff</item>
-    <item name="mircColor86">#ff9cff</item>
-
-    <item name="mircColor27">#47002a</item>
-    <item name="mircColor39">#740045</item>
-    <item name="mircColor51">#b5006b</item>
-    <item name="mircColor63">#ff0098</item>
-    <item name="mircColor75">#ff59bc</item>
-    <item name="mircColor87">#ff94d3</item>
-
-    <item name="mircColor88">#000000</item>
-    <item name="mircColor89">#131313</item>
-    <item name="mircColor90">#282828</item>
-    <item name="mircColor91">#363636</item>
-    <item name="mircColor92">#4d4d4d</item>
-    <item name="mircColor93">#656565</item>
-    <item name="mircColor94">#818181</item>
-    <item name="mircColor95">#9f9f9f</item>
-    <item name="mircColor96">#bcbcbc</item>
-    <item name="mircColor97">#e2e2e2</item>
-    <item name="mircColor98">#ffffff</item>
   </style>
 
   <style name="Theme.ChatTheme.Light.Auto" parent="Theme.ChatTheme.Light">
diff --git a/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt b/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt
index f186fa80f45be1bff3d6a70598f6e93dc9eaed2d..8c9984ad47d292b6ed9962be219a064f34261ae4 100644
--- a/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt
+++ b/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt
@@ -32,7 +32,7 @@ import org.threeten.bp.Instant
 class AvatarHelperTest {
   @Test
   fun testGravatarAvatars() {
-    val message = QuasselDatabase.DatabaseMessage(
+    val message = QuasselDatabase.MessageData(
       messageId = 1,
       time = Instant.now(),
       type = Message_Type.of(Message_Type.Plain).toInt(),
@@ -69,7 +69,7 @@ class AvatarHelperTest {
 
   @Test
   fun testIrcCloudAvatars() {
-    val message = QuasselDatabase.DatabaseMessage(
+    val message = QuasselDatabase.MessageData(
       messageId = 1,
       time = Instant.now(),
       type = Message_Type.of(Message_Type.Plain).toInt(),
@@ -106,7 +106,7 @@ class AvatarHelperTest {
 
   @Test
   fun testActualAvatars() {
-    val message = QuasselDatabase.DatabaseMessage(
+    val message = QuasselDatabase.MessageData(
       messageId = 1,
       time = Instant.now(),
       type = Message_Type.of(Message_Type.Plain).toInt(),
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
index cb222c566d6538ae3d5cca8374618d82e3568479..50410fcc3665cc0a490fc0cb96cb15321f989384 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
@@ -27,12 +27,14 @@ import de.kuschku.libquassel.protocol.Type
 import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.interfaces.IBufferSyncer
 import de.kuschku.libquassel.session.ISession
+import de.kuschku.libquassel.session.NotificationManager
 import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import io.reactivex.Observable
 import io.reactivex.subjects.BehaviorSubject
 
 class BufferSyncer constructor(
-  private val session: ISession
+  private val session: ISession,
+  private val notificationManager: NotificationManager?
 ) : SyncableObject(session.proxy, "BufferSyncer"), IBufferSyncer {
   fun lastSeenMsg(buffer: BufferId): MsgId = _lastSeenMsg[buffer] ?: 0
   fun liveLastSeenMsg(buffer: BufferId): Observable<MsgId> = live_lastSeenMsg.map {
@@ -165,11 +167,7 @@ class BufferSyncer constructor(
   }
 
   override fun mergeBuffersPermanently(buffer1: BufferId, buffer2: BufferId) {
-    _lastSeenMsg.remove(buffer2);live_lastSeenMsg.onNext(Unit)
-    _markerLines.remove(buffer2);live_markerLines.onNext(Unit)
-    _bufferActivities.remove(buffer2);live_bufferActivities.onNext(Unit)
-    _highlightCounts.remove(buffer2);live_highlightCounts.onNext(Unit)
-    _bufferInfos.remove(buffer2);live_bufferInfos.onNext(Unit)
+    removeBuffer(buffer2)
   }
 
   override fun removeBuffer(buffer: BufferId) {
@@ -179,6 +177,7 @@ class BufferSyncer constructor(
     _highlightCounts.remove(buffer);live_highlightCounts.onNext(Unit)
     _bufferInfos.remove(buffer);live_bufferInfos.onNext(Unit)
     session.backlogManager?.removeBuffer(buffer)
+    notificationManager?.clear(buffer)
   }
 
   override fun renameBuffer(buffer: BufferId, newName: String) {
@@ -210,6 +209,7 @@ class BufferSyncer constructor(
       _lastSeenMsg[buffer] = msgId
       live_lastSeenMsg.onNext(Unit)
       super.setLastSeenMsg(buffer, msgId)
+      notificationManager?.clear(buffer, msgId)
     }
   }
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt
index f9a31b3072720a4641039bac7dc900f5c1ba245b..c3ab526bf205b736f5578014637617a0dd689367 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt
@@ -28,13 +28,15 @@ import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.libquassel.quassel.syncables.interfaces.IRpcHandler
 import de.kuschku.libquassel.session.BacklogStorage
+import de.kuschku.libquassel.session.NotificationManager
 import de.kuschku.libquassel.session.Session
 import de.kuschku.libquassel.util.helpers.deserializeString
 import java.nio.ByteBuffer
 
 class RpcHandler(
   override val session: Session,
-  private val backlogStorage: BacklogStorage
+  private val backlogStorage: BacklogStorage,
+  private val notificationManager: NotificationManager?
 ) : IRpcHandler {
   override fun displayStatusMsg(net: String, msg: String) {
   }
@@ -61,6 +63,7 @@ class RpcHandler(
   override fun displayMsg(message: Message) {
     session.bufferSyncer.bufferInfoUpdated(message.bufferInfo)
     backlogStorage.storeMessages(session, message)
+    notificationManager?.processMessages(session, message)
   }
 
   override fun createIdentity(identity: Identity, additional: QVariantMap) =
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/NotificationManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/NotificationManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b6e4f40854d1bc6c0050f0235fbec11e72bbd9a9
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/session/NotificationManager.kt
@@ -0,0 +1,30 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2018 Janne Koschinski
+ * Copyright (c) 2018 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.libquassel.session
+
+import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.protocol.Message
+import de.kuschku.libquassel.protocol.MsgId
+
+interface NotificationManager {
+  fun init(session: Session)
+  fun processMessages(session: Session, vararg messages: Message)
+  fun clear(buffer: BufferId, lastRead: MsgId = Int.MAX_VALUE)
+}
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
index 251c6b4037687644e8cd89a539555a44871b0007..09cfb79cfc6455e9789fd8b9abe56ba542953780 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -44,14 +44,14 @@ class Session(
   address: SocketAddress,
   private val handlerService: HandlerService,
   backlogStorage: BacklogStorage,
+  private val notificationManager: NotificationManager?,
   private var userData: Pair<String, String>,
   val disconnectFromCore: () -> Unit,
   exceptionHandler: (Throwable) -> Unit
 ) : ProtocolHandler(exceptionHandler), ISession {
   override val objectStorage: ObjectStorage = ObjectStorage(this)
   override val proxy: SignalProxy = this
-  override val features = Features(clientData.clientFeatures,
-                                   QuasselFeatures.empty())
+  override val features = Features(clientData.clientFeatures, QuasselFeatures.empty())
 
   override val sslSession
     get() = coreConnection.sslSession
@@ -67,7 +67,7 @@ class Session(
   override val aliasManager = AliasManager(this)
   override val backlogManager = BacklogManager(this, backlogStorage)
   override val bufferViewManager = BufferViewManager(this)
-  override val bufferSyncer = BufferSyncer(this)
+  override val bufferSyncer = BufferSyncer(this, notificationManager)
   override val certManagers = mutableMapOf<IdentityId, CertManager>()
   override val coreInfo = CoreInfo(this)
   override val dccConfig = DccConfig(this)
@@ -86,7 +86,7 @@ class Session(
 
   override val networkConfig = NetworkConfig(this)
 
-  override var rpcHandler: RpcHandler? = RpcHandler(this, backlogStorage)
+  override var rpcHandler: RpcHandler? = RpcHandler(this, backlogStorage, notificationManager)
 
   override val initStatus = BehaviorSubject.createDefault(0 to 0)
 
@@ -230,6 +230,7 @@ class Session(
         config.handleBuffer(info, bufferSyncer)
       }
     }
+    notificationManager?.init(this)
     coreConnection.setState(ConnectionState.CONNECTED)
     dispatch(SignalProxyMessage.HeartBeat(Instant.now()))
   }
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index 53562b631f517e1ced7649dadbf55938173835c0..83d3188ceac60b6ded1786af6edd996d60bec6b4 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -41,6 +41,7 @@ import javax.net.ssl.X509TrustManager
 class SessionManager(
   offlineSession: ISession,
   val backlogStorage: BacklogStorage,
+  val notificationManager: NotificationManager?,
   val handlerService: HandlerService,
   private val disconnectFromCore: () -> Unit,
   private val exceptionHandler: (Throwable) -> Unit
@@ -129,6 +130,7 @@ class SessionManager(
         address,
         handlerService,
         backlogStorage,
+        notificationManager,
         userData,
         disconnectFromCore,
         exceptionHandler
diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt
index f448a885303c989290929dca7aaabd6ff06d4882..160fc01cb6266052044c8ddfd61739f5fa6d039f 100644
--- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt
+++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt
@@ -24,7 +24,6 @@ package de.kuschku.quasseldroid.persistence
 
 import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.protocol.Message
-import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.libquassel.quassel.syncables.IgnoreListManager
 import de.kuschku.libquassel.session.BacklogStorage
 import de.kuschku.libquassel.session.Session
@@ -43,11 +42,11 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage {
 
   override fun storeMessages(session: Session, messages: Iterable<Message>, initialLoad: Boolean) {
     db.message().save(*messages.map {
-      QuasselDatabase.DatabaseMessage(
+      QuasselDatabase.MessageData(
         messageId = it.messageId,
         time = it.time,
-        type = it.type.value,
-        flag = it.flag.value,
+        type = it.type,
+        flag = it.flag,
         bufferId = it.bufferInfo.bufferId,
         sender = it.sender,
         senderPrefixes = it.senderPrefixes,
@@ -81,14 +80,14 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage {
     ) != IgnoreListManager.StrictnessType.UnmatchedStrictness
   }
 
-  private fun isIgnored(session: Session, message: QuasselDatabase.DatabaseMessage): Boolean {
+  private fun isIgnored(session: Session, message: QuasselDatabase.MessageData): Boolean {
     val bufferInfo = session.bufferSyncer.bufferInfo(message.bufferId)
     val bufferName = bufferInfo?.bufferName ?: ""
     val networkId = bufferInfo?.networkId ?: -1
     val networkName = session.network(networkId)?.networkName() ?: ""
 
     return session.ignoreListManager.match(
-      message.content, message.sender, Message_Type.of(message.type), networkName, bufferName
+      message.content, message.sender, message.type, networkName, bufferName
     ) != IgnoreListManager.StrictnessType.UnmatchedStrictness
   }
 }
diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
index 2a35c3c414440643d0a8ae1cc3b27430b85bd2d9..bc7a596e9da9dd9ed3dc0f0f4fc42d890bb3dd6f 100644
--- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
+++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
@@ -29,72 +29,81 @@ import android.arch.persistence.room.*
 import android.arch.persistence.room.migration.Migration
 import android.content.Context
 import android.support.annotation.IntRange
-import de.kuschku.libquassel.protocol.Message_Flag
-import de.kuschku.libquassel.protocol.Message_Type
-import de.kuschku.libquassel.protocol.MsgId
+import de.kuschku.libquassel.protocol.*
 import de.kuschku.quasseldroid.persistence.QuasselDatabase.*
 import io.reactivex.Flowable
 import org.threeten.bp.Instant
 
-@Database(entities = [DatabaseMessage::class, Filtered::class, SslValidityWhitelistEntry::class, SslHostnameWhitelistEntry::class],
-          version = 13)
-@TypeConverters(DatabaseMessage.MessageTypeConverters::class)
+@Database(entities = [MessageData::class, Filtered::class, SslValidityWhitelistEntry::class, SslHostnameWhitelistEntry::class, NotificationData::class],
+          version = 14)
+@TypeConverters(MessageTypeConverter::class)
 abstract class QuasselDatabase : RoomDatabase() {
   abstract fun message(): MessageDao
   abstract fun filtered(): FilteredDao
   abstract fun validityWhitelist(): SslValidityWhitelistDao
   abstract fun hostnameWhitelist(): SslHostnameWhitelistDao
+  abstract fun notifications(): NotificationDao
+
+  class MessageTypeConverter {
+    @TypeConverter
+    fun convertInstant(value: Long): Instant = Instant.ofEpochMilli(value)
+
+    @TypeConverter
+    fun convertInstant(value: Instant) = value.toEpochMilli()
+
+    @TypeConverter
+    fun convertBufferTypes(value: Buffer_Types) = value.toShort()
+
+    @TypeConverter
+    fun convertBufferTypes(value: Short) = Buffer_Type.of(value)
+
+    @TypeConverter
+    fun convertMessageTypes(value: Message_Types) = value.toInt()
+
+    @TypeConverter
+    fun convertMessageTypes(value: Int) = Message_Type.of(value)
+
+    @TypeConverter
+    fun convertMessageFlags(value: Message_Flags) = value.toInt()
+
+    @TypeConverter
+    fun convertMessageFlags(value: Int) = Message_Flag.of(value)
+  }
 
   @Entity(tableName = "message", indices = [Index("bufferId"), Index("ignored")])
-  data class DatabaseMessage(
+  data class MessageData(
     @PrimaryKey var messageId: Int,
     var time: Instant,
-    var type: Int,
-    var flag: Int,
-    var bufferId: Int,
+    var type: Message_Types,
+    var flag: Message_Flags,
+    var bufferId: BufferId,
     var sender: String,
     var senderPrefixes: String,
     var realName: String,
     var avatarUrl: String,
     var content: String,
     var ignored: Boolean
-  ) {
-    class MessageTypeConverters {
-      @TypeConverter
-      fun convertInstant(value: Long): Instant = Instant.ofEpochMilli(value)
-
-      @TypeConverter
-      fun convertInstant(value: Instant) = value.toEpochMilli()
-    }
-
-    override fun toString(): String {
-      return "Message(messageId=$messageId, time=$time, type=${Message_Type.of(
-        type
-      )}, flag=${Message_Flag.of(
-        flag
-      )}, bufferId=$bufferId, sender='$sender', senderPrefixes='$senderPrefixes', realName='$realName', avatarUrl='$avatarUrl', content='$content')"
-    }
-  }
+  )
 
   @Dao
   interface MessageDao {
     @Query("SELECT * FROM message")
-    fun all(): List<DatabaseMessage>
+    fun all(): List<MessageData>
 
     @Query("SELECT * FROM message WHERE messageId = :messageId")
-    fun find(messageId: Int): DatabaseMessage?
+    fun find(messageId: Int): MessageData?
 
     @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC")
-    fun findByBufferId(bufferId: Int): List<DatabaseMessage>
+    fun findByBufferId(bufferId: Int): List<MessageData>
 
     @Query("SELECT * FROM message WHERE bufferId = :bufferId AND type & ~ :type > 0 AND ignored = 0 ORDER BY messageId DESC")
-    fun findByBufferIdPaged(bufferId: Int, type: Int): DataSource.Factory<Int, DatabaseMessage>
+    fun findByBufferIdPaged(bufferId: Int, type: Int): DataSource.Factory<Int, MessageData>
 
     @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId DESC LIMIT 1")
-    fun findLastByBufferId(bufferId: Int): DatabaseMessage?
+    fun findLastByBufferId(bufferId: Int): MessageData?
 
     @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId DESC LIMIT 1")
-    fun lastMsgId(bufferId: Int): LiveData<DatabaseMessage>
+    fun lastMsgId(bufferId: Int): LiveData<MessageData>
 
     @Query("SELECT messageId FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC LIMIT 1")
     fun firstMsgId(bufferId: Int): Flowable<MsgId>
@@ -103,10 +112,10 @@ abstract class QuasselDatabase : RoomDatabase() {
     fun firstVisibleMsgId(bufferId: Int, type: Int): MsgId?
 
     @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId ASC LIMIT 1")
-    fun findFirstByBufferId(bufferId: Int): DatabaseMessage?
+    fun findFirstByBufferId(bufferId: Int): MessageData?
 
     @Insert(onConflict = OnConflictStrategy.REPLACE)
-    fun save(vararg entities: DatabaseMessage)
+    fun save(vararg entities: MessageData)
 
     @Query("UPDATE message SET bufferId = :bufferId1 WHERE bufferId = :bufferId2")
     fun merge(@IntRange(from = 0) bufferId1: Int, @IntRange(from = 0) bufferId2: Int)
@@ -129,7 +138,7 @@ abstract class QuasselDatabase : RoomDatabase() {
   @Entity(tableName = "filtered", primaryKeys = ["accountId", "bufferId"])
   data class Filtered(
     var accountId: Long,
-    var bufferId: Int,
+    var bufferId: BufferId,
     var filtered: Int
   )
 
@@ -215,6 +224,38 @@ abstract class QuasselDatabase : RoomDatabase() {
     fun clear()
   }
 
+  @Entity(tableName = "notification", indices = [Index("bufferId")])
+  data class NotificationData(
+    @PrimaryKey var messageId: Int,
+    var time: Instant,
+    var type: Message_Types,
+    var flag: Message_Flags,
+    var bufferId: BufferId,
+    var bufferName: String,
+    var bufferType: Buffer_Types,
+    var networkId: NetworkId,
+    var sender: String,
+    var senderPrefixes: String,
+    var realName: String,
+    var avatarUrl: String,
+    var content: String
+  )
+
+  @Dao
+  interface NotificationDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun save(vararg entities: NotificationData)
+
+    @Query("SELECT * FROM notification WHERE bufferId = :bufferId ORDER BY time ASC")
+    fun all(bufferId: BufferId): List<NotificationData>
+
+    @Query("DELETE FROM notification WHERE bufferId = :bufferId AND messageId <= :messageId")
+    fun markRead(bufferId: BufferId, messageId: MsgId)
+
+    @Query("DELETE FROM notification")
+    fun clear()
+  }
+
   object Creator {
     private var database: QuasselDatabase? = null
 
@@ -298,6 +339,12 @@ abstract class QuasselDatabase : RoomDatabase() {
                   database.execSQL("create table ssl_validity_whitelist (fingerprint TEXT not null, ignoreDate INTEGER not null, primary key(fingerprint));")
                   database.execSQL("create table ssl_hostname_whitelist (fingerprint TEXT not null, hostname TEXT not null, primary key(fingerprint, hostname));")
                 }
+              },
+              object : Migration(13, 14) {
+                override fun migrate(database: SupportSQLiteDatabase) {
+                  database.execSQL("CREATE TABLE IF NOT EXISTS `notification` (`messageId` INTEGER NOT NULL, `time` INTEGER NOT NULL, `type` INTEGER NOT NULL, `flag` INTEGER NOT NULL, `bufferId` INTEGER NOT NULL, `bufferName` TEXT NOT NULL, `bufferType` INTEGER NOT NULL, `networkId` INTEGER NOT NULL, `sender` TEXT NOT NULL, `senderPrefixes` TEXT NOT NULL, `realName` TEXT NOT NULL, `avatarUrl` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`messageId`));")
+                  database.execSQL("CREATE  INDEX `index_notification_bufferId` ON `notification` (`bufferId`);")
+                }
               }
             ).build()
           }