diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb
index 697d93474f4..53406bf0f96 100644
--- a/lib/discourse_plugin_registry.rb
+++ b/lib/discourse_plugin_registry.rb
@@ -116,6 +116,8 @@ class DiscoursePluginRegistry
 
   define_filtered_register :summarization_strategies
 
+  define_filtered_register :post_action_notify_user_handlers
+
   def self.register_auth_provider(auth_provider)
     self.auth_providers << auth_provider
   end
diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb
index 7c775b5453a..ec11c01db46 100644
--- a/lib/guardian/post_guardian.rb
+++ b/lib/guardian/post_guardian.rb
@@ -58,7 +58,19 @@ module PostGuardian
 
         if action_key == :notify_user &&
              !@user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map)
-          return false
+          # The modifier below is used to add additional permissions for notifying users.
+          # In core the only method of notifying a user is personal messages so we check if the
+          # user can PM. Plugins can extend the behavior of how users are notifier via `notify_user`
+          # post action, and this allows extension for that use case.
+          can_notify = false
+          can_notify =
+            DiscoursePluginRegistry.apply_modifier(
+              :post_guardian_can_notify_user,
+              can_notify,
+              self,
+              post,
+            )
+          return can_notify
         end
 
         # we allow flagging for trust level 1 and higher
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index a3f3887b3ab..af82b6411a1 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -1238,6 +1238,14 @@ class Plugin::Instance
     DiscoursePluginRegistry.register_summarization_strategy(strategy, self)
   end
 
+  ##
+  # Register a block that will be called when PostActionCreator is going to notify a
+  # user of a post action. If any of these handlers returns false the default PostCreator
+  # call will be skipped.
+  def register_post_action_notify_user_handler(handler)
+    DiscoursePluginRegistry.register_post_action_notify_user_handler(handler, self)
+  end
+
   protected
 
   def self.js_path
diff --git a/lib/post_action_creator.rb b/lib/post_action_creator.rb
index 409bb94eb0b..f1e46d855ca 100644
--- a/lib/post_action_creator.rb
+++ b/lib/post_action_creator.rb
@@ -116,12 +116,16 @@ class PostActionCreator
     # create meta topic / post if needed
     if @message.present? && %i[notify_moderators notify_user spam].include?(@post_action_name)
       creator = create_message_creator
-      post = creator.create
-      if creator.errors.present?
-        result.add_errors_from(creator)
-        return result
+      # We need to check if the creator exists because it's possible `create_message_creator` returns nil
+      # in the event that a `post_action_notify_user_handler` evaluated to false, haulting the post creation.
+      if creator
+        post = creator.create
+        if creator.errors.present?
+          result.add_errors_from(creator)
+          return result
+        end
+        @meta_post = post
       end
-      @meta_post = post
     end
 
     begin
@@ -339,8 +343,16 @@ class PostActionCreator
     else
       create_args[:subtype] = TopicSubtype.notify_user
 
-      create_args[:target_usernames] = if @post_action_name == :notify_user
-        @post.user.username
+      if @post_action_name == :notify_user
+        create_args[:target_usernames] = @post.user.username
+
+        # Evaluate DiscoursePluginRegistry.post_action_notify_user_handlers.
+        # If any return false, return early from this method
+        handler_values =
+          DiscoursePluginRegistry.post_action_notify_user_handlers.map do |handler|
+            handler.call(@created_by, @post, @message)
+          end
+        return if handler_values.any? { |value| value == false }
       elsif @post_action_name != :notify_moderators
         # this is a hack to allow a PM with no recipients, we should think through
         # a cleaner technique, a PM with myself is valid for flagging
diff --git a/spec/lib/post_action_creator_spec.rb b/spec/lib/post_action_creator_spec.rb
index 81aeba92dfd..fef228d75b3 100644
--- a/spec/lib/post_action_creator_spec.rb
+++ b/spec/lib/post_action_creator_spec.rb
@@ -327,4 +327,73 @@ RSpec.describe PostActionCreator do
       )
     end
   end
+
+  describe "With plugin adding post_action_notify_user_handlers" do
+    let(:message) { "oh that was really bad what you said there" }
+    let(:plugin) { Plugin::Instance.new }
+
+    after { DiscoursePluginRegistry.reset! }
+
+    it "evaluates all handlers and creates post if none return false" do
+      plugin.register_post_action_notify_user_handler(
+        Proc.new do |user, post, message|
+          MessageBus.publish("notify_user", { user_id: user.id, message: message })
+        end,
+      )
+
+      plugin.register_post_action_notify_user_handler(
+        Proc.new do |user, post, message|
+          MessageBus.publish("notify_user", { poster_id: post.user_id, message: message })
+        end,
+      )
+
+      messages =
+        MessageBus.track_publish("notify_user") do
+          result =
+            PostActionCreator.new(
+              user,
+              post,
+              PostActionType.types[:notify_user],
+              message: message,
+              flag_topic: false,
+            ).perform
+          post_action = result.post_action
+          expect(post_action.related_post).to be_present
+        end
+
+      expect(
+        messages.find { |m| m.data[:user_id] == user.id && m.data[:message] == message },
+      ).to be_present
+      expect(
+        messages.find { |m| m.data[:poster_id] == post.user_id && m.data[:message] == message },
+      ).to be_present
+    end
+
+    it "evaluates all handlers and doesn't create a post one returns false" do
+      plugin.register_post_action_notify_user_handler(
+        Proc.new do |user, post, message|
+          MessageBus.publish("notify_user", { user_id: user.id, message: message })
+          false
+        end,
+      )
+
+      messages =
+        MessageBus.track_publish("notify_user") do
+          result =
+            PostActionCreator.new(
+              user,
+              post,
+              PostActionType.types[:notify_user],
+              message: message,
+              flag_topic: false,
+            ).perform
+          post_action = result.post_action
+          expect(post_action.related_post).not_to be_present
+        end
+
+      expect(
+        messages.find { |m| m.data[:user_id] == user.id && m.data[:message] == message },
+      ).to be_present
+    end
+  end
 end