diff --git a/comments/models/comment.py b/comments/models/comment.py
index 4b8aec1a15d7154ba1eec9b58734ae9fd34e5f77..9f024189dc4412b70fec89c60b9571c7a55df2e9 100644
--- a/comments/models/comment.py
+++ b/comments/models/comment.py
@@ -34,6 +34,7 @@ from openbook_common.utils.model_loaders import (
     get_user_model,
 )
 from openbook_moderation.models import ModeratedObject
+from openbook_notifications.services import NotificationsService
 
 from .comment_reaction import CommentReaction
 from .enabled_commentable_type import EnabledCommentableType
@@ -182,7 +183,11 @@ class Comment(ModelWithUUID):
         return post_comment
 
     def react(self, reactor, reaction_type):
-        return CommentReaction.create_reaction(reactor=reactor, reaction_type=reaction_type, comment=self)
+        reaction = CommentReaction.create_reaction(reactor=reactor, reaction_type=reaction_type, comment=self)
+        NotificationsService.send_comment_reaction_notification(
+            comment=self, reactor=reactor, recipient=self.commenter
+        )
+        return reaction
 
     def clear_reaction(self, reactor, reaction_type):
         return CommentReaction.remove_reaction(reactor=reactor, reaction_type=reaction_type, comment=self)
diff --git a/comments/tests/test_graphql.py b/comments/tests/test_graphql.py
index d9f0b2ea579e35ddc249c35d555039da7424ef5f..2d8fb452b609a5967266a956176689e74e7402d1 100644
--- a/comments/tests/test_graphql.py
+++ b/comments/tests/test_graphql.py
@@ -1,5 +1,6 @@
 import logging
 from unittest import TestCase, mock
+from unittest.mock import patch
 
 import pytest
 from django.core.management import call_command
@@ -18,8 +19,10 @@ from openbook_common.tests.helpers import (
     make_post,
     make_space_post_comment,
     make_user,
+    make_community,
 )
 from openbook_insights.models.discussion import Discussion
+from openbook_notifications.notifications import CommentReactionNotification
 
 from .helpers import (
     get_comment_by_id,
@@ -183,12 +186,13 @@ class TestPostComments(TestCase):
 
     def test_should_delete_comment(self):
         posting_user = make_user()
+        space = make_community(creator=posting_user)
         comment_user = make_user()
 
         post_text = make_fake_post_text()
         post_title = make_fake_post_title()
 
-        post = posting_user.create_post(text=post_text, title=post_title, is_public=True)
+        post = posting_user.create_post(text=post_text, title=post_title, is_public=True, community_id=space.id)
         comment = post.comment(text="comment text", commenter=comment_user)
         reaction = comment.react(posting_user, "HEART")
         post.comment(text="comment text 2", commenter=comment_user)
@@ -203,13 +207,14 @@ class TestPostComments(TestCase):
 
     def test_should_not_be_able_to_delete_others_comments(self):
         posting_user = make_user()
+        space = make_community(creator=posting_user)
         comment_user = make_user()
         other_user = make_user()
 
         post_text = make_fake_post_text()
         post_title = make_fake_post_title()
 
-        post = posting_user.create_post(text=post_text, title=post_title, is_public=True)
+        post = posting_user.create_post(text=post_text, title=post_title, is_public=True, community_id=space.id)
         comment = post.comment(text="comment text", commenter=comment_user)
         reaction = comment.react(posting_user, "HEART")
         post.comment(text="comment text 2", commenter=comment_user)
@@ -223,12 +228,13 @@ class TestPostComments(TestCase):
 
     def test_should_not_be_able_to_delete_a_comment_that_does_not_exist(self):
         posting_user = make_user()
+        space = make_community(creator=posting_user)
         comment_user = make_user()
 
         post_text = make_fake_post_text()
         post_title = make_fake_post_title()
 
-        post = posting_user.create_post(text=post_text, title=post_title, is_public=True)
+        post = posting_user.create_post(text=post_text, title=post_title, is_public=True, community_id=space.id)
         comment = post.comment(text="comment text", commenter=comment_user)
         reaction = comment.react(posting_user, "HEART")
         post.comment(text="comment text 2", commenter=comment_user)
@@ -240,17 +246,21 @@ class TestPostComments(TestCase):
         assert comment.id in post.comments.values_list("id", flat=True)
         assert reaction.id in CommentReaction.objects.filter(post_comment=comment).values_list("id", flat=True)
 
-    def test_should_be_able_to_react_to_a_comment(self):
+    @patch.object(CommentReactionNotification, "send")
+    def test_should_be_able_to_react_to_a_comment(self, mock_send_comment_reaction_notification):
         posting_user = make_user()
+        space = make_community(creator=posting_user)
         other_user = make_user(name="Other")
 
         post_text = make_fake_post_text()
         post_title = make_fake_post_title()
 
-        post = posting_user.create_post(text=post_text, title=post_title, is_public=True)
+        post = posting_user.create_post(text=post_text, title=post_title, is_public=True, community_id=space.id)
         comment = post.comment(text="comment text", commenter=other_user)
 
         react_to_post_comment(comment, "SET", posting_user)
+        mock_send_comment_reaction_notification.assert_called_once()
+
         executed = react_to_post_comment(comment, "SET", posting_user)  # setting twice is same as setting once
         updated_post_comment = executed.data["updatePostCommentReaction"]
 
diff --git a/feed_posts/enums.py b/feed_posts/enums.py
index 844b0163e2c25f0e2340db5a4ecf293cbb882e0b..67a750ab93a244df45b0733c6f783e84db484ddb 100644
--- a/feed_posts/enums.py
+++ b/feed_posts/enums.py
@@ -6,3 +6,9 @@ from django.db.models import TextChoices
 class VisibilityLevel(TextChoices):
     PUBLIC = ("PUBLIC",)
     REGISTERED_USERS_ONLY = ("REGISTERED_USERS_ONLY",)
+
+
+@strawberry.enum
+class AspectRatio(TextChoices):
+    SIXTEEN_NINE = ("SIXTEEN_NINE",)
+    ONE_ONE = ("ONE_ONE",)
diff --git a/feed_posts/migrations/0016_feedpostimage_aspect_ratio.py b/feed_posts/migrations/0016_feedpostimage_aspect_ratio.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e31b4630c31ff59ca6d60218865fcd9aef7170c
--- /dev/null
+++ b/feed_posts/migrations/0016_feedpostimage_aspect_ratio.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.0.8 on 2024-09-23 09:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("feed_posts", "0015_alter_feedpost_language_code"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="feedpostimage",
+            name="aspect_ratio",
+            field=models.CharField(
+                blank=True,
+                choices=[("SIXTEEN_NINE", "Sixteen Nine"), ("ONE_ONE", "One One")],
+                max_length=20,
+                null=True,
+            ),
+        ),
+    ]
diff --git a/feed_posts/models.py b/feed_posts/models.py
index 8e2d448c60bc31fc56d0650f440f8e77d4cdd366..0e1cee9ee5e35744665c1d2b1ae90b0b0a1996c6 100644
--- a/feed_posts/models.py
+++ b/feed_posts/models.py
@@ -13,7 +13,7 @@ from rest_framework.exceptions import ValidationError
 
 from comments.models import Comment
 from comments.models.commentable_notification_context import CommentNotificationMixin, CommentNotificationContext
-from feed_posts.enums import VisibilityLevel
+from feed_posts.enums import VisibilityLevel, AspectRatio
 from feed_posts.helpers import upload_to_feed_post_images_directory, upload_to_new_feed_post_images_directory
 from openbook import settings
 from openbook_auth.models import User, UserRole
@@ -167,6 +167,7 @@ class FeedPostImage(ModelWithUUID):
     order = models.PositiveIntegerField()
     uploaded_at = models.DateTimeField(editable=False, auto_now_add=True)
     alt_text = models.TextField(max_length=settings.CONTENT_IMAGE_ALT_TEXT_MAX_LENGTH, blank=True, null=True)
+    aspect_ratio = models.CharField(max_length=20, choices=AspectRatio.choices, blank=True, null=True)
 
     class Meta:
         ordering = ["order"]
diff --git a/feed_posts/repositories.py b/feed_posts/repositories.py
index 5168d9fb74d94b6e2bed21923040a74e032d861d..a02d3ea72505a5c157774a1df70e892a913fe9ef 100644
--- a/feed_posts/repositories.py
+++ b/feed_posts/repositories.py
@@ -149,6 +149,7 @@ class FeedPostRepository:
                 image=obj["image"],
                 order=obj["order"],
                 alt_text=obj.get("alt_text") or None,
+                aspect_ratio=obj.get("aspect_ratio") or None,
             )
             image.save()
 
@@ -177,6 +178,7 @@ class FeedPostRepository:
                     if image_data:
                         image.order = image_data.get("order", image.order)
                         image.alt_text = image_data.get("alt_text", image.alt_text)
+                        image.aspect_ratio = image_data.get("aspect_ratio", image.aspect_ratio)
                         image.save()
 
         if new_images is not None:
diff --git a/feed_posts/schema/types.py b/feed_posts/schema/types.py
index f224c32d12a3296fcd84e125435c5d67ec8383a9..65309aad95e1af6d92a319b5ba523b67a7fdf317 100644
--- a/feed_posts/schema/types.py
+++ b/feed_posts/schema/types.py
@@ -6,7 +6,7 @@ import strawberry_django
 from strawberry.file_uploads import Upload
 
 from comments.models.enabled_commentable_type import EnabledCommentableType
-from feed_posts.enums import VisibilityLevel
+from feed_posts.enums import VisibilityLevel, AspectRatio
 from feed_posts.models import (
     FeedPost as FeedPostModel,
     FeedPostReaction as FeedPostReactionModel,
@@ -28,6 +28,7 @@ class FeedPostImage:
     image_blurhash: str
     order: int
     alt_text: Optional[str]
+    aspect_ratio: Optional[AspectRatio]
 
 
 @strawberry_django.type(FeedPostModel)
@@ -106,6 +107,7 @@ class FeedPostImageInput:
     image: Upload
     order: int
     alt_text: Optional[str] = strawberry.UNSET
+    aspect_ratio: Optional[AspectRatio] = strawberry.UNSET
 
 
 @strawberry.input
@@ -129,6 +131,7 @@ class FeedPostImageMetadata:
     id: uuid.UUID
     order: int
     alt_text: Optional[str] = strawberry.UNSET
+    aspect_ratio: Optional[AspectRatio] = strawberry.UNSET
 
 
 @strawberry.input
diff --git a/feed_posts/serializers.py b/feed_posts/serializers.py
index 3dfddc53a9b625a8f45f6ec3336fbd313744fc07..374aa769e55641ac6d5753f1085006dab903883a 100644
--- a/feed_posts/serializers.py
+++ b/feed_posts/serializers.py
@@ -1,6 +1,6 @@
 from rest_framework import serializers
 
-from feed_posts.enums import VisibilityLevel
+from feed_posts.enums import VisibilityLevel, AspectRatio
 from feed_posts.validators import (
     validate_topic_exists_by_id,
     validate_feed_post_category_exists_by_id,
@@ -23,6 +23,7 @@ class FeedPostImageSerializer(serializers.Serializer):
     alt_text = serializers.CharField(
         required=False, max_length=settings.CONTENT_IMAGE_ALT_TEXT_MAX_LENGTH, allow_blank=True
     )
+    aspect_ratio = serializers.ChoiceField(required=False, choices=AspectRatio.choices)
 
 
 class FeedPostImageMetadataSerializer(serializers.Serializer):
@@ -31,6 +32,7 @@ class FeedPostImageMetadataSerializer(serializers.Serializer):
     alt_text = serializers.CharField(
         required=False, max_length=settings.CONTENT_IMAGE_ALT_TEXT_MAX_LENGTH, allow_blank=True
     )
+    aspect_ratio = serializers.ChoiceField(required=False, choices=AspectRatio.choices)
 
 
 class CreateFeedPostSerializer(serializers.Serializer):
diff --git a/feed_posts/services.py b/feed_posts/services.py
index 39e47e6a0834b11b1ea65edfa321a145bfa2853c..a240774cf61ce674017fbbef63cf1588cb82e537 100644
--- a/feed_posts/services.py
+++ b/feed_posts/services.py
@@ -8,6 +8,7 @@ from openbook_common.helpers import extract_urls_from_string
 from openbook_common.models import LinkPreview
 from openbook_common.tracking import track, TrackingEvent
 from openbook_common.utils.helpers import extract_usernames_from_string
+from openbook_notifications.services import NotificationsService
 from openbook_terms.models import FeedPostCategory, Topic
 
 
@@ -135,7 +136,11 @@ class FeedPostReactionService:
         return self.repository.get_reaction_by_user(feed_post=feed_post, reactor=reactor)
 
     def create_reaction(self, feed_post, reactor, reaction_type):
-        return self.repository.create_reaction(feed_post=feed_post, reactor=reactor, reaction_type=reaction_type)
+        reaction = self.repository.create_reaction(feed_post=feed_post, reactor=reactor, reaction_type=reaction_type)
+        NotificationsService.send_post_reaction_notification(
+            post=feed_post, reactor=reactor, recipient=feed_post.creator
+        )
+        return reaction
 
     def remove_reaction(self, feed_post, reactor, reaction_type):
         return self.repository.remove_reaction(feed_post=feed_post, reactor=reactor, reaction_type=reaction_type)
diff --git a/feed_posts/tests/helpers.py b/feed_posts/tests/helpers.py
index 7c8b7f28f3c0bae85c498cff9b061a07c3d94072..cb99c26d5c34fc504cf3bb3bedcaf63604dfd4a7 100644
--- a/feed_posts/tests/helpers.py
+++ b/feed_posts/tests/helpers.py
@@ -40,6 +40,7 @@ def create_feed_post(user, input):
                     image
                     order
                     altText
+                    aspectRatio
                 }
             }
         }
@@ -84,6 +85,7 @@ def update_feed_post(user, input):
                     image
                     order
                     altText
+                    aspectRatio
                 }
             }
         }
diff --git a/feed_posts/tests/test_graphql.py b/feed_posts/tests/test_graphql.py
index 7573356f910898878ce8bdf229860129b4e8e212..32d62f8b12418e242fcf5eba17085a6689a07dea 100644
--- a/feed_posts/tests/test_graphql.py
+++ b/feed_posts/tests/test_graphql.py
@@ -1,4 +1,5 @@
 import logging
+from unittest.mock import patch
 
 import pytest
 import pytz
@@ -7,7 +8,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
 from django.test import AsyncClient, TestCase
 from faker import Faker
 
-from feed_posts.enums import VisibilityLevel
+from feed_posts.enums import VisibilityLevel, AspectRatio
 from feed_posts.repositories import FeedPostRepository
 from feed_posts.tests.helpers import (
     create_feed_post,
@@ -32,6 +33,7 @@ from openbook_common.tests.helpers import (
     make_community,
     make_feed_post_founder_update_category,
 )
+from openbook_notifications.notifications import PostReactionNotification
 
 logger = logging.getLogger(__name__)
 fake = Faker()
@@ -148,6 +150,7 @@ class TestFeedPosts(TestCase):
                     "image": SimpleUploadedFile(name="image_2.jpg", content=simple_jpeg_bytes),
                     "order": 1,
                     "altText": "image description",
+                    "aspectRatio": AspectRatio.ONE_ONE,
                 }
             ],
             "location": "Munich, Germany",
@@ -169,6 +172,7 @@ class TestFeedPosts(TestCase):
                     "image": SimpleUploadedFile(name="image_3.jpg", content=simple_jpeg_bytes),
                     "order": 0,
                     "altText": "image description",
+                    "aspectRatio": AspectRatio.ONE_ONE,
                 }
             ],
             "location": "Munich, Germany",
@@ -419,7 +423,14 @@ class TestFeedPosts(TestCase):
 
         post_id = mutation_response.data["createFeedPost"]["id"]
         images = mutation_response.data["createFeedPost"]["images"]
-        existing_images = [{"id": images[0]["id"], "order": 9, "altText": "new image description"}]
+        existing_images = [
+            {
+                "id": images[0]["id"],
+                "order": 9,
+                "altText": "new image description",
+                "aspectRatio": AspectRatio.SIXTEEN_NINE,
+            }
+        ]
         input_data = update_input_data(
             post_id=post_id,
             input_data=self.registered_users_only_feed_post_update_input_data,
@@ -562,7 +573,8 @@ class TestFeedPosts(TestCase):
 
         assert "Unauthorized" in str(response.errors[0].message)
 
-    def test_can_react_to_feed_post_if_authenticated_user(self):
+    @patch.object(PostReactionNotification, "send")
+    def test_can_react_to_feed_post_if_authenticated_user(self, mock_send_post_reaction_notification):
         mutation_response = create_feed_post(user=self.user, input=self.public_feed_post_input_data)
         assert mutation_response.errors is None
 
@@ -579,6 +591,7 @@ class TestFeedPosts(TestCase):
 
         assert data["myReaction"] == {"HEART": 1}
         assert data["reactions"] == {"HEART": 1}
+        mock_send_post_reaction_notification.assert_called_once()
 
     def test_can_remove_reaction_to_feed_post_if_authenticated_user(self):
         mutation_response = create_feed_post(user=self.user, input=self.public_feed_post_input_data)
diff --git a/openbook/graphql_schema.py b/openbook/graphql_schema.py
index 741dcb13072969a7e11bdf71676080119649dda1..8b2c6bd0b5a4abf5741e3950937b5d8637d1053b 100644
--- a/openbook/graphql_schema.py
+++ b/openbook/graphql_schema.py
@@ -58,6 +58,10 @@ class HoliSchema(strawberry.Schema):
                     GraphQLErrorCodes.UNAUTHORIZED,
                 ]
             )
+            and not (
+                # don't error log UUID parsing failures of user parameters (-> BAD_USER_INPUT)
+                error.message.startswith("Value cannot represent a UUID:")
+            )
         ]
         super().process_errors(real_errors, execution_context)
 
diff --git a/openbook_auth/models.py b/openbook_auth/models.py
index 5e097c8235505b71c6b1ccdd7dcd365c551eebf0..d3e2f8ab35b9690edb8d3e344f8dbe3512c55d3b 100644
--- a/openbook_auth/models.py
+++ b/openbook_auth/models.py
@@ -2941,6 +2941,7 @@ class User(ModelWithUUID, AbstractUser):
         is_public=None,
         circles_ids=None,
         language=None,
+        community_id=None,
     ):
         Post = get_post_model()
         return Post.create_post(
@@ -2954,6 +2955,7 @@ class User(ModelWithUUID, AbstractUser):
             is_draft=is_draft,
             circles_ids=circles_ids,
             language=language,
+            community_id=community_id,
         )
 
     def create_poll(
diff --git a/openbook_notifications/notifications.py b/openbook_notifications/notifications.py
index 2d51e1e8c717120b94786e9afe74abc5b73a8363..dc0d11dc89356a72ced9f34a044c0aab99eaeae2 100644
--- a/openbook_notifications/notifications.py
+++ b/openbook_notifications/notifications.py
@@ -26,6 +26,8 @@ from openbook_notifications.payloads import (
     SpaceMembershipRequestPayload,
     SpaceNewPostPayload,
     PollUpdatesPayload,
+    PostReactionPayload,
+    CommentReactionPayload,
 )
 from openbook_notifications.toggles import notifications_toggle
 
@@ -48,6 +50,8 @@ class Workflow(Enum):
     SPACE_APPOINTMENT_UPDATE = "space-appointment-update"
     SPACE_APPOINTMENT_DELETION = "space-appointment-deletion"
     POLL_UPDATES = "poll-updates"
+    POST_REACTIONS = "post-reactions"
+    COMMENT_REACTIONS = "comment-reactions"
 
 
 # INTERNAL USE ONLY, use NotificationsService  to trigger notifications
@@ -343,3 +347,33 @@ class PollUpdatesNotification(Notification):
             payload=payload.to_dict(),
             transaction_id=transaction_id if transaction_id else str(uuid.uuid4()),
         )
+
+
+class PostReactionNotification(Notification):
+    def __init__(
+        self,
+        author: str,
+        payload: PostReactionPayload,
+        transaction_id: Optional[str] = None,
+    ):
+        super().__init__(
+            workflow=Workflow.POST_REACTIONS.value,
+            recipients=author,
+            payload=payload.to_dict(),
+            transaction_id=transaction_id if transaction_id else str(uuid.uuid4()),
+        )
+
+
+class CommentReactionNotification(Notification):
+    def __init__(
+        self,
+        commenter: str,
+        payload: CommentReactionPayload,
+        transaction_id: Optional[str] = None,
+    ):
+        super().__init__(
+            workflow=Workflow.COMMENT_REACTIONS.value,
+            recipients=commenter,
+            payload=payload.to_dict(),
+            transaction_id=transaction_id if transaction_id else str(uuid.uuid4()),
+        )
diff --git a/openbook_notifications/payloads.py b/openbook_notifications/payloads.py
index ca230676d0d2d3181da4a6be22d72a99e07c0dc0..5b755cc71df126faff2139105c782200235c9979 100644
--- a/openbook_notifications/payloads.py
+++ b/openbook_notifications/payloads.py
@@ -11,7 +11,6 @@ from openbook_notifications.helpers import replace_mentions_with_full_names
 if TYPE_CHECKING:
     from openbook_auth.models import User
     from openbook_communities.models import Community
-    from openbook_posts.models import Post
     from comments.models import Comment
     from openbook_appointments.models import Appointment
     from openbook_polls.models import Poll
@@ -35,6 +34,8 @@ class NotificationType(Enum):
     SPACE_APPOINTMENT_UPDATE = "SPACE_APPOINTMENT_UPDATE"
     SPACE_APPOINTMENT_DELETION = "SPACE_APPOINTMENT_DELETION"
     POLL_UPDATES = "POLL_UPDATES"
+    POST_REACTION = "POST_REACTION"
+    COMMENT_REACTION = "COMMENT_REACTION"
 
 
 class UserData:
@@ -186,30 +187,43 @@ class AppointmentData:
 
 
 class PostData:
-    def __init__(self, id: str, title: str, text: str, context: str, is_public: bool):
+    def __init__(
+        self, id: str, title: str, text: str = None, community=None, is_public: bool = None, description: str = None
+    ):
         self.id = id
         self.title = title
         self.text = text
-        self.context = context
+        self.community = community
         self.is_public = is_public
+        self.description = description
 
     def to_dict(self):
-        return {
+        data = {
             "id": self.id,
             "title": self.title,
-            "text": self.text,
-            "context": self.context,
-            "isPublic": self.is_public,
         }
+        if self.text:
+            data["text"] = self.text
+        if self.community:
+            data["context"] = self.community.title
+            data["link"] = f"/spaces/{self.community.id}/updates/{self.id}"
+        else:
+            data["link"] = f"/feed-post/{self.id}"
+        if self.description:
+            data["description"] = self.description
+        if self.is_public:
+            data["isPublic"] = self.is_public
+        return data
 
     @classmethod
-    def from_post(cls, post: Post):
+    def from_post(cls, post):
         return cls(
             id=str(post.id),
             title=post.title if post.title else "",
-            text=post.text,
-            context=post.community.title,
-            is_public=post.is_public,
+            text=post.text if hasattr(post, "text") else None,
+            community=post.community if hasattr(post, "community") else None,
+            is_public=post.is_public if hasattr(post, "is_public") else None,
+            description=post.description if hasattr(post, "description") else None,
         )
 
 
@@ -435,7 +449,7 @@ class SpaceNewPostPayload(Payload):
 
     def __init__(
         self,
-        post: Post,
+        post,
         notification_type: NotificationType,
     ):
         super().__init__(
@@ -510,3 +524,33 @@ class PollUpdatesPayload(Payload):
             notification_type=NotificationType.POLL_UPDATES.value,
             notification_version=PollUpdatesPayload.NOTIFICATION_VERSION,
         )
+
+
+class PostReactionPayload(Payload):
+    NOTIFICATION_VERSION = "1.0.0"
+
+    def __init__(self, reactor: User, post):
+        super().__init__(
+            data={
+                "user": UserData.from_user(reactor).to_dict(),
+                "post": PostData.from_post(post).to_dict(),
+            },
+            notification_type=NotificationType.POST_REACTION.value,
+            notification_version=PostReactionPayload.NOTIFICATION_VERSION,
+        )
+
+
+class CommentReactionPayload(Payload):
+    NOTIFICATION_VERSION = "1.0.0"
+
+    def __init__(self, reactor: User, comment: Comment, context_id: str):
+        comment_dict = CommentData.from_comment(comment).to_dict()
+        comment_dict["link"] = CommentData.create_link(comment, context_id)
+        super().__init__(
+            data={
+                "user": UserData.from_user(reactor).to_dict(),
+                "comment": comment_dict,
+            },
+            notification_type=NotificationType.COMMENT_REACTION.value,
+            notification_version=CommentReactionPayload.NOTIFICATION_VERSION,
+        )
diff --git a/openbook_notifications/services.py b/openbook_notifications/services.py
index a90e4a5ebda5d66be11d660d73570d8869957e01..59e9020c63d2285ab543f65fe63ae0e1284cf5b6 100644
--- a/openbook_notifications/services.py
+++ b/openbook_notifications/services.py
@@ -25,6 +25,8 @@ from openbook_notifications.notifications import (
     SpaceAppointmentUpdateNotification,
     SpaceAppointmentDeletionNotification,
     PollUpdatesNotification,
+    CommentReactionNotification,
+    PostReactionNotification,
 )
 from openbook_notifications.payloads import (
     SpaceMembershipRequestPayload,
@@ -42,6 +44,8 @@ from openbook_notifications.payloads import (
     SpaceAppointmentUpdatePayload,
     SpaceAppointmentDeletionPayload,
     PollUpdatesPayload,
+    PostReactionPayload,
+    CommentReactionPayload,
 )
 
 if TYPE_CHECKING:
@@ -329,3 +333,28 @@ class NotificationsService:
             payload=PollUpdatesPayload(poll=poll),
         )
         notification.send()
+
+    @staticmethod
+    def send_post_reaction_notification(
+        post,
+        reactor: User,
+        recipient: User,
+    ):
+        notification = PostReactionNotification(
+            author=str(recipient.username),
+            payload=PostReactionPayload(reactor=reactor, post=post),
+        )
+        notification.send()
+
+    @staticmethod
+    def send_comment_reaction_notification(
+        comment: Comment,
+        reactor: User,
+        recipient: User,
+    ):
+        context_id = NotificationsService._find_comment_commentable_context_id(comment)
+        notification = CommentReactionNotification(
+            commenter=str(recipient.username),
+            payload=CommentReactionPayload(reactor=reactor, comment=comment, context_id=context_id),
+        )
+        notification.send()
diff --git a/openbook_posts/models.py b/openbook_posts/models.py
index 42b9b660515f8f8b9ce7bc8a72edeb3c0dcc52ed..94de5ae53b51091513c888f884daac2bd0fc9323 100644
--- a/openbook_posts/models.py
+++ b/openbook_posts/models.py
@@ -65,6 +65,7 @@ from openbook_common.utils.model_loaders import (
     get_user_notifications_subscription_model,
 )
 from openbook_moderation.models import ModeratedObject
+from openbook_notifications.services import NotificationsService
 from openbook_posts.checkers import (
     check_can_add_media,
     check_can_be_published,
@@ -537,7 +538,9 @@ class Post(CommentNotificationMixin, ModelWithUUID):
         commenter.delete_comment_with_id(post_comment_id=id)
 
     def react(self, reactor, reaction_type):
-        return PostReaction.create_reaction(reactor=reactor, reaction_type=reaction_type, post=self)
+        reaction = PostReaction.create_reaction(reactor=reactor, reaction_type=reaction_type, post=self)
+        NotificationsService.send_post_reaction_notification(post=self, reactor=reactor, recipient=self.creator)
+        return reaction
 
     def clear_reaction(self, reactor, reaction_type):
         return PostReaction.remove_reaction(reactor=reactor, reaction_type=reaction_type, post=self)
diff --git a/openbook_posts/tests/test_graphql.py b/openbook_posts/tests/test_graphql.py
index 4a9d3d93aed9ab527c058c46893df1c5de909bd5..759654428895e703d861cae30ea7b5e67c5d3ac6 100644
--- a/openbook_posts/tests/test_graphql.py
+++ b/openbook_posts/tests/test_graphql.py
@@ -1,5 +1,6 @@
 import logging
 from unittest import TestCase, mock
+from unittest.mock import patch, Mock
 
 import pytest
 from django.core.files.uploadedfile import SimpleUploadedFile
@@ -19,6 +20,7 @@ from openbook_common.tests.helpers import (
 )
 from openbook_communities.models import CommunityNotificationsSubscription
 from openbook_notifications.models import CommunityNewPostNotification
+from openbook_notifications.notifications import PostReactionNotification
 from openbook_posts.models import (
     Post,
     PostImage,
@@ -203,7 +205,8 @@ class TestPosts(TestCase):
         assert post_data is None
         assert "Forbidden" in response.errors[0].message
 
-    def test_react_to_post(self):
+    @patch.object(PostReactionNotification, "send")
+    def test_react_to_post(self, mock_send_post_reaction_notification: Mock):
         posting_user = make_user()
         other_user = make_user(name="Other")
 
@@ -213,6 +216,7 @@ class TestPosts(TestCase):
         post = posting_user.create_post(text=post_text, title=post_title, is_public=True)
 
         react_to_post(post, "SET", posting_user)
+        mock_send_post_reaction_notification.assert_called_once()
         executed = react_to_post(post, "SET", posting_user)  # setting twice is same as setting once
         updated_post = executed.data["updatePostReaction"]