diff --git a/CMakeLists.txt b/CMakeLists.txt
index 98b3edffe4ba42575c3b65289f418c0229defc4e..e8e814ca0c650d60c92dfc31443d57c1dc8c0638 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -100,6 +100,7 @@ set(SRC_FILES
     src/Splitter.cc
     src/Sync.cc
     src/TextInputWidget.cc
+    src/TrayIcon.cc
     src/TopRoomBar.cc
     src/UserInfoWidget.cc
     src/WelcomePage.cc
@@ -171,6 +172,7 @@ qt5_wrap_cpp(MOC_HEADERS
     include/UserInfoWidget.h
     include/SlidingStackWidget.h
     include/TopRoomBar.h
+    include/TrayIcon.h
     include/TextInputWidget.h
     include/WelcomePage.h
 
diff --git a/include/ChatPage.h b/include/ChatPage.h
index 7e8bc9e9c7da1a98ab045204202750bef2779909..12eaf6b7498501b025a25564065772b0cb3d0c86 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -44,6 +44,7 @@ public:
 signals:
 	void close();
 	void changeWindowTitle(const QString &msg);
+	void unreadMessages(int count);
 
 private slots:
 	void showUnreadMessageNotification(int count);
diff --git a/include/MainWindow.h b/include/MainWindow.h
index ae8d1648e95bff093dbef92921eb97e87b6b5775..a58cc668fdc30e783545155cddebffc0466dc4d6 100644
--- a/include/MainWindow.h
+++ b/include/MainWindow.h
@@ -27,6 +27,7 @@
 #include "OverlayModal.h"
 #include "RegisterPage.h"
 #include "SlidingStackWidget.h"
+#include "TrayIcon.h"
 #include "WelcomePage.h"
 
 class MainWindow : public QMainWindow
@@ -37,7 +38,13 @@ public:
 	explicit MainWindow(QWidget *parent = 0);
 	~MainWindow();
 
+protected:
+	void closeEvent(QCloseEvent *event);
+
 private slots:
+	// Handle interaction with the tray icon.
+	void iconActivated(QSystemTrayIcon::ActivationReason reason);
+
 	// Show the welcome page in the main window.
 	void showWelcomePage();
 
@@ -74,4 +81,7 @@ private:
 
 	// Matrix Client API provider.
 	QSharedPointer<MatrixClient> client_;
+
+	// Tray icon that shows the unread message count.
+	TrayIcon *trayIcon_;
 };
diff --git a/include/TrayIcon.h b/include/TrayIcon.h
new file mode 100644
index 0000000000000000000000000000000000000000..bef8564ff719c2ada40f2bb0c4ad9fcaae1ad267
--- /dev/null
+++ b/include/TrayIcon.h
@@ -0,0 +1,58 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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/>.
+ */
+
+#pragma once
+
+#include <QAction>
+#include <QIcon>
+#include <QIconEngine>
+#include <QMenu>
+#include <QPainter>
+#include <QRect>
+#include <QSystemTrayIcon>
+
+class MsgCountComposedIcon : public QIconEngine
+{
+public:
+	MsgCountComposedIcon(const QString &filename);
+
+	virtual void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state);
+	virtual QIconEngine *clone() const;
+
+	int msgCount = 0;
+
+private:
+	const int BubbleDiameter = 15;
+
+	QIcon icon_;
+};
+
+class TrayIcon : public QSystemTrayIcon
+{
+	Q_OBJECT
+public:
+	TrayIcon(const QString &filename, QWidget *parent);
+
+public slots:
+	void setUnreadCount(int count);
+
+private:
+	QAction *viewAction_;
+	QAction *quitAction_;
+
+	MsgCountComposedIcon *icon_;
+};
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index bd4d656886c3f848d510c041c167833df632dacd..b50128185cf1c9f0593fdf24c26313cf9ecceb0d 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -311,6 +311,8 @@ void ChatPage::changeTopRoomInfo(const QString &room_id)
 
 void ChatPage::showUnreadMessageNotification(int count)
 {
+	emit unreadMessages(count);
+
 	// TODO: Make the default title a const.
 	if (count == 0)
 		emit changeWindowTitle("nheko");
diff --git a/src/LoginPage.cc b/src/LoginPage.cc
index 4c63820c8e15d841a3828aa5b568b750240f9d2b..02ab92c6258e6e20ea41a412e74302453fbd2f31 100644
--- a/src/LoginPage.cc
+++ b/src/LoginPage.cc
@@ -26,6 +26,8 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent)
     , login_settings_{nullptr}
     , client_{client}
 {
+	setStyleSheet("background-color: #f9f9f9");
+
 	top_layout_ = new QVBoxLayout();
 
 	top_bar_layout_ = new QHBoxLayout();
diff --git a/src/MainWindow.cc b/src/MainWindow.cc
index 9a3a44efdc7e240ccdf3d1731883c2668a1a7827..4f813c07a6e814bee6c0602ec09415e773366371 100644
--- a/src/MainWindow.cc
+++ b/src/MainWindow.cc
@@ -20,6 +20,7 @@
 #include <QLayout>
 #include <QNetworkReply>
 #include <QSettings>
+#include <QSystemTrayIcon>
 
 MainWindow::MainWindow(QWidget *parent)
     : QMainWindow(parent)
@@ -29,16 +30,18 @@ MainWindow::MainWindow(QWidget *parent)
 	QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
 	setSizePolicy(sizePolicy);
 	setWindowTitle("nheko");
+	setObjectName("MainWindow");
+	setStyleSheet("QWidget#MainWindow {background-color: #f9f9f9}");
 
 	resize(1066, 600);  // 16:9 ratio
 	setMinimumSize(QSize(950, 600));
-	setStyleSheet("background-color: #f9f9f9");
 
 	QFont font("Open Sans", 12);
 	font.setStyleStrategy(QFont::PreferAntialias);
 	setFont(font);
 
 	client_ = QSharedPointer<MatrixClient>(new MatrixClient("matrix.org"));
+	trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this);
 
 	welcome_page_ = new WelcomePage(this);
 	login_page_ = new LoginPage(client_, this);
@@ -62,6 +65,12 @@ MainWindow::MainWindow(QWidget *parent)
 
 	connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage()));
 	connect(chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString)));
+	connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
+
+	connect(trayIcon_,
+		SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
+		this,
+		SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
 
 	connect(client_.data(),
 		SIGNAL(initialSyncCompleted(const SyncResponse &)),
@@ -159,6 +168,29 @@ void MainWindow::showRegisterPage()
 	sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT);
 }
 
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+	if (isVisible()) {
+		event->ignore();
+		hide();
+	}
+}
+
+void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
+{
+	switch (reason) {
+	case QSystemTrayIcon::Trigger:
+		if (!isVisible()) {
+			show();
+		} else {
+			hide();
+		}
+		break;
+	default:
+		break;
+	}
+}
+
 MainWindow::~MainWindow()
 {
 }
diff --git a/src/RegisterPage.cc b/src/RegisterPage.cc
index f4c35f2e126d3943a27e2ed33d69515a42226006..d5c6091805e3a4130f559a2f09ca7f775c96e8d1 100644
--- a/src/RegisterPage.cc
+++ b/src/RegisterPage.cc
@@ -25,6 +25,8 @@ RegisterPage::RegisterPage(QSharedPointer<MatrixClient> client, QWidget *parent)
     : QWidget(parent)
     , client_(client)
 {
+	setStyleSheet("background-color: #f9f9f9");
+
 	top_layout_ = new QVBoxLayout();
 
 	back_layout_ = new QHBoxLayout();
diff --git a/src/TrayIcon.cc b/src/TrayIcon.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4954044a04d2767f7716f7ace2137a311b9dbad7
--- /dev/null
+++ b/src/TrayIcon.cc
@@ -0,0 +1,93 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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/>.
+ */
+
+#include <QTimer>
+
+#include "TrayIcon.h"
+
+MsgCountComposedIcon::MsgCountComposedIcon(const QString &filename)
+    : QIconEngine()
+{
+	icon_ = QIcon(filename);
+}
+
+void MsgCountComposedIcon::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+	icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
+
+	if (msgCount <= 0)
+		return;
+
+	QColor backgroundColor("red");
+	QColor textColor("white");
+
+	QBrush brush;
+	brush.setStyle(Qt::SolidPattern);
+	brush.setColor(backgroundColor);
+
+	painter->setBrush(brush);
+	painter->setPen(Qt::NoPen);
+	painter->setFont(QFont("Open Sans", 7, QFont::Black));
+
+	QRectF bubble(rect.width() - BubbleDiameter,
+		      rect.height() - BubbleDiameter,
+		      BubbleDiameter,
+		      BubbleDiameter);
+	painter->drawEllipse(bubble);
+	painter->setPen(QPen(textColor));
+	painter->setBrush(Qt::NoBrush);
+	painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
+}
+
+QIconEngine *MsgCountComposedIcon::clone() const
+{
+	return new MsgCountComposedIcon(*this);
+}
+
+TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
+    : QSystemTrayIcon(parent)
+{
+	icon_ = new MsgCountComposedIcon(filename);
+	setIcon(QIcon(icon_));
+
+	QMenu *menu = new QMenu(parent);
+	viewAction_ = new QAction("Show", parent);
+	quitAction_ = new QAction("Quit", parent);
+
+	connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
+	connect(quitAction_, SIGNAL(triggered()), parent, SLOT(close()));
+
+	menu->addAction(viewAction_);
+	menu->addAction(quitAction_);
+
+	setContextMenu(menu);
+
+	// We wait a little for the icon to load.
+	QTimer::singleShot(500, this, [=]() {
+		show();
+	});
+}
+
+void TrayIcon::setUnreadCount(int count)
+{
+	MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(icon_->clone());
+	tmp->msgCount = count;
+
+	setIcon(QIcon(tmp));
+
+	icon_ = tmp;
+}