diff --git a/app/assets/javascripts/discourse/app/components/d-modal-body.js b/app/assets/javascripts/discourse/app/components/d-modal-body.js
index 120d00e23cb..4f2f38292fc 100644
--- a/app/assets/javascripts/discourse/app/components/d-modal-body.js
+++ b/app/assets/javascripts/discourse/app/components/d-modal-body.js
@@ -62,7 +62,12 @@ export default Component.extend({
     const modalAlert = document.getElementById("modal-alert");
     if (modalAlert) {
       modalAlert.style.display = "none";
-      modalAlert.classList.remove("alert-info", "alert-error", "alert-success");
+      modalAlert.classList.remove(
+        "alert-error",
+        "alert-info",
+        "alert-success",
+        "alert-warning"
+      );
     }
   },
 
diff --git a/app/assets/javascripts/discourse/app/controllers/create-invite.js b/app/assets/javascripts/discourse/app/controllers/create-invite.js
index d24e4525685..1a774042373 100644
--- a/app/assets/javascripts/discourse/app/controllers/create-invite.js
+++ b/app/assets/javascripts/discourse/app/controllers/create-invite.js
@@ -99,10 +99,15 @@ export default Controller.extend(
 
       return this.invite
         .save(data)
-        .then(() => {
+        .then((result) => {
           this.rollbackBuffer();
           this.setAutogenerated(opts.autogenerated);
-          if (!this.autogenerated) {
+          if (result.warnings) {
+            this.appEvents.trigger("modal-body:flash", {
+              text: result.warnings.join(","),
+              messageClass: "warning",
+            });
+          } else if (!this.autogenerated) {
             if (this.isEmail && opts.sendEmail) {
               this.send("closeModal");
             } else {
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 0ef40298b82..acd5b49f4fd 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -102,7 +102,7 @@ class InvitesController < ApplicationController
       )
 
       if invite.present?
-        render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email))
+        render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true)
       else
         render json: failed_json, status: 422
       end
@@ -121,7 +121,7 @@ class InvitesController < ApplicationController
 
     guardian.ensure_can_invite_to_forum!(nil)
 
-    render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email))
+    render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true)
   end
 
   def update
@@ -194,7 +194,7 @@ class InvitesController < ApplicationController
       Jobs.enqueue(:invite_email, invite_id: invite.id, invite_to_topic: params[:invite_to_topic])
     end
 
-    render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email))
+    render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true)
   end
 
   def destroy
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 704a62f0e28..a5c08d38347 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -249,6 +249,23 @@ class Invite < ActiveRecord::Base
     Jobs.enqueue(:invite_email, invite_id: self.id)
   end
 
+  def warnings(guardian)
+    @warnings ||= begin
+      warnings = []
+
+      topic = self.topics.first
+      if topic&.read_restricted_category?
+        topic_groups = topic.category.groups
+        if (self.groups & topic_groups).blank?
+          editable_topic_groups = topic_groups.filter { |g| guardian.can_edit_group?(g) }
+          warnings << I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", "))
+        end
+      end
+
+      warnings
+    end
+  end
+
   def limit_invites_per_day
     RateLimiter.new(invited_by, "invites-per-day", SiteSetting.max_invites_per_day, 1.day.to_i)
   end
diff --git a/app/serializers/invite_serializer.rb b/app/serializers/invite_serializer.rb
index 0d8f0dbf6bd..51aa36939e3 100644
--- a/app/serializers/invite_serializer.rb
+++ b/app/serializers/invite_serializer.rb
@@ -12,7 +12,8 @@ class InviteSerializer < ApplicationSerializer
              :created_at,
              :updated_at,
              :expires_at,
-             :expired
+             :expired,
+             :warnings
 
   has_many :topics, embed: :object, serializer: BasicTopicSerializer
   has_many :groups, embed: :object, serializer: BasicGroupSerializer
@@ -44,4 +45,12 @@ class InviteSerializer < ApplicationSerializer
   def expired
     object.expired?
   end
+
+  def warnings
+    object.warnings(scope)
+  end
+
+  def include_warnings?
+    object.warnings(scope).present?
+  end
 end
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index f8d75b4fcec..c815b967ef5 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -254,6 +254,7 @@ en:
     disabled_errors:
       discourse_connect_enabled: "Invites are disabled because DiscourseConnect is enabled."
       invalid_access: "You are not permitted to view the requested resource."
+    requires_groups: "Invite saved. To give access to the specified topic, add one of the following groups: %{groups}."
 
   bulk_invite:
     file_should_be_csv: "The uploaded file should be of csv format."
diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb
index 91316b4a86d..58f32775397 100644
--- a/spec/models/invite_spec.rb
+++ b/spec/models/invite_spec.rb
@@ -404,4 +404,27 @@ describe Invite do
       expect(invite.invalidated_at).to be_nil
     end
   end
+
+  describe '#warnings' do
+    fab!(:admin) { Fabricate(:admin) }
+    fab!(:invite) { Fabricate(:invite) }
+    fab!(:group) { Fabricate(:group) }
+    fab!(:secured_category) do
+      secured_category = Fabricate(:category)
+      secured_category.permissions = { group.name => :full }
+      secured_category.save!
+      secured_category
+    end
+
+    it 'does not return any warnings for simple invites' do
+      expect(invite.warnings(admin.guardian)).to be_blank
+    end
+
+    it 'returns a warning if topic is private' do
+      topic = Fabricate(:topic, category: secured_category)
+      TopicInvite.create!(topic: topic, invite: invite)
+
+      expect(invite.warnings(admin.guardian)).to contain_exactly(I18n.t("invite.requires_groups", groups: group.name))
+    end
+  end
 end