diff --git a/src/api/serializers.py b/src/api/serializers.py
index 2434911e88e10b6c9caf6e33a4e7cb9292c95941..2ac02e23e81d31d04572c295787c421cc36da605 100644
--- a/src/api/serializers.py
+++ b/src/api/serializers.py
@@ -11,9 +11,10 @@ from core.models.assemblies import Assembly
 from core.models.badges import Badge, BadgeToken, BadgeTokenTimeConstraint
 from core.models.conference import Conference, ConferenceMember, ConferenceTrack
 from core.models.events import Event
+from core.models.messages import DirectMessage
 from core.models.metanavi import MetaNavItem
 from core.models.rooms import Room
-from core.models.users import UserTimelineEntry
+from core.models.users import PlatformUser, UserTimelineEntry
 
 
 class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
@@ -77,14 +78,20 @@ class HubModelSerializer(ValidatingModelSerializer):
             staff_only_fields = None
 
         # now check the user's conference membership based on the request
-        self.request_user = request_user = self.context['request'].user
-        self.conference_member = None
-        if request_user.is_authenticated:
-            with contextlib.suppress(ConferenceMember.DoesNotExist):
-                self.conference_member = ConferenceMember.objects.select_related('conference').get(
-                    conference=self.conference,
-                    user=request_user,
-                )
+        if 'request' not in self.context:
+            from django.contrib.auth.models import AnonymousUser
+
+            self.request_user = request_user = AnonymousUser()
+            self.conference_member = None
+        else:
+            self.request_user = request_user = self.context['request'].user
+            self.conference_member = None
+            if request_user.is_authenticated:
+                with contextlib.suppress(ConferenceMember.DoesNotExist):
+                    self.conference_member = ConferenceMember.objects.select_related('conference').get(
+                        conference=self.conference,
+                        user=request_user,
+                    )
 
         # store if the request's user has staff permissions in the conference (either direct or globally)
         self.is_staff = request_user.is_superuser or request_user.is_staff or (self.conference_member is not None and self.conference_member.is_staff)
@@ -297,3 +304,68 @@ class MetaNavItemSerializer(HubModelSerializer):
             'graphic_light',
             'graphic_dark',
         ]
+
+
+class PlatformUserVisibleSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = PlatformUser
+        fields = ['username', 'display_name']
+        read_only_fields = fields
+
+
+class PlatformUserByUsernameFieldSerializer(serializers.SlugRelatedField):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, slug_field='username', **kwargs)
+
+    def get_queryset(self):
+        return PlatformUser.objects.all()
+
+
+class DirectMessageSerializerSentShort(HubModelSerializer):
+    class Meta:
+        model = DirectMessage
+        fields = ['id', 'sender', 'recipient', 'timestamp', 'in_reply_to', 'subject']
+        read_only_fields = fields
+
+    sender = PlatformUserVisibleSerializer(read_only=True)
+    recipient = PlatformUserVisibleSerializer(read_only=True)
+
+
+class DirectMessageSerializerReceivedShort(HubModelSerializer):
+    class Meta:
+        model = DirectMessage
+        fields = [*DirectMessageSerializerSentShort.Meta.fields, 'has_responded']
+        read_only_fields = fields
+
+    sender = PlatformUserVisibleSerializer(read_only=True)
+    recipient = PlatformUserVisibleSerializer(read_only=True)
+
+
+class DirectMessageSerializerSent(HubModelSerializer):
+    class Meta:
+        model = DirectMessage
+        fields = [*DirectMessageSerializerSentShort.Meta.fields, 'body']
+        read_only_fields = fields
+
+    sender = PlatformUserVisibleSerializer(read_only=True)
+    recipient = PlatformUserVisibleSerializer(read_only=True)
+
+
+class DirectMessageSerializerReceived(HubModelSerializer):
+    class Meta:
+        model = DirectMessage
+        fields = [*DirectMessageSerializerReceivedShort.Meta.fields, 'body']
+        read_only_fields = [f for f in fields if f != 'was_read']
+
+    sender = PlatformUserVisibleSerializer(read_only=True)
+    recipient = PlatformUserVisibleSerializer(read_only=True)
+
+
+class DirectMessageSendSerializer(HubModelSerializer):
+    class Meta:
+        model = DirectMessage
+        fields = ['id', 'recipient', 'subject', 'body']
+        read_only_fields = ['id']
+        write_only_fields = [f for f in fields if f != 'id']
+
+    recipient = PlatformUserByUsernameFieldSerializer()
diff --git a/src/api/tests/__init__.py b/src/api/tests/__init__.py
index 8fb7973a17a79486bf8775d3b886df46eb305234..5f3d708f7a0cb7923a0e711001468b5e408cf603 100644
--- a/src/api/tests/__init__.py
+++ b/src/api/tests/__init__.py
@@ -1,7 +1,9 @@
 from .badges import *  # noqa: F401, F403
 from .bbb import *  # noqa: F401, F403
 from .engelsystem import *  # noqa: F401, F403
+from .events import *  # noqa: F401, F403
 from .map import *  # noqa: F401, F403
+from .messages import *  # noqa: F401, F403
 from .metrics import *  # noqa: F401, F403
 from .schedule import *  # noqa: F401, F403
 from .workadventure import *  # noqa: F401, F403
diff --git a/src/api/tests/events.py b/src/api/tests/events.py
new file mode 100644
index 0000000000000000000000000000000000000000..581e8f958a64f6ad37531bd4dfaf47ee045746b4
--- /dev/null
+++ b/src/api/tests/events.py
@@ -0,0 +1,73 @@
+import uuid
+from datetime import datetime, timedelta
+
+from pytz import UTC
+
+from django.test import Client, TestCase, override_settings
+from django.urls import reverse
+
+from core.models import Assembly, Conference, ConferenceMember, Event, PlatformUser
+
+TEST_CONF_ID = uuid.uuid4()
+
+
+@override_settings(SELECTED_CONFERENCE_ID=TEST_CONF_ID)
+class EventsTestCase(TestCase):
+    def setUp(self):
+        self.conference1 = Conference(id=TEST_CONF_ID, slug='foo', name='Foo Conference', is_public=True)
+        self.conference1.save()
+
+        self.human_user = PlatformUser(username='bernd', user_type=PlatformUser.Type.HUMAN)
+        self.human_user.save()
+        self.human_cm = ConferenceMember(conference=self.conference1, user=self.human_user)
+        self.human_cm.save()
+
+        assembly = Assembly(conference=self.conference1, name='DUMMY', slug='dummy', state_assembly=Assembly.State.ACCEPTED, is_official=True)
+        assembly.save()
+
+        self.event = Event(
+            conference=self.conference1,
+            slug='event1',
+            assembly=assembly,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
+            kind=Event.Kind.OFFICIAL,
+        )
+        self.event.save()
+
+    def test_EventMyFavorites(self):
+        c = Client()
+        resp = c.get(reverse('api:my-events'))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user)
+
+        resp = c.get(reverse('api:my-events'))
+        self.assertEqual(resp.json(), [])
+
+        self.human_user.favorite_events.add(self.event)
+        resp = c.get(reverse('api:my-events'))
+        self.assertEqual(resp.json(), [str(self.event.pk)])
+
+    def test_EventMyFavoritesSet_put(self):
+        c = Client()
+        resp = c.put(reverse('api:my-events-set', kwargs={'pk': str(uuid.uuid4())}))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user)
+
+        self.assertEqual(list(self.human_user.favorite_events.all()), [])
+        resp = c.put(reverse('api:my-events-set', kwargs={'pk': str(self.event.pk)}))
+        self.assertEqual(list(self.human_user.favorite_events.all()), [self.event])
+
+    def test_EventMyFavoritesSet_delete(self):
+        c = Client()
+        resp = c.delete(reverse('api:my-events-set', kwargs={'pk': str(uuid.uuid4())}))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user)
+
+        self.human_user.favorite_events.add(self.event)
+
+        self.assertEqual(list(self.human_user.favorite_events.all()), [self.event])
+        resp = c.delete(reverse('api:my-events-set', kwargs={'pk': str(self.event.pk)}))
+        self.assertEqual(list(self.human_user.favorite_events.all()), [])
diff --git a/src/api/tests/messages.py b/src/api/tests/messages.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdde3af340a281b68309a49f2c4e4ac42a1c5927
--- /dev/null
+++ b/src/api/tests/messages.py
@@ -0,0 +1,371 @@
+import uuid
+from datetime import datetime
+
+from freezegun import freeze_time
+from pytz import UTC
+
+from django.test import Client, TestCase, override_settings
+from django.urls import reverse
+from django.utils.timezone import localtime
+
+from core.models import Conference, ConferenceMember, DirectMessage, PlatformUser
+
+TEST_CONF_ID = uuid.uuid4()
+
+
+@override_settings(SELECTED_CONFERENCE_ID=TEST_CONF_ID)
+class MessagesTestCase(TestCase):
+    def setUp(self):
+        self.conference1 = Conference(id=TEST_CONF_ID, slug='foo', name='Foo Conference', is_public=True)
+        self.conference1.save()
+
+        self.human_user1 = PlatformUser(username='bernd', user_type=PlatformUser.Type.HUMAN)
+        self.human_user1.save()
+        self.human_cm1 = ConferenceMember(conference=self.conference1, user=self.human_user1)
+        self.human_cm1.save()
+
+        self.human_user2 = PlatformUser(username='björnd', user_type=PlatformUser.Type.HUMAN)
+        self.human_user2.save()
+        self.human_cm2 = ConferenceMember(conference=self.conference1, user=self.human_user2)
+        self.human_cm2.save()
+
+    def test_DirectMessagesReceived(self):
+        c = Client()
+        resp = c.get(reverse('api:my-messages-received'))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user1)
+
+        resp = c.get(reverse('api:my-messages-received'))
+        self.assertEqual(resp.json(), [])
+
+        dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user2,
+            recipient=self.human_user1,
+            timestamp=datetime(2020, 5, 4, 3, 2, 1, tzinfo=UTC),
+            subject='Test Message2',
+            body='Message Body2',
+        )
+        dm.save()
+        resp = c.get(reverse('api:my-messages-received'))
+        self.assertEqual(
+            resp.json(),
+            [
+                {
+                    'id': str(dm.pk),
+                    'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name},
+                    'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name},
+                    'timestamp': localtime(dm.timestamp).isoformat(),
+                    'in_reply_to': None,
+                    'subject': dm.subject,
+                    'has_responded': False,
+                }
+            ],
+        )
+
+        dm.has_responded = True
+        dm.deleted_by_sender = True
+        dm.save()
+        resp = c.get(reverse('api:my-messages-received'))
+        self.assertEqual(
+            resp.json(),
+            [
+                {
+                    'id': str(dm.pk),
+                    'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name},
+                    'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name},
+                    'timestamp': localtime(dm.timestamp).isoformat(),
+                    'in_reply_to': None,
+                    'subject': dm.subject,
+                    'has_responded': True,
+                }
+            ],
+        )
+
+        dm.deleted_by_recipient = True
+        dm.save()
+        resp = c.get(reverse('api:my-messages-received'))
+        self.assertEqual(resp.json(), [])
+
+    def test_DirectMessagesSent(self):
+        c = Client()
+        resp = c.get(reverse('api:my-messages-sent'))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user1)
+
+        resp = c.get(reverse('api:my-messages-sent'))
+        self.assertEqual(resp.json(), [])
+
+        dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user1,
+            recipient=self.human_user2,
+            timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC),
+            subject='Test Message',
+            body='Message Body',
+        )
+        dm.save()
+        resp = c.get(reverse('api:my-messages-sent'))
+        self.assertEqual(
+            resp.json(),
+            [
+                {
+                    'id': str(dm.pk),
+                    'sender': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name},
+                    'recipient': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name},
+                    'timestamp': localtime(dm.timestamp).isoformat(),
+                    'in_reply_to': None,
+                    'subject': dm.subject,
+                }
+            ],
+        )
+
+        dm.deleted_by_recipient = True
+        dm.save()
+        resp = c.get(reverse('api:my-messages-sent'))
+        self.assertEqual(len(resp.json()), 1)
+
+        dm.deleted_by_sender = True
+        dm.save()
+        resp = c.get(reverse('api:my-messages-sent'))
+        self.assertEqual(resp.json(), [])
+
+    def test_DirectMessageReceived(self):
+        dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user2,
+            recipient=self.human_user1,
+            timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC),
+            subject='Test Message',
+            body='Message Body',
+        )
+        dm.save()
+
+        other_dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user2,
+            recipient=self.human_user2,
+            timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC),
+            subject='Test Message',
+            body='Message Body',
+        )
+        other_dm.save()
+
+        c = Client()
+        resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user1)
+
+        resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(other_dm.pk)}))
+        self.assertEqual(resp.status_code, 404)
+
+        resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(
+            resp.json(),
+            {
+                'id': str(dm.pk),
+                'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name},
+                'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name},
+                'timestamp': localtime(dm.timestamp).isoformat(),
+                'in_reply_to': None,
+                'subject': dm.subject,
+                'body': dm.body,
+                'has_responded': False,
+            },
+        )
+
+        dm.has_responded = True
+        dm.deleted_by_sender = True
+        dm.save()
+        resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(
+            resp.json(),
+            {
+                'id': str(dm.pk),
+                'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name},
+                'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name},
+                'timestamp': localtime(dm.timestamp).isoformat(),
+                'in_reply_to': None,
+                'subject': dm.subject,
+                'body': dm.body,
+                'has_responded': True,
+            },
+        )
+
+        dm.deleted_by_recipient = True
+        dm.save()
+        resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(other_dm.pk)}))
+        self.assertEqual(resp.status_code, 404)
+
+    def test_DirectMessageSent(self):
+        dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user1,
+            recipient=self.human_user2,
+            timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC),
+            subject='Test Message',
+            body='Message Body',
+        )
+        dm.save()
+
+        other_dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user2,
+            recipient=self.human_user2,
+            timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC),
+            subject='Test Message',
+            body='Message Body',
+        )
+        other_dm.save()
+
+        c = Client()
+        resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user1)
+
+        resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(other_dm.pk)}))
+        self.assertEqual(resp.status_code, 404)
+
+        resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(
+            resp.json(),
+            {
+                'id': str(dm.pk),
+                'sender': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name},
+                'recipient': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name},
+                'timestamp': localtime(dm.timestamp).isoformat(),
+                'in_reply_to': None,
+                'subject': dm.subject,
+                'body': dm.body,
+            },
+        )
+
+        dm.deleted_by_recipient = True
+        dm.save()
+        resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(
+            resp.json(),
+            {
+                'id': str(dm.pk),
+                'sender': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name},
+                'recipient': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name},
+                'timestamp': localtime(dm.timestamp).isoformat(),
+                'in_reply_to': None,
+                'subject': dm.subject,
+                'body': dm.body,
+            },
+        )
+
+        dm.deleted_by_sender = True
+        dm.save()
+        resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(other_dm.pk)}))
+        self.assertEqual(resp.status_code, 404)
+
+    def test_DirectMessagesSend(self):
+        c = Client()
+        resp = c.post(reverse('api:my-send-message'))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user1)
+
+        with freeze_time(datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC)):
+            resp = c.post(
+                reverse('api:my-send-message'),
+                {
+                    'recipient': self.human_user2.username,
+                    'subject': 'Message Subject',
+                    'body': 'Message Body',
+                },
+            )
+        self.assertEqual(resp.status_code, 201)
+        self.assertEqual(DirectMessage.objects.all().count(), 1)
+        dm = DirectMessage.objects.get()
+        self.assertEqual(dm.conference, self.conference1)
+        self.assertEqual(dm.sender, self.human_user1)
+        self.assertEqual(dm.recipient, self.human_user2)
+        self.assertEqual(dm.timestamp, datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC))
+        self.assertEqual(dm.subject, 'Message Subject')
+        self.assertEqual(dm.body, 'Message Body')
+        self.assertFalse(dm.was_read)
+        self.assertFalse(dm.has_responded)
+        self.assertFalse(dm.deleted_by_recipient)
+
+        with freeze_time(datetime(2020, 3, 5, 7, 9, 11, tzinfo=UTC)):
+            resp = c.post(
+                reverse('api:my-send-message'),
+                {
+                    'recipient': self.human_user2.username,
+                    'subject': 'Message Subject2',
+                    'body': 'Message Body2',
+                },
+            )
+
+        self.assertEqual(DirectMessage.objects.all().count(), 2)
+        dm2 = DirectMessage.objects.get(pk=resp.json()['id'])
+        self.assertEqual(dm2.timestamp, datetime(2020, 3, 5, 7, 9, 11, tzinfo=UTC))
+
+        self.human_user1.shadow_banned = True
+        self.human_user1.save()
+        with freeze_time(datetime(2020, 3, 5, 7, 9, 11, tzinfo=UTC)):
+            resp = c.post(
+                reverse('api:my-send-message'),
+                {
+                    'recipient': self.human_user2.username,
+                    'subject': 'Message Subject, Sender is shadow banned',
+                    'body': 'Message Body, Sender is shadow banned',
+                },
+            )
+
+        self.assertEqual(DirectMessage.objects.all().count(), 3)
+        dm3 = DirectMessage.objects.get(pk=resp.json()['id'])
+        self.assertTrue(dm3.deleted_by_recipient)
+
+    def test_DirectMessageDelete(self):
+        c = Client()
+        resp = c.delete(reverse('api:my-delete-message', kwargs={'pk': str(uuid.uuid4())}))
+        self.assertEqual(resp.status_code, 403)
+        c.force_login(self.human_user1)
+
+        dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user1,
+            recipient=self.human_user2,
+            timestamp=datetime(2020, 5, 4, 3, 2, 1, tzinfo=UTC),
+            subject='Test Message',
+            body='Message Body',
+        )
+        dm.save()
+
+        resp = c.delete(reverse('api:my-delete-message', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(resp.status_code, 204)
+        dm.refresh_from_db()
+        self.assertTrue(dm.deleted_by_sender)
+        self.assertFalse(dm.deleted_by_recipient)
+
+        dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user2,
+            recipient=self.human_user1,
+            timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC),
+            subject='Test Message2',
+            body='Message Body2',
+        )
+        dm.save()
+        resp = c.delete(reverse('api:my-delete-message', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(resp.status_code, 204)
+        dm.refresh_from_db()
+        self.assertFalse(dm.deleted_by_sender)
+        self.assertTrue(dm.deleted_by_recipient)
+
+        dm = DirectMessage(
+            conference=self.conference1,
+            sender=self.human_user2,
+            recipient=self.human_user1,
+            timestamp=datetime(2020, 2, 2, 2, 2, 2, tzinfo=UTC),
+            deleted_by_sender=True,
+            subject='Test Message3',
+            body='Message Body3',
+        )
+        dm.save()
+        resp = c.delete(reverse('api:my-delete-message', kwargs={'pk': str(dm.pk)}))
+        self.assertEqual(resp.status_code, 204)
+        self.assertEqual(DirectMessage.objects.filter(pk=dm.pk).count(), 0)
diff --git a/src/api/urls.py b/src/api/urls.py
index d10c632e9ea06adbb8e5905bf619a5fbd03f60ee..8b618b9743729cffacb04f7193e364b5eb4cd352 100644
--- a/src/api/urls.py
+++ b/src/api/urls.py
@@ -4,7 +4,7 @@ from rest_framework.urlpatterns import format_suffix_patterns
 from django.conf import settings
 from django.urls import path
 
-from .views import api_root, assemblies, badges, bbb, conferencemember, conferences, events, maps, metanav, rooms, schedule, users, workadventure
+from .views import api_root, assemblies, badges, bbb, conferencemember, conferences, events, maps, messages, metanav, rooms, schedule, users, workadventure
 
 app_name = 'api'
 urlpatterns = [
@@ -13,6 +13,14 @@ urlpatterns = [
     path('me', users.profile, name='profile'),
     path('me/badges.zip', users.BadgeExportView.as_view(), name='badge-export'),
     path('me/badges', users.badges, name='badges'),
+    path('me/events', events.EventMyFavorites.as_view(), name='my-events'),
+    path('me/events/<uuid:pk>', events.EventMyFavoritesSet.as_view(), name='my-events-set'),
+    path('me/received-messages/', messages.DirectMessagesReceived.as_view(), name='my-messages-received'),
+    path('me/sent-messages/', messages.DirectMessagesSent.as_view(), name='my-messages-sent'),
+    path('me/received-messages/<uuid:pk>', messages.DirectMessageReceived.as_view(), name='my-message-received'),
+    path('me/sent-messages/<uuid:pk>', messages.DirectMessageSent.as_view(), name='my-message-sent'),
+    path('me/send-message', messages.DirectMessageSend.as_view(), name='my-send-message'),
+    path('me/delete-message/<uuid:pk>', messages.DirectMessageDelete.as_view(), name='my-delete-message'),
     path('me/friends', users.friends, name='friends'),
     path('me/timeline', users.UserTimelineList.as_view(), name='timeline-list'),
     # conference-specific views
diff --git a/src/api/views/events.py b/src/api/views/events.py
index 44aec250388c25dbbe17f0e5cb5d2264e22730df..dccc9dd8ee1b305a599b6fb831013b0bc75714b9 100644
--- a/src/api/views/events.py
+++ b/src/api/views/events.py
@@ -1,4 +1,6 @@
-from rest_framework import generics
+from rest_framework import generics, permissions
+from rest_framework.response import Response
+from rest_framework.views import APIView
 
 from django.shortcuts import get_object_or_404
 
@@ -21,3 +23,22 @@ class EventDetail(ConferenceSlugMixin, generics.RetrieveAPIView):
     def get_object(self, **kwargs):
         event_id = self.request.resolver_match.kwargs['pk']
         return get_object_or_404(Event.objects.conference_accessible(conference=self.conference), pk=event_id)
+
+
+class EventMyFavorites(ConferenceSlugMixin, generics.ListAPIView):
+    permission_classes = [permissions.IsAuthenticated]
+
+    def list(self, request, *args, **kwargs):
+        return Response(self.request.user.favorite_events.values_list('pk', flat=True))
+
+
+class EventMyFavoritesSet(ConferenceSlugMixin, APIView):
+    permission_classes = [permissions.IsAuthenticated]
+
+    def put(self, request, *args, pk=None, **kwargs):
+        self.request.user.favorite_events.add(*Event.objects.conference_accessible(conference=self.conference).filter(pk=pk))
+        return Response(True)
+
+    def delete(self, request, *args, pk=None, **kwargs):
+        self.request.user.favorite_events.remove(*Event.objects.conference_accessible(conference=self.conference).filter(pk=pk))
+        return Response(True)
diff --git a/src/api/views/messages.py b/src/api/views/messages.py
new file mode 100644
index 0000000000000000000000000000000000000000..01a3ecf3266d9f84dc9f302736355978944ee579
--- /dev/null
+++ b/src/api/views/messages.py
@@ -0,0 +1,101 @@
+from rest_framework import generics, permissions
+
+from django.db.models import Q
+
+from core.models.messages import DirectMessage
+
+from ..serializers import (
+    DirectMessageSendSerializer,
+    DirectMessageSerializerReceived,
+    DirectMessageSerializerReceivedShort,
+    DirectMessageSerializerSent,
+    DirectMessageSerializerSentShort,
+)
+from .mixins import ConferenceSlugMixin
+
+
+class DirectMessagesReceived(ConferenceSlugMixin, generics.ListAPIView):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = DirectMessageSerializerReceivedShort
+
+    def get_queryset(self):
+        return (
+            DirectMessage.objects.filter(conference=self.conference, recipient=self.request.user, deleted_by_recipient=False)
+            .select_related('recipient', 'sender')
+            .order_by('-timestamp')
+        )
+
+
+class DirectMessagesSent(ConferenceSlugMixin, generics.ListAPIView):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = DirectMessageSerializerSentShort
+
+    def get_queryset(self):
+        return (
+            DirectMessage.objects.filter(conference=self.conference, sender=self.request.user, deleted_by_sender=False)
+            .select_related('recipient', 'sender')
+            .order_by('-timestamp')
+        )
+
+
+class DirectMessageReceived(ConferenceSlugMixin, generics.RetrieveUpdateAPIView):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = DirectMessageSerializerReceived
+
+    def get_queryset(self):
+        return (
+            DirectMessage.objects.filter(conference=self.conference, recipient=self.request.user, deleted_by_recipient=False)
+            .select_related('recipient', 'sender')
+            .order_by('-timestamp')
+        )
+
+
+class DirectMessageSent(ConferenceSlugMixin, generics.RetrieveAPIView):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = DirectMessageSerializerSent
+
+    def get_queryset(self):
+        return (
+            DirectMessage.objects.filter(conference=self.conference, sender=self.request.user, deleted_by_sender=False)
+            .select_related('recipient', 'sender')
+            .order_by('-timestamp')
+        )
+
+
+class DirectMessageSend(ConferenceSlugMixin, generics.CreateAPIView):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = DirectMessageSendSerializer
+
+    def perform_create(self, serializer: DirectMessageSendSerializer):
+        serializer.save(
+            conference=self.conference,
+            sender=self.request.user,
+            deleted_by_recipient=self.request.user != serializer.validated_data['recipient'] and self.request.user.shadow_banned,
+        )
+
+
+class DirectMessageDelete(ConferenceSlugMixin, generics.DestroyAPIView):
+    permission_classes = [permissions.IsAuthenticated]
+
+    def get_queryset(self):
+        return DirectMessage.objects.filter(
+            Q(conference=self.conference, recipient=self.request.user, deleted_by_recipient=False)
+            | Q(conference=self.conference, sender=self.request.user, deleted_by_sender=False)
+        )
+
+    def perform_destroy(self, instance: DirectMessage):
+        if instance.sender == self.request.user:
+            if instance.deleted_by_recipient:
+                instance.delete()
+                return
+
+            instance.deleted_by_sender = True
+            instance.save(update_fields=['deleted_by_sender'])
+
+        elif instance.recipient == self.request.user:
+            if instance.deleted_by_sender:
+                instance.delete()
+                return
+
+            instance.deleted_by_recipient = True
+            instance.save(update_fields=['deleted_by_recipient'])