diff --git a/aleksis/core/feeds.py b/aleksis/core/feeds.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ba3aed4d5796c612a27c64ac99bed4484a89090
--- /dev/null
+++ b/aleksis/core/feeds.py
@@ -0,0 +1,38 @@
+from typing import Any
+
+from django.conf import settings
+from django.core.handlers.wsgi import WSGIRequest
+from django.utils.functional import classproperty
+from django_ical.views import ICalFeed
+
+from aleksis.core.util.core_helpers import get_site_preferences
+
+
+class PersonalICalFeedBase(ICalFeed):
+    @property
+    def product_id(self):
+        lang = self.request.LANGUAGE_CODE
+        return f"-//AlekSIS®//{get_site_preferences()['general__title']}//{lang}"
+
+    link = settings.BASE_URL
+    timezone = settings.TIME_ZONE
+    person = None
+    request = None
+
+    def get_object(self, request: WSGIRequest, *args: Any, **kwargs: Any) -> None:
+        if kwargs.get("person"):
+            self.person = kwargs.pop("person")
+        self.request = request
+        return super().get_object(request, *args, **kwargs)
+
+    @classproperty
+    def subclasses_list(cls):
+        return cls.__subclasses__()
+
+    @classproperty
+    def subclasses_dict(cls):
+        return {subclass.__name__: subclass for subclass in cls.subclasses_list}
+
+    @classproperty
+    def subclass_choices(cls):
+        return [(subclass.__name__, f"{subclass.title} – {subclass.description}") for subclass in cls.subclasses_list]
diff --git a/pyproject.toml b/pyproject.toml
index 5d3af51b6451515a6e226e3f3025ff79f692fbc0..5450eaeed7fc371b47fdcf9be589cf48c3108d0d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -126,6 +126,7 @@ python-gnupg = "^0.4.7"
 sentry-sdk = {version = "^1.4.3", optional = true}
 django-cte = "^1.1.5"
 pycountry = "^22.0.0"
+django-ical = "^1.8.3"
 django-iconify = "^0.1.0"
 customidenticon = "^0.1.5"