diff --git a/.bootstraprc b/.bootstraprc
index 924360a9818097b167d91384b0effefbc2d47a14..e579effcaed034a57ec58ed2b2205a61833e0952 100644
--- a/.bootstraprc
+++ b/.bootstraprc
@@ -36,14 +36,17 @@
         "type": true,
         "tooltip": true,
         "utilities": true,
+        "popover": true,
     },
     "scripts": {
         "alert": true,
         "collapse": true,
+        "dropdown": true,
         "modal": true,
         "scrollspy": true,
         "tab": true,
         "tooltip": true,
         "util": true,
+        "popover": true,
     }
 }
diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py
index f3a4ed8e5a68b10a591d4081d9ea2e8ec02948fa..8fd730d747a36290c629701311008135a53c02a9 100644
--- a/SciPost_v1/settings/base.py
+++ b/SciPost_v1/settings/base.py
@@ -90,6 +90,7 @@ INSTALLED_APPS = (
     'mails',
     'mailing_lists',
     'news',
+    'notifications',
     'scipost',
     'submissions',
     'theses',
@@ -107,11 +108,6 @@ HAYSTACK_CONNECTIONS = {
         'PATH': 'local_files/haystack/',
         'EXCLUDED_INDEXES': ['sphinxdoc.search_indexes.DocumentIndex'],
     },
-    # 'scipost': {
-    #     'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
-    #     'PATH': 'local_files/haystack_scipost/',
-    #     'EXCLUDED_INDEXES': ['sphinxdoc.search_indexes.DocumentIndex'],
-    # },
 }
 
 # Brute force automatically re-index Haystack using post_save signals on all models.
@@ -149,7 +145,7 @@ MATHJAX_CONFIG_DATA = {
         }
     }
 
-MIDDLEWARE_CLASSES = (
+MIDDLEWARE = (
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.locale.LocaleMiddleware',
     'django.middleware.common.CommonMiddleware',
@@ -158,7 +154,7 @@ MIDDLEWARE_CLASSES = (
     'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
-    'django.middleware.security.SecurityMiddleware',
+    'django.middleware.security.SecurityMiddleware'
 )
 
 ROOT_URLCONF = 'SciPost_v1.urls'
diff --git a/SciPost_v1/settings/local_jorran.py b/SciPost_v1/settings/local_jorran.py
index f1f650d012c570fc7a512b1dbc8269640a9e66f1..cd2ed99b46436673da26e9b69da2ec69da258b0d 100644
--- a/SciPost_v1/settings/local_jorran.py
+++ b/SciPost_v1/settings/local_jorran.py
@@ -7,7 +7,7 @@ DEBUG = True
 INSTALLED_APPS += (
     'debug_toolbar',
 )
-MIDDLEWARE_CLASSES += (
+MIDDLEWARE += (
     'debug_toolbar.middleware.DebugToolbarMiddleware',
 )
 INTERNAL_IPS = ['127.0.0.1', '::1']
diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py
index fc2b471e09c717922963e7287c2600c115d356a1..ba767eb8166b0f60df1e78d25b141bf652b53732 100644
--- a/SciPost_v1/urls.py
+++ b/SciPost_v1/urls.py
@@ -41,6 +41,7 @@ urlpatterns = [
     url(r'^thesis/', include('theses.urls', namespace="_theses")),
     url(r'^meetings/', include('virtualmeetings.urls', namespace="virtualmeetings")),
     url(r'^news/', include('news.urls', namespace="news")),
+    url(r'^notifications/', include('notifications.urls', namespace="notifications")),
     url(r'^production/', include('production.urls', namespace="production")),
     url(r'^partners/', include('partners.urls', namespace="partners")),
 
diff --git a/notifications/__init__.py b/notifications/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d51321cada927770a881312f84365d972e19b924
--- /dev/null
+++ b/notifications/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'notifications.apps.NotificationsConfig'
diff --git a/notifications/admin.py b/notifications/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cf8c7c7b329a7a98b23bb5332d71059d5023d56
--- /dev/null
+++ b/notifications/admin.py
@@ -0,0 +1,12 @@
+from django.contrib import admin
+from .models import Notification
+
+
+class NotificationAdmin(admin.ModelAdmin):
+    raw_id_fields = ('recipient', )
+    list_display = ('recipient', 'actor',
+                    'level', 'target', 'unread', 'public')
+    list_filter = ('level', 'unread', 'public', 'created', )
+
+
+admin.site.register(Notification, NotificationAdmin)
diff --git a/notifications/apps.py b/notifications/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a0aef11beb47c7407ecf968d3eab794db559274
--- /dev/null
+++ b/notifications/apps.py
@@ -0,0 +1,10 @@
+from django.apps import AppConfig
+
+
+class NotificationsConfig(AppConfig):
+    name = 'notifications'
+
+    def ready(self):
+        super().ready()
+        import notifications.signals
+        notifications.notify = notifications.signals.notify
diff --git a/notifications/managers.py b/notifications/managers.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ec6b22b904ec620247661c0314472a25ade2ba7
--- /dev/null
+++ b/notifications/managers.py
@@ -0,0 +1,77 @@
+from django.db import models
+
+
+class NotificationQuerySet(models.query.QuerySet):
+
+    def unsent(self):
+        return self.filter(emailed=False)
+
+    def sent(self):
+        return self.filter(emailed=True)
+
+    def unread(self):
+        """Return only unread items in the current queryset"""
+        return self.filter(unread=True)
+
+    def read(self):
+        """Return only read items in the current queryset"""
+        return self.filter(unread=False)
+
+    def mark_all_as_read(self, recipient=None):
+        """Mark as read any unread messages in the current queryset."""
+        # We want to filter out read ones, as later we will store
+        # the time they were marked as read.
+        qs = self.unread()
+        if recipient:
+            qs = qs.filter(recipient=recipient)
+
+        return qs.update(unread=False)
+
+    def mark_all_as_unread(self, recipient=None):
+        """Mark as unread any read messages in the current queryset."""
+        qs = self.read()
+
+        if recipient:
+            qs = qs.filter(recipient=recipient)
+
+        return qs.update(unread=True)
+
+    def deleted(self):
+        """Return only deleted items in the current queryset"""
+        raise DeprecationWarning
+        return self.filter(deleted=True)
+
+    def active(self):
+        """Return only active(un-deleted) items in the current queryset"""
+        raise DeprecationWarning
+        return self.filter(deleted=False)
+
+    def mark_all_as_deleted(self, recipient=None):
+        """Mark current queryset as deleted."""
+        raise DeprecationWarning
+        qs = self.active()
+        if recipient:
+            qs = qs.filter(recipient=recipient)
+
+        return qs.update(deleted=True)
+
+    def mark_all_as_active(self, recipient=None):
+        """Mark current queryset as active(un-deleted)."""
+        raise DeprecationWarning
+        qs = self.deleted()
+        if recipient:
+            qs = qs.filter(recipient=recipient)
+
+        return qs.update(deleted=False)
+
+    def mark_as_unsent(self, recipient=None):
+        qs = self.sent()
+        if recipient:
+            qs = self.filter(recipient=recipient)
+        return qs.update(emailed=False)
+
+    def mark_as_sent(self, recipient=None):
+        qs = self.unsent()
+        if recipient:
+            qs = self.filter(recipient=recipient)
+        return qs.update(emailed=True)
diff --git a/notifications/migrations/0001_initial.py b/notifications/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e4dfe442887f4f26d76b60d175abfafb98ce9bd
--- /dev/null
+++ b/notifications/migrations/0001_initial.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-11 18:33
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('contenttypes', '0002_remove_content_type_name'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Notification',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('level', models.CharField(choices=[('success', 'Success'), ('info', 'Info'), ('warning', 'Warning'), ('error', 'Error')], default='info', max_length=20)),
+                ('unread', models.BooleanField(default=True)),
+                ('actor_object_id', models.CharField(max_length=255)),
+                ('verb', models.CharField(max_length=255)),
+                ('description', models.TextField(blank=True, null=True)),
+                ('target_object_id', models.CharField(blank=True, max_length=255, null=True)),
+                ('action_object_object_id', models.CharField(blank=True, max_length=255, null=True)),
+                ('created', models.DateTimeField(default=django.utils.timezone.now)),
+                ('public', models.BooleanField(default=True)),
+                ('emailed', models.BooleanField(default=False)),
+                ('action_object_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action_object', to='contenttypes.ContentType')),
+                ('actor_content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.ContentType')),
+                ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
+                ('target_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.ContentType')),
+            ],
+            options={
+                'ordering': ('-created',),
+            },
+        ),
+    ]
diff --git a/notifications/migrations/__init__.py b/notifications/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/notifications/models.py b/notifications/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecec80cfea09c80841445b49b984d9b8cde60c25
--- /dev/null
+++ b/notifications/models.py
@@ -0,0 +1,111 @@
+from django.db import models
+from django.core.urlresolvers import reverse
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes.fields import GenericForeignKey
+from django.utils import timezone
+
+from .managers import NotificationQuerySet
+
+
+class Notification(models.Model):
+    """
+    Action model describing the actor acting out a verb (on an optional
+    target).
+    Nomenclature based on http://activitystrea.ms/specs/atom/1.0/
+    Generalized Format::
+        <actor> <verb> <time>
+        <actor> <verb> <target> <time>
+        <actor> <verb> <action_object> <target> <time>
+    Examples::
+        <justquick> <reached level 60> <1 minute ago>
+        <brosner> <commented on> <pinax/pinax> <2 hours ago>
+        <washingtontimes> <started follow> <justquick> <8 minutes ago>
+        <mitsuhiko> <closed> <issue 70> on <mitsuhiko/flask> <about 2 hours ago>
+    Unicode Representation::
+        justquick reached level 60 1 minute ago
+        mitsuhiko closed issue 70 on mitsuhiko/flask 3 hours ago
+    """
+    LEVELS = (('success', 'Success'), ('info', 'Info'), ('warning', 'Warning'), ('error', 'Error'))
+    level = models.CharField(choices=LEVELS, default='info', max_length=20)
+
+    recipient = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False,
+                                  related_name='notifications')
+    unread = models.BooleanField(default=True)
+
+    actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor')
+    actor_object_id = models.CharField(max_length=255)
+    actor = GenericForeignKey('actor_content_type', 'actor_object_id')
+
+    verb = models.CharField(max_length=255)
+    description = models.TextField(blank=True, null=True)
+
+    target_content_type = models.ForeignKey(ContentType, related_name='notify_target',
+                                            blank=True, null=True)
+    target_object_id = models.CharField(max_length=255, blank=True, null=True)
+    target = GenericForeignKey('target_content_type', 'target_object_id')
+
+    action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True,
+                                                   related_name='notify_action_object')
+    action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
+    action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')
+
+    created = models.DateTimeField(default=timezone.now)
+
+    public = models.BooleanField(default=True)
+    emailed = models.BooleanField(default=False)
+
+    objects = NotificationQuerySet.as_manager()
+
+    class Meta:
+        ordering = ('-created', )
+
+    def __str__(self):
+        ctx = {
+            'actor': self.actor,
+            'verb': self.verb,
+            'action_object': self.action_object,
+            'target': self.target,
+            'timesince': self.timesince()
+        }
+        if self.target:
+            if self.action_object:
+                return u'%(actor)s %(verb)s %(action_object)s on %(target)s %(timesince)s ago' % ctx
+            return u'%(actor)s %(verb)s %(target)s %(timesince)s ago' % ctx
+        if self.action_object:
+            return u'%(actor)s %(verb)s %(action_object)s %(timesince)s ago' % ctx
+        return u'%(actor)s %(verb)s %(timesince)s ago' % ctx
+
+    def get_absolute_url(self):
+        return reverse('notifications:forward', args=(self.slug,))
+
+    def timesince(self, now=None):
+        """
+        Shortcut for the ``django.utils.timesince.timesince`` function of the
+        current timestamp.
+        """
+        from django.utils.timesince import timesince as timesince_
+        return timesince_(self.created, now)
+
+    @property
+    def slug(self):
+        from .utils import id2slug
+        return id2slug(self.id)
+
+    def mark_toggle(self):
+        if self.unread:
+            self.unread = False
+            self.save()
+        else:
+            self.unread = True
+            self.save()
+
+    def mark_as_read(self):
+        if self.unread:
+            self.unread = False
+            self.save()
+
+    def mark_as_unread(self):
+        if not self.unread:
+            self.unread = True
+            self.save()
diff --git a/notifications/signals.py b/notifications/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..af8be2f2807ae3a16573f49e4319d49b5a615dac
--- /dev/null
+++ b/notifications/signals.py
@@ -0,0 +1,36 @@
+from django.dispatch import receiver, Signal
+
+from .models import Notification
+
+
+notify = Signal(providing_args=[
+    'recipient', 'actor', 'verb', 'action_object', 'target', 'description', 'level'
+])
+
+
+@receiver(notify)
+def notify_receiver(sender, **kwargs):
+    if not type(kwargs['recipient']) == list:
+        recipient = [kwargs['recipient']]
+    else:
+        recipient = kwargs['recipient']
+
+    for user in recipient:
+        notification = Notification(
+            recipient=user,
+            actor=kwargs['actor'],
+            verb=kwargs['verb'],
+            action_object=kwargs.get('action_object'),
+            target=kwargs.get('target'),
+            description=kwargs.get('description'),
+            level=kwargs.get('level', 'info')
+        )
+        notification.save()
+    print("Request finished!")
+
+
+# Basic working method to send a notification to a user using signals:
+#   ---
+#     from notifications.signals import notify
+#     notify.send(user, recipient=user, verb='you reached level 10')
+#   ---
diff --git a/notifications/templates/notifications/notification_list.html b/notifications/templates/notifications/notification_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..a2288eb3a70f25d59f831a98d4106600c140ab2a
--- /dev/null
+++ b/notifications/templates/notifications/notification_list.html
@@ -0,0 +1,25 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <span class="breadcrumb-item">My notifications</span>
+{% endblock %}
+
+{% block pagetitle %}: My notifications{% endblock pagetitle %}
+
+{% block content %}
+    <h1>My notifications</h1>
+    <div class="mb-3">
+        <a href="{% url 'notifications:mark_all_as_read' %}">Mark all as read</a>
+    </div>
+
+    <ul class="list-group notifications">
+        {% for notice in notifications %}
+            {% include 'notifications/partials/notice.html' %}
+        {% empty %}
+            <li class="list-group-item">
+                <div class="text-center px-2 py-3"><i class="fa fa-star-o fa-2x" aria-hidden="true"></i><h3>You have no new notifications</h3></div>
+            </li>
+        {% endfor %}
+    </ul>
+{% endblock %}
diff --git a/notifications/templates/notifications/partials/notice.html b/notifications/templates/notifications/partials/notice.html
new file mode 100644
index 0000000000000000000000000000000000000000..642acde443cb311d1726ee30fbbe899a4fab3680
--- /dev/null
+++ b/notifications/templates/notifications/partials/notice.html
@@ -0,0 +1,34 @@
+<div class="list-group-item list-group-item-action flex-column align-items-start{% if notice.unread %} active{% endif %} px-3 py-2">
+    <div>
+        <div class="actions">
+            <a href="{% url 'notifications:mark_toggle' notice.slug %}">
+                {% if notice.unread %}
+                    <i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i>
+                {% else %}
+                    <i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i>
+                {% endif %}
+            </a>
+        </div>
+        <h4>
+            <strong>
+                {% if notice.actor.first_name and notice.actor.last_name %}
+                    {{ notice.actor.first_name}} {{ notice.actor.last_name }}
+                {% else %}
+                    {{ notice.actor }}
+                {% endif %}
+            </strong>
+            {{ notice.verb }}
+            {% if notice.target %}
+                {{ notice.target }}
+            {% endif %}
+        </h4>
+        <div class="text-muted mb-1">{{ notice.created|timesince }} ago</div>
+
+    </div>
+    {% if notice.description %}
+        <p class="mb-1">{{ notice.description|linebreaksbr }}</p>
+    {% endif %}
+    {% if notice.target %}
+        <a href="{{notice.target.get_absolute_url}}">Go to {{notice.target_content_type}}</a>
+    {% endif %}
+</div>
diff --git a/notifications/templatetags/notifications_tags.py b/notifications/templatetags/notifications_tags.py
new file mode 100644
index 0000000000000000000000000000000000000000..42c62d09bfa42bf4735008e895623171f7f500ec
--- /dev/null
+++ b/notifications/templatetags/notifications_tags.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+from django.template import Library
+from django.utils.html import format_html
+
+register = Library()
+
+
+@register.assignment_tag(takes_context=True)
+def notifications_unread(context):
+    user = user_context(context)
+    if not user:
+        return ''
+    return user.notifications.unread().count()
+
+
+@register.simple_tag(takes_context=True)
+def live_notify_badge(context, badge_class='live_notify_badge', classes=''):
+    user = user_context(context)
+    if not user:
+        return ''
+
+    html = "<span class='{badge_class} {classes}' data-count='{unread}'>{unread}</span>".format(
+        badge_class=badge_class, unread=user.notifications.unread().count(),
+        classes=classes
+    )
+    return format_html(html)
+
+
+@register.simple_tag
+def live_notify_list(list_class='live_notify_list', classes=''):
+    html = "<ul class='{list_class} {classes}'></ul>".format(list_class=list_class,
+                                                             classes=classes)
+    return format_html(html)
+
+
+def user_context(context):
+    if 'user' not in context:
+        return None
+
+    request = context['request']
+    user = request.user
+    if user.is_anonymous():
+        return None
+    return user
diff --git a/notifications/tests.py b/notifications/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/notifications/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/notifications/urls.py b/notifications/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..23bb20e9e1e4aedb2bc6ec2191bf9dbf2f4c5101
--- /dev/null
+++ b/notifications/urls.py
@@ -0,0 +1,15 @@
+from django.conf.urls import url
+
+from . import views
+
+
+urlpatterns = [
+    url(r'^$', views.AllNotificationsList.as_view(), name='all'),
+    url(r'^redirect/(?P<slug>\d+)$', views.forward, name='forward'),
+    url(r'^mark-all-as-read/$', views.mark_all_as_read, name='mark_all_as_read'),
+    url(r'^mark-toggle/(?P<slug>\d+)/$', views.mark_toggle, name='mark_toggle'),
+    url(r'^delete/(?P<slug>\d+)/$', views.delete, name='delete'),
+    url(r'^api/unread_count/$', views.live_unread_notification_count,
+        name='live_unread_notification_count'),
+    url(r'^api/list/$', views.live_notification_list, name='live_unread_notification_list'),
+]
diff --git a/notifications/utils.py b/notifications/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f80b64365f8d617a8b0d6a3787f0b6b360df25e
--- /dev/null
+++ b/notifications/utils.py
@@ -0,0 +1,6 @@
+def slug2id(slug):
+    return int(slug) - 9631
+
+
+def id2slug(id):
+    return id + 9631
diff --git a/notifications/views.py b/notifications/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..38dd0329b46e900f6b569d04e50cbb586c2fb517
--- /dev/null
+++ b/notifications/views.py
@@ -0,0 +1,137 @@
+from django.contrib.auth.decorators import login_required, user_passes_test
+from django.contrib.auth.models import User
+from django.forms import model_to_dict
+from django.http import JsonResponse
+from django.shortcuts import get_object_or_404, redirect
+from django.utils.decorators import method_decorator
+from django.views.generic import ListView
+
+from .models import Notification
+from .utils import id2slug, slug2id
+
+
+def is_test_user(user):
+    return user.groups.filter(name='Testers').exists()
+
+
+@method_decorator(login_required, name='dispatch')
+@method_decorator(user_passes_test(is_test_user), name='dispatch')
+class NotificationViewList(ListView):
+    context_object_name = 'notifications'
+
+
+class AllNotificationsList(NotificationViewList):
+    """
+    Index page for authenticated user
+    """
+    def get_queryset(self):
+        return self.request.user.notifications.all()
+
+
+@login_required
+@user_passes_test(is_test_user)
+def forward(request, slug):
+    """
+    Open the url of the target object of the notification and redirect.
+    In addition, mark the notification as read.
+    """
+    notification = get_object_or_404(Notification, recipient=request.user, id=slug2id(slug))
+    notification.mark_as_read()
+    return redirect(notification.target.get_absolute_url())
+
+
+@login_required
+@user_passes_test(is_test_user)
+def mark_all_as_read(request):
+    request.user.notifications.mark_all_as_read()
+
+    _next = request.GET.get('next')
+
+    if _next:
+        return redirect(_next)
+    return redirect('notifications:all')
+
+
+@login_required
+@user_passes_test(is_test_user)
+def mark_toggle(request, slug=None):
+    id = slug2id(slug)
+
+    notification = get_object_or_404(Notification, recipient=request.user, id=id)
+    notification.mark_toggle()
+
+    _next = request.GET.get('next')
+    if _next:
+        return redirect(_next)
+
+    if request.GET.get('json'):
+        return JsonResponse({'unread': notification.unread})
+
+    return redirect('notifications:all')
+
+
+@login_required
+@user_passes_test(is_test_user)
+def delete(request, slug=None):
+    id = slug2id(slug)
+
+    notification = get_object_or_404(Notification, recipient=request.user, id=id)
+    notification.delete()
+
+    _next = request.GET.get('next')
+
+    if _next:
+        return redirect(_next)
+
+    return redirect('notifications:all')
+
+
+def live_unread_notification_count(request):
+    if not request.user.is_authenticated():
+        data = {'unread_count': 0}
+    else:
+        data = {'unread_count': request.user.notifications.unread().count()}
+    return JsonResponse(data)
+
+
+def live_notification_list(request):
+    if not request.user.is_authenticated():
+        data = {
+           'unread_count': 0,
+           'list': []
+        }
+        return JsonResponse(data)
+
+    try:
+        # Default to 5 as a max number of notifications
+        num_to_fetch = max(int(request.GET.get('max', 5)), 1)
+        num_to_fetch = min(num_to_fetch, 100)
+    except ValueError:
+        num_to_fetch = 5
+
+    list = []
+
+    for n in request.user.notifications.all()[:num_to_fetch]:
+        struct = model_to_dict(n)
+        struct['slug'] = id2slug(n.id)
+        if n.actor:
+            if isinstance(n.actor, User):
+                # Humanize if possible
+                struct['actor'] = '{f} {l}'.format(f=n.actor.first_name, l=n.actor.last_name)
+            else:
+                struct['actor'] = str(n.actor)
+        if n.target:
+            struct['target'] = str(n.target)
+            struct['forward_link'] = n.get_absolute_url()
+        if n.action_object:
+            struct['action_object'] = str(n.action_object)
+        struct['timesince'] = n.timesince()
+
+        list.append(struct)
+        if request.GET.get('mark_as_read'):
+            n.mark_as_read()
+    data = {
+        'unread_count': request.user.notifications.unread().count(),
+        'list': list
+    }
+    return JsonResponse(data)
diff --git a/production/__init__.py b/production/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2418e502e4286e2cfcb4845419fa9fee9ea0e4cb 100644
--- a/production/__init__.py
+++ b/production/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'production.apps.ProductionConfig'
diff --git a/production/apps.py b/production/apps.py
index d8fba60df00793d7fbaff3cf230a0ba937c9576a..549c6bd6bb6fac43a2edebae72c2e580cb66bb11 100644
--- a/production/apps.py
+++ b/production/apps.py
@@ -1,5 +1,14 @@
 from django.apps import AppConfig
+from django.db.models.signals import post_save
 
 
 class ProductionConfig(AppConfig):
     name = 'production'
+
+    def ready(self):
+        super().ready()
+
+        from .models import ProductionStream, ProductionEvent
+        from .signals import notify_new_stream, notify_new_event
+        post_save.connect(notify_new_stream, sender=ProductionStream)
+        post_save.connect(notify_new_event, sender=ProductionEvent)
diff --git a/production/models.py b/production/models.py
index 6b515e8c2b2f8c2c11e8396f6969210cb5554106..3d94ddaa295504fbb05aa1224937ee7436f2093b 100644
--- a/production/models.py
+++ b/production/models.py
@@ -5,12 +5,6 @@ from django.core.urlresolvers import reverse
 from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_STREAM_ONGOING, PRODUCTION_EVENTS
 from .managers import ProductionStreamManager, ProductionEventManager
 
-from scipost.models import Contributor
-
-
-##############
-# Production #
-##############
 
 class ProductionStream(models.Model):
     submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE)
@@ -22,7 +16,8 @@ class ProductionStream(models.Model):
     objects = ProductionStreamManager()
 
     def __str__(self):
-        return str(self.submission)
+        return '{arxiv}, {title}'.format(arxiv=self.submission.arxiv_identifier_w_vn_nr,
+                                         title=self.submission.title)
 
     def get_absolute_url(self):
         if self.status == PRODUCTION_STREAM_ONGOING:
@@ -39,7 +34,7 @@ class ProductionEvent(models.Model):
     event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS)
     comments = models.TextField(blank=True, null=True)
     noted_on = models.DateTimeField(default=timezone.now)
-    noted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE)
+    noted_by = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE)
     duration = models.DurationField(blank=True, null=True)
 
     objects = ProductionEventManager()
@@ -48,7 +43,7 @@ class ProductionEvent(models.Model):
         ordering = ['noted_on']
 
     def __str__(self):
-        return '%s: %s' % (str(self.stream.submission), self.get_event_display())
+        return '%s: %s' % (self.stream, self.get_event_display())
 
     def get_absolute_url(self):
         return self.stream.get_absolute_url()
diff --git a/production/signals.py b/production/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a3102f9249f4a5a71f1a9603974a7590b1b4a31
--- /dev/null
+++ b/production/signals.py
@@ -0,0 +1,33 @@
+from django.contrib.auth.models import Group, User
+
+from notifications.signals import notify
+
+
+def notify_new_stream(sender, instance, created, **kwargs):
+    """
+    Notify the production team about a new Production Stream created.
+    """
+    if created:
+        production_officers = User.objects.filter(groups__name='Production Officers')
+        editorial_college = Group.objects.get(name='Editorial College')
+        for user in production_officers:
+            notify.send(sender=sender, recipient=user, actor=editorial_college, verb=' accepted a submission. A new productionstream has been started.', target=instance)
+
+
+def notify_new_event(sender, instance, created, **kwargs):
+    """
+    Notify the production team about a new Production Event created.
+    """
+    if created:
+        production_officers = User.objects.filter(groups__name='Production Officers')
+        for user in production_officers:
+            notify.send(sender=sender, recipient=user, actor=instance.noted_by.user, verb=' created a new Production Event ', target=instance)
+
+
+def notify_stream_completed(sender, instance, **kwargs):
+    """
+    Notify the production team about a Production Stream being completed.
+    """
+    production_officers = User.objects.filter(groups__name='Production Officers')
+    for user in production_officers:
+        notify.send(sender=sender, recipient=user, actor=sender, verb=' marked Production Stream as completed.', target=instance)
diff --git a/production/views.py b/production/views.py
index 06c723470234e8d9acf18dddf4a589860526be55..517c09c116ecdd90c20cc0dcdc5ba199f194a202 100644
--- a/production/views.py
+++ b/production/views.py
@@ -13,6 +13,7 @@ from guardian.decorators import permission_required
 from .constants import PRODUCTION_STREAM_COMPLETED
 from .models import ProductionStream, ProductionEvent
 from .forms import ProductionEventForm
+from .signals import notify_stream_completed
 
 from scipost.models import Contributor
 
@@ -107,6 +108,8 @@ def mark_as_completed(request, stream_id):
     stream.status = PRODUCTION_STREAM_COMPLETED
     stream.closed = timezone.now()
     stream.save()
+
+    notify_stream_completed(request.user, stream)
     return redirect(reverse('production:production'))
 
 
diff --git a/scipost/models.py b/scipost/models.py
index d6cac15fd9be45affe5c0bfbe9e9ba47ace74861..31ad4a40f60bb649e49f8819bd5fd17f9a286558 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -7,7 +7,6 @@ from django.core.urlresolvers import reverse
 from django.contrib.auth.models import User
 from django.contrib.postgres.fields import ArrayField
 from django.db import models
-from django.template import Template, Context
 from django.utils import timezone
 
 from django_countries.fields import CountryField
diff --git a/scipost/static/scipost/assets/config/preconfig.scss b/scipost/static/scipost/assets/config/preconfig.scss
index 37775af6b78bb58872d74ecd493da60bfb6d8464..7ddb10372dd8fed7fd054288653e0a21ca3e1024 100644
--- a/scipost/static/scipost/assets/config/preconfig.scss
+++ b/scipost/static/scipost/assets/config/preconfig.scss
@@ -7,6 +7,7 @@
 //
 $base-border-radius: 2px;
 $border-radius: 2px;
+$border-radius-lg: 2px;
 
 // Alert
 //
@@ -93,6 +94,9 @@ $list-group-bg: transparent;
 $list-group-item-padding-x: 0;
 $list-group-item-padding-y: 0;
 $list-group-border-color: #ddd;
+$list-group-active-color: $scipost-darkblue;
+$list-group-active-bg: $scipost-lightestblue;
+$list-group-active-border-color: $scipost-lightestblue;
 
 // Fonts
 //
@@ -137,6 +141,16 @@ $blockquote-font-size: $font-size-base;
 // $blockquote-border-color: #ececec;
 
 
+// Popover
+//
+$popover-border-color:                #dddddd;
+$popover-arrow-width:                 5px;
+$popover-arrow-height:                5px;
+// $popover-arrow-color:                 $popover-bg;
+$popover-max-width:                   500px;
+
+$popover-arrow-outer-width:           ($popover-arrow-width + 1px);
+
 
 
 //  ---
diff --git a/scipost/static/scipost/assets/css/_alert.scss b/scipost/static/scipost/assets/css/_alert.scss
index b60845a33bf55c71b223feb8416b4d3f27a19902..67ca539f4142e341a56a4f97b659e6e17216339a 100644
--- a/scipost/static/scipost/assets/css/_alert.scss
+++ b/scipost/static/scipost/assets/css/_alert.scss
@@ -1,5 +1,5 @@
 .alert {
-    padding: 0.75rem 3rem 0.75rem 1.25rem;
+    padding: 0.75rem 1.25rem;
     margin-bottom: 0.5rem;
     position: relative;
     clear: both;
diff --git a/scipost/static/scipost/assets/css/_badge.scss b/scipost/static/scipost/assets/css/_badge.scss
new file mode 100644
index 0000000000000000000000000000000000000000..13ed52bb69e315f34a811d44613535b4bc54404c
--- /dev/null
+++ b/scipost/static/scipost/assets/css/_badge.scss
@@ -0,0 +1,4 @@
+.badge {
+    vertical-align: bottom;
+
+}
diff --git a/scipost/static/scipost/assets/css/_navbar.scss b/scipost/static/scipost/assets/css/_navbar.scss
index e267881bddb4c174f1510f04f081e0672ea64320..452b2716637529ae319a7b1506839cd1a91581ce 100644
--- a/scipost/static/scipost/assets/css/_navbar.scss
+++ b/scipost/static/scipost/assets/css/_navbar.scss
@@ -73,3 +73,32 @@
         white-space: nowrap;
     }
 }
+
+.navbar-counter {
+    position: relative;
+
+    a.dropdown-toggle {
+        min-width: 45px;
+    }
+
+    a:hover {
+        text-decoration: none;
+
+        .badge {
+            background-color: $scipost-darkblue;
+        }
+
+        .fa-bell-o:before {
+            content: "\f0f3";
+        }
+    }
+
+    .badge {
+        vertical-align: top;
+        margin-left: -5px;
+        margin-top: -2px;
+        height: 16px;
+        min-width: 16px;
+        line-height: 12px;
+    }
+}
diff --git a/scipost/static/scipost/assets/css/_popover.scss b/scipost/static/scipost/assets/css/_popover.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6cc116b60479e5d4661291068b077b55cec1d9c6
--- /dev/null
+++ b/scipost/static/scipost/assets/css/_popover.scss
@@ -0,0 +1,43 @@
+.popover {
+    width: 500px;
+    box-shadow: #ccc 0px 1px 2px 1px;
+}
+
+.notifications {
+    .popover-body {
+        padding: 0;
+    }
+
+    &.popover .list-group-item {
+        padding: 9px 14px;
+        border-radius: 0;
+        border-top: 1px solid #fff;
+    }
+
+    .actions {
+        display: block;
+        opacity: 0.0;
+        transition: opacity 0.1s;
+        width: 20px;
+        float: right;
+        height: 100%;
+
+        a:hover {
+            .fa-circle-o:before {
+                content: '\f111';
+            }
+
+            .fa-circle:before {
+                content: '\f10c';
+            }
+        }
+    }
+
+    .list-group-item:hover .actions {
+        opacity: 1.0;
+    }
+}
+
+.popover-header {
+    font-size: initial;
+}
diff --git a/scipost/static/scipost/assets/css/_type.scss b/scipost/static/scipost/assets/css/_type.scss
index 6aba22c09b1129e47f6037081f719db50a8b3812..9f21c44547325bbbf1055366f00574a18cc4a3b2 100644
--- a/scipost/static/scipost/assets/css/_type.scss
+++ b/scipost/static/scipost/assets/css/_type.scss
@@ -26,7 +26,8 @@ h1 > a {
     color: $scipost-darkblue;
 }
 
-h3 {
+h3,
+h4 {
     line-height: normal;
 }
 
diff --git a/scipost/static/scipost/assets/css/style.scss b/scipost/static/scipost/assets/css/style.scss
index a9ca9127664dd156d871fe1213d93ddd118ca978..5c8bb75eef61d1059c45fa6a2e6d1f86d20e8b1f 100644
--- a/scipost/static/scipost/assets/css/style.scss
+++ b/scipost/static/scipost/assets/css/style.scss
@@ -16,6 +16,7 @@
 @import "mixins_labels";
 
 @import "alert";
+@import "badge";
 @import "breadcrumb";
 @import "buttons";
 @import "cards";
@@ -30,6 +31,7 @@
 @import "navbar";
 @import "nav";
 @import "page_header";
+@import "popover";
 @import "tables";
 @import "tooltip";
 @import "type";
diff --git a/scipost/static/scipost/assets/js/notifications.js b/scipost/static/scipost/assets/js/notifications.js
new file mode 100644
index 0000000000000000000000000000000000000000..533c57a74a540a983911e7a9e267f2b03e6b34d9
--- /dev/null
+++ b/scipost/static/scipost/assets/js/notifications.js
@@ -0,0 +1,157 @@
+var notify_badge_class = 'live_notify_badge';
+var notify_menu_class = 'live_notify_list';
+var notify_api_url_count = '/notifications/api/unread_count/';
+var notify_api_url_list = '/notifications/api/list/';
+var notify_api_url_toggle_read = '/notifications/mark-toggle';
+var notify_fetch_count = '5';
+var notify_refresh_period = 15000;
+var consecutive_misfires = 0;
+var registered_functions = [fill_notification_badge];
+
+
+function initiate_popover(reinitiate=false) {
+    var notification_template = '<div class="popover notifications" role="tooltip"><div class="arrow"></div><h3 class="popover-header h2"></h3><div class="popover-body"></div></div>';
+
+    var get_notifications_title = function() {
+        return 'New notifications <div class="badge badge-warning live_notify_badge"></div><div class="mt-1"><small><a href="/notifications">See all my notifications</a></small></div>';
+    }
+
+    var get_notifications = function() {
+        var _str = '<ul id="notification-list" class="update_notifications list-group"><div class="w-100 text-center py-4"><i class="fa fa-circle-o-notch fa-2x fa-spin fa-fw"></i><span class="sr-only">Loading...</span></div></ul>';
+        get_notification_list();
+        return _str;
+    }
+
+    $('.popover [data-toggle="tooltip"]').tooltip('dispose')
+    $('#notifications_badge').popover('dispose').popover({
+        animation: false,
+        trigger: 'click',
+        title: get_notifications_title,
+        template: notification_template,
+        content: get_notifications,
+        container: 'body',
+        offset: '0, 9px',
+        placement: "bottom",
+        html: true
+    }).on('inserted.bs.popover', function() {
+        // Bloody js
+        setTimeout(function() {
+            $('.popover [data-toggle="tooltip"]').tooltip({
+                animation: false,
+                fallbackPlacement: 'clockwise',
+                placement: 'bottom'
+            });
+            $('.popover .actions a').on('click', function() {
+                mark_toggle(this)
+            })
+        }, 1000);
+    });
+    if (reinitiate) {
+        $('#notifications_badge').popover('show')
+    }
+}
+
+
+function mark_toggle(el) {
+    var r = new XMLHttpRequest();
+    r.addEventListener('readystatechange', function(event){
+        if (this.readyState == 4 && this.status == 200) {
+            fetch_api_data()
+            initiate_popover(reinitiate=true)
+        }
+    })
+    r.open("GET", notify_api_url_toggle_read +'/' + $(el).data('slug') + '/?json=1', true);
+    r.send();
+}
+
+
+function fill_notification_badge(data) {
+    var badges = document.getElementsByClassName(notify_badge_class);
+    if (badges) {
+        for(var i = 0; i < badges.length; i++){
+            badges[i].innerHTML = data.unread_count;
+        }
+    }
+}
+
+function get_notification_list() {
+    var data = fetch_api_data(notify_api_url_list, function(data) {
+
+        var messages = data.list.map(function (item) {
+            var message = '';
+            if(typeof item.actor !== 'undefined'){
+                message += '<strong>' + item.actor + '</strong>';
+            }
+            if(typeof item.verb !== 'undefined'){
+                message += " " + item.verb;
+            }
+            if(typeof item.target !== 'undefined'){
+                if(typeof item.forward_link !== 'undefined') {
+                    message += " <a href='" + item.forward_link + "'>" + item.target + "</a>";
+                } else {
+                    message += " " + item.target;
+                }
+            }
+            if(typeof item.timesince !== 'undefined'){
+                message += " <div class='text-muted'>" + item.timesince + " ago</div>";
+            }
+
+            if(item.unread) {
+                var mark_as_read = '<div class="actions"><a href="#" data-slug="' + item.slug + '"><i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i></a></div>';
+            } else {
+                var mark_as_read = '<div class="actions"><a href="#" data-slug="' + item.slug + '"><i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i></a></div>';
+            }
+            return '<li class="list-group-item ' + (item.unread ? ' active' : '') + '">' + mark_as_read + message + '</li>';
+        }).join('');
+
+        if (messages == '') {
+            messages = '<div class="text-center px-2 py-3"><i class="fa fa-star-o fa-2x" aria-hidden="true"></i><h3>You have no new notifications</h3></div>'
+        }
+
+        document.getElementById('notification-list').innerHTML = messages;
+    });
+}
+
+function fetch_api_data(url=null, callback=null) {
+    if (!url) {
+        var url = notify_api_url_count;
+    }
+
+    if (registered_functions.length > 0) {
+        //only fetch data if a function is setup
+        var r = new XMLHttpRequest();
+        r.addEventListener('readystatechange', function(event){
+            if (this.readyState === 4){
+                if (this.status === 200){
+                    consecutive_misfires = 0;
+                    var data = JSON.parse(r.responseText);
+                    registered_functions.forEach(function (func) { func(data); });
+                    if (callback) {
+                        return callback(data);
+                    }
+                }else{
+                    consecutive_misfires++;
+                }
+            }
+        })
+        r.open("GET", url +'?max='+notify_fetch_count, true);
+        r.send();
+    }
+    if (consecutive_misfires < 10) {
+        setTimeout(fetch_api_data,notify_refresh_period);
+    } else {
+        var badges = document.getElementsByClassName(notify_badge_class);
+        if (badges) {
+            for (var i = 0; i < badges.length; i++){
+                badges[i].innerHTML = "!";
+                badges[i].title = "Connection lost!"
+            }
+        }
+    }
+}
+
+setTimeout(fetch_api_data, 1000);
+
+$(function(){
+    initiate_popover();
+});
diff --git a/scipost/static/scipost/assets/js/scripts.js b/scipost/static/scipost/assets/js/scripts.js
index c17d95694a341b8d5df160393ae149ec8214f5f9..e16263ae7d94b480e54096a08385c61e00e893ba 100644
--- a/scipost/static/scipost/assets/js/scripts.js
+++ b/scipost/static/scipost/assets/js/scripts.js
@@ -1,4 +1,5 @@
 import tooltip from './tooltip.js';
+import notifications from './notifications.js';
 
 function hide_all_alerts() {
     $(".alert").fadeOut(300);
@@ -43,5 +44,5 @@ $(function(){
     // Auto-submit hook for general form elements
     $("form .auto-submit input").on('change', function(){
         $(this).parents('form').submit()
-    })
+    });
 });
diff --git a/scipost/static/scipost/assets/js/tooltip.js b/scipost/static/scipost/assets/js/tooltip.js
index 1542867e52fa2936c8a818e9fa08b33a293ed78c..f636b484f0f08f51adc9dcc92ad1d83a4f6985e2 100644
--- a/scipost/static/scipost/assets/js/tooltip.js
+++ b/scipost/static/scipost/assets/js/tooltip.js
@@ -1,5 +1,9 @@
-jQuery('[data-toggle="tooltip"]').tooltip({
-    animation: false,
-    fallbackPlacement: 'clockwise',
-    placement: 'auto'
-});
+var activate_tooltip = function() {
+    jQuery('[data-toggle="tooltip"]').tooltip({
+        animation: false,
+        fallbackPlacement: 'clockwise',
+        placement: 'auto'
+    });
+}
+
+activate_tooltip();
diff --git a/scipost/templates/scipost/bare_base.html b/scipost/templates/scipost/bare_base.html
index 663e6e2da76ba8114249f5374c40a1a68dc575a9..6b326d991c7cb9bd84acf26c42158e272b0e350f 100644
--- a/scipost/templates/scipost/bare_base.html
+++ b/scipost/templates/scipost/bare_base.html
@@ -21,6 +21,8 @@
     <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
     <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
 
+    <script src="https://use.fontawesome.com/1f60a2fe64.js"></script><!-- Temporary -->
+
     {% block headsup %}
     {% endblock headsup %}
   </head>
diff --git a/scipost/templates/scipost/navbar.html b/scipost/templates/scipost/navbar.html
index ed33013d14f50075d450eba4b2c9916846f1a638..2095054a8911f5e1770c891c825649bae7cd2bef 100644
--- a/scipost/templates/scipost/navbar.html
+++ b/scipost/templates/scipost/navbar.html
@@ -1,4 +1,9 @@
-<nav class="navbar navbar-scroll navbar-light main-nav">
+{% load staticfiles %}
+{% load notifications_tags %}
+{% load scipost_extras %}
+
+
+<nav class="navbar navbar-scroll navbar-light main-nav navbar-expand-lg">
     <div class="navbar-scroll-inner">
         <ul class="navbar-nav mr-auto">
           <li class="nav-item{% if request.path == '/' %} active{% endif %}">
@@ -21,9 +26,26 @@
           </li>
 
           {% if user.is_authenticated %}
+            {% if request.user|is_in_group:'Testers' %}
+              <li class="nav-item highlighted dropdown navbar-counter">
+                <div class="nav-link">
+                    <span class="user">{% if user.last_name %}{% if user.contributor %}{{ user.contributor.get_title_display }} {% endif %}{{ user.first_name }} {{ user.last_name }}{% else %}{{ user.username }}{% endif %}</span>
+                    <a href="javascript:;" class="d-inline-block ml-1 dropdown-toggle" id="notifications_badge">
+                        <i class="fa fa-bell-o" aria-hidden="true"></i>
+                        {% live_notify_badge classes="badge badge-pill badge-primary" %}
+                    </a>
+                    {% live_notify_list classes="update_notifications d-none" %}
+                </div>
+
+              </li>
+          {% else %}
               <li class="nav-item highlighted">
                 <span class="nav-link">Logged in as {{ user.username }}</span>
               </li>
+          {% endif %}
+              <li class="nav-item">
+                  <a class="nav-link" href="{% url 'scipost:logout' %}">Logout</a>
+              </li>
               {% if perms.scipost.can_oversee_refereeing %}
                   <li class="nav-item{% if '/submissions/admin' in request.path %} active{% endif %}">
                     <a class="nav-link" href="{% url 'submissions:admin' %}">Editorial Administration</a>
@@ -39,9 +61,6 @@
                     <a class="nav-link" href="{% url 'partners:dashboard' %}">Partner Page</a>
                   </li>
               {% endif %}
-              <li class="nav-item">
-                <a class="nav-link" href="{% url 'scipost:logout' %}">Logout</a>
-              </li>
           {% else %}
               <li class="nav-item{% if request.path == '/login/' %} active{% endif %}">
                 <a class="nav-link" href="{% url 'scipost:login' %}">Login</a>
diff --git a/webpack.config.js b/webpack.config.js
index 54f4a619ad056e5ab3e515ec862394c1aedff8f5..8de385d65a2d81ab99706192d981c190ef952239 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -48,10 +48,21 @@ module.exports = {
     },
     plugins: [
         new webpack.ProvidePlugin({
-            $: "jquery",
-            jQuery: "jquery",
-            "window.jQuery": "jquery",
-            Util: "exports-loader?Util!bootstrap/js/dist/util",
+            $: 'jquery',
+            jQuery: 'jquery',
+            Tether: 'tether',
+            'window.Tether': 'tether',
+            // Alert: 'exports-loader?Alert!bootstrap/js/dist/alert',
+            // Button: 'exports-loader?Button!bootstrap/js/dist/button',
+            // Carousel: 'exports-loader?Carousel!bootstrap/js/dist/carousel',
+            // Collapse: 'exports-loader?Collapse!bootstrap/js/dist/collapse',
+            // Dropdown: 'exports-loader?Dropdown!bootstrap/js/dist/dropdown',
+            // Modal: 'exports-loader?Modal!bootstrap/js/dist/modal',
+            // Popover: 'exports-loader?Popover!bootstrap/js/dist/popover',
+            // Scrollspy: 'exports-loader?Scrollspy!bootstrap/js/dist/scrollspy',
+            // Tab: 'exports-loader?Tab!bootstrap/js/dist/tab',
+            Tooltip: "exports-loader?Tooltip!bootstrap/js/dist/tooltip",
+            Util: 'exports-loader?Util!bootstrap/js/dist/util',
             Popper: ['popper.js', 'default'],
         }),
         new BundleTracker({
@@ -65,8 +76,8 @@ module.exports = {
             dry: false,
             exclude: []
         }),
-        new webpack.optimize.UglifyJsPlugin(),
-        new webpack.optimize.OccurrenceOrderPlugin(),
-        new webpack.optimize.AggressiveMergingPlugin()
+        // new webpack.optimize.UglifyJsPlugin(),
+        // new webpack.optimize.OccurrenceOrderPlugin(),
+        // new webpack.optimize.AggressiveMergingPlugin()
     ],
 }