diff --git a/greg/admin.py b/greg/admin.py
index 809f5bfb29bf7be379a11a2aaade54fa0643e1ec..c19389dce61a8aa022051be0338ce6a6f0e0d6ca 100644
--- a/greg/admin.py
+++ b/greg/admin.py
@@ -5,6 +5,7 @@ from greg.models import (
     OuIdentifier,
     Invitation,
     InvitationLink,
+    Notification,
     Person,
     Role,
     RoleType,
@@ -133,6 +134,18 @@ class InvitationLinkAdmin(VersionAdmin):
     readonly_fields = ("uuid",)
 
 
+class NotificationAdmin(VersionAdmin):
+    list_display = (
+        "id",
+        "object_type",
+        "identifier",
+        "operation",
+        "created",
+        "updated",
+        "issued_at",
+    )
+
+
 admin.site.register(Person, PersonAdmin)
 admin.site.register(Role, RoleAdmin)
 admin.site.register(RoleType, RoleTypeAdmin)
@@ -145,3 +158,4 @@ admin.site.register(SponsorOrganizationalUnit, SponsorOrganizationalUnitAdmin)
 admin.site.register(Invitation, InvitationAdmin)
 admin.site.register(InvitationLink, InvitationLinkAdmin)
 admin.site.register(OuIdentifier, IdentifierAdmin)
+admin.site.register(Notification, NotificationAdmin)
diff --git a/greg/signals.py b/greg/signals.py
index 3f52d8615325965feb9265fe6f069903195b79ae..9cf2a8ea5fcd96e141a3aaca98ed62bdf0205eb8 100644
--- a/greg/signals.py
+++ b/greg/signals.py
@@ -1,10 +1,11 @@
+import datetime
 import time
 import logging
 from typing import Dict, Union
 
 from django.db import models
 from django.dispatch import receiver
-
+from django_q.models import Schedule
 from greg.models import (
     Person,
     Role,
@@ -14,6 +15,7 @@ from greg.models import (
     Consent,
     ConsentType,
 )
+from greg.utils import date_to_datetime_midnight
 
 logger = logging.getLogger(__name__)
 
@@ -74,6 +76,37 @@ def _store_notification(identifier, object_type, operation, **kwargs):
     )
 
 
+def _queue_role_notification(role_id: int, created: str, attrname: str):
+    """Create a notification if the date in attrname of role is today"""
+    try:
+        instance = Role.objects.get(id=role_id)
+    except Role.DoesNotExist:
+        # Role was deleted, ignore it
+        return
+    if getattr(instance, attrname) != datetime.date.today():
+        # Date was changed after this task was queued, ignore it, the new one will take
+        # care of it
+        return
+    meta = _create_metadata(instance)
+    operation = "add" if created else "update"
+    _store_notification(
+        identifier=instance.id,
+        object_type=instance._meta.object_name,
+        operation=operation,
+        **meta,
+    )
+
+
+def _queue_role_start_notification(role_id: int, created: str):
+    """Create a notification for the role if start_date is today"""
+    _queue_role_notification(role_id, created, "start_date")
+
+
+def _queue_role_end_notification(role_id: int, created: str):
+    """Create a notification for the role if end_date is today"""
+    _queue_role_notification(role_id, created, "end_date")
+
+
 @receiver(models.signals.pre_save, dispatch_uid="add_changed_fields_callback")
 def add_changed_fields_callback(sender, instance, raw, *args, **kwargs):
     """
@@ -94,13 +127,28 @@ def add_changed_fields_callback(sender, instance, raw, *args, **kwargs):
 def save_notification_callback(sender, instance, created, *args, **kwargs):
     if not isinstance(instance, SUPPORTED_MODELS):
         return
+    # Queue future notifications on start and end date for roles
+    if isinstance(instance, Role):
+        if instance.start_date:
+            Schedule.objects.create(
+                func="greg.signals._queue_role_start_notification",
+                args=f"{instance.id},True",
+                next_run=date_to_datetime_midnight(instance.start_date),
+                schedule_type=Schedule.ONCE,
+            )
+        Schedule.objects.create(
+            func="greg.signals._queue_role_end_notification",
+            args=f"{instance.id},True",
+            next_run=date_to_datetime_midnight(instance.end_date),
+            schedule_type=Schedule.ONCE,
+        )
     meta = _create_metadata(instance)
     operation = "add" if created else "update"
     _store_notification(
         identifier=instance.id,
         object_type=instance._meta.object_name,
         operation=operation,
-        **meta
+        **meta,
     )
 
 
@@ -113,7 +161,7 @@ def delete_notification_callback(sender, instance, *args, **kwargs):
         identifier=instance.id,
         object_type=instance._meta.object_name,
         operation="delete",
-        **meta
+        **meta,
     )
 
 
@@ -146,7 +194,7 @@ def m2m_changed_notification_callback(
                 identifier=pc.id,
                 object_type=Consent._meta.object_name,
                 operation=operation,
-                **meta
+                **meta,
             )
     elif sender is Role:
         roles = []
@@ -161,7 +209,7 @@ def m2m_changed_notification_callback(
                 identifier=pr.id,
                 object_type=Role._meta.object_name,
                 operation=operation,
-                **meta
+                **meta,
             )
 
 
diff --git a/greg/tests/test_notifications.py b/greg/tests/test_notifications.py
index e5b68d0a6f4829681b3a3f663c2ba198131972cf..c0fd917da896d0866d72b3dc0729bc357c1a5014 100644
--- a/greg/tests/test_notifications.py
+++ b/greg/tests/test_notifications.py
@@ -15,22 +15,25 @@ from greg.models import (
 
 @pytest.fixture
 def org_unit_bar() -> OrganizationalUnit:
-    return OrganizationalUnit.objects.create()
+    org = OrganizationalUnit.objects.create()
+    return OrganizationalUnit.objects.get(pk=org.id)
 
 
 @pytest.fixture
 def sponsor() -> Sponsor:
-    return Sponsor.objects.create(feide_id="sponsor_id")
+    sp = Sponsor.objects.create(feide_id="sponsor_id")
+    return Sponsor.objects.get(pk=sp.id)
 
 
 @pytest.fixture
 def identity(person: Person) -> Identity:
-    return Identity.objects.create(
+    ident = Identity.objects.create(
         person=person,
         type=Identity.IdentityType.PASSPORT_NUMBER,
         source="Test",
         value="12345678901",
     )
+    return Identity.objects.get(pk=ident.id)
 
 
 @pytest.mark.django_db
diff --git a/greg/tests/test_signals.py b/greg/tests/test_signals.py
index 1570fd26c785cc228fe2e339c576841a3c16fb39..31e4da470a1f3503e152e3f432ef4ea5bdf27f45 100644
--- a/greg/tests/test_signals.py
+++ b/greg/tests/test_signals.py
@@ -1,6 +1,22 @@
+import datetime
 import pytest
 
-from greg.models import Notification
+from greg.models import Notification, Role
+from greg.signals import _queue_role_start_notification, _queue_role_end_notification
+
+
+@pytest.fixture
+def role_today(person, role_type_test_guest, sponsor_guy, unit_foo):
+    """A test role with end and start date today."""
+    role = Role.objects.create(
+        person=person,
+        type=role_type_test_guest,
+        sponsor=sponsor_guy,
+        orgunit=unit_foo,
+        start_date=datetime.date.today(),
+        end_date=datetime.date.today(),
+    )
+    return Role.objects.get(pk=role.id)
 
 
 @pytest.mark.django_db
@@ -17,3 +33,37 @@ def test_delete_signal_ou(unit_foo):
     assert Notification.objects.count() == 0
     unit_foo.delete()
     assert Notification.objects.count() == 0
+
+
+@pytest.mark.django_db
+def test_queue_role_start_notification(role_today):
+    """Check that a notification is produced if the role starts today"""
+    assert Notification.objects.all().count() == 3
+    _queue_role_start_notification(role_today.id, True)
+    assert Notification.objects.all().count() == 4
+
+
+@pytest.mark.django_db
+def test_queue_role_end_notification(role_today):
+    """Check that a notification is produced if the role ends today"""
+    assert Notification.objects.all().count() == 3
+    _queue_role_end_notification(role_today.id, True)
+    assert Notification.objects.all().count() == 4
+
+
+@pytest.mark.django_db
+def test_queue_role_end_notification_wrong_date(role_today):
+    """Check that a notification is produced if the role ends today"""
+    role_today.end_date = datetime.date.today() - datetime.timedelta(days=2)
+    role_today.save()
+    assert Notification.objects.all().count() == 4
+    _queue_role_end_notification(role_today.id, True)
+    assert Notification.objects.all().count() == 4
+
+
+@pytest.mark.django_db
+def test_queue_role_end_notification_role_deleted():
+    """Check that a notification is not produced if the role was deleted"""
+    assert Notification.objects.all().count() == 0
+    _queue_role_end_notification(10, True)
+    assert Notification.objects.all().count() == 0
diff --git a/greg/utils.py b/greg/utils.py
index 8408087a224f8877f6be4c619434eae081bccdce..a7a27171c3f441ebfebc0201c4fc621e0e76223c 100644
--- a/greg/utils.py
+++ b/greg/utils.py
@@ -1,5 +1,8 @@
 import re
-from datetime import date
+import typing
+from datetime import date, datetime
+
+from django.utils import timezone
 
 
 def camel_to_snake(s: str) -> str:
@@ -90,3 +93,12 @@ def _compute_checksum(input_digits: str) -> bool:
         k2 = 0
 
     return k1 < 10 and k2 < 10 and k1 == d[9] and k2 == d[10]
+
+
+def date_to_datetime_midnight(in_date: typing.Union[date, str]) -> datetime:
+    """Convert a date or str object to a datetime object with timezone utc"""
+    start_date = (
+        datetime.strptime(in_date, "%Y-%M-%d") if isinstance(in_date, str) else in_date
+    )
+    start = datetime.combine(start_date, datetime.min.time(), tzinfo=timezone.utc)
+    return start
diff --git a/mypy.ini b/mypy.ini
index 4b3066fc11e7e93a77557f2f7735c495474657ca..9b205574d67da2da925ac0b524dc1b3cd3dd76c0 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -20,6 +20,9 @@ ignore_missing_imports = True
 [mypy-django_extensions.db.fields]
 ignore_missing_imports = True
 
+[mypy-django_q.*]
+ignore_missing_imports = True
+
 [mypy-django_filters.*]
 ignore_missing_imports = True