diff --git a/app/mailers/emails/admin_notification.rb b/app/mailers/emails/admin_notification.rb
index e11f06d8fc94d66ae325839688b85267108808dd..f44dd448a35567904d03c70a5e3f0c8cde242ad3 100644
--- a/app/mailers/emails/admin_notification.rb
+++ b/app/mailers/emails/admin_notification.rb
@@ -15,5 +15,18 @@ def send_unsubscribed_notification(user_id)
       email = user.notification_email_or_default
       mail to: email, subject: "Unsubscribed from GitLab administrator notifications"
     end
+
+    def user_auto_banned_email(admin_id, user_id, max_project_downloads:, within_seconds:)
+      admin = User.find(admin_id)
+      @user = User.find(user_id)
+      @max_project_downloads = max_project_downloads
+      @within_minutes = within_seconds / 60
+
+      Gitlab::I18n.with_locale(admin.preferred_language) do
+        email_with_layout(
+          to: admin.notification_email_or_default,
+          subject: subject(_("We've detected unusual activity")))
+      end
+    end
   end
 end
diff --git a/app/mailers/emails/auto_devops.rb b/app/mailers/emails/auto_devops.rb
index 9705a3052d49dc92a887ce87be0e3842a90883ba..d10ba40d225ab32470b8c0f34dbfea769c5b80a3 100644
--- a/app/mailers/emails/auto_devops.rb
+++ b/app/mailers/emails/auto_devops.rb
@@ -8,11 +8,9 @@ def autodevops_disabled_email(pipeline, recipient)
 
       add_project_headers
 
-      mail(to: recipient,
-           subject: auto_devops_disabled_subject(@project.name)) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: recipient,
+        subject: auto_devops_disabled_subject(@project.name))
     end
 
     private
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index bbc4be3b3243d73f616ae89f33c9f5404338700f..6a5680c080bd363d1a48f2567014d4e8a467d971 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -94,10 +94,9 @@ def import_issues_csv_email(user_id, project_id, results)
       @project = Project.find(project_id)
       @results = results
 
-      mail(to: @user.notification_email_for(@project.group), subject: subject('Imported issues')) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: @user.notification_email_for(@project.group),
+        subject: subject('Imported issues'))
     end
 
     def issues_csv_email(user, project, csv_data, export_status)
@@ -110,10 +109,9 @@ def issues_csv_email(user, project, csv_data, export_status)
 
       filename = "#{project.full_path.parameterize}_issues_#{Date.today.iso8601}.csv"
       attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
-      mail(to: user.notification_email_for(@project.group), subject: subject("Exported issues")) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: user.notification_email_for(@project.group),
+        subject: subject("Exported issues"))
     end
 
     private
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index ef2220751bf40b336e351f27321fae4a77fc70e1..c885e41671c2779e329499aa5edcddb6b287ed5d 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -21,7 +21,7 @@ def member_access_requested_email(member_source_type, member_id, recipient_id)
 
       user = User.find(recipient_id)
 
-      member_email_with_layout(
+      email_with_layout(
         to: user.notification_email_for(notification_group),
         subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
     end
@@ -32,7 +32,7 @@ def member_access_granted_email(member_source_type, member_id)
 
       return unless member_exists?
 
-      member_email_with_layout(
+      email_with_layout(
         to: member.user.notification_email_for(notification_group),
         subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted"))
     end
@@ -47,7 +47,7 @@ def member_access_denied_email(member_source_type, source_id, user_id)
 
       human_name = @source_hidden ? 'Hidden' : member_source.human_name
 
-      member_email_with_layout(
+      email_with_layout(
         to: user.notification_email_for(notification_group),
         subject: subject("Access to the #{human_name} #{member_source.model_name.singular} was denied"))
     end
@@ -83,7 +83,7 @@ def member_invited_reminder_email(member_source_type, member_id, token, reminder
 
       subject_line = subjects[reminder_index] % { inviter: member.created_by.name }
 
-      member_email_with_layout(
+      email_with_layout(
         layout: 'unknown_user_mailer',
         to: member.invite_email,
         subject: subject(subject_line)
@@ -97,7 +97,7 @@ def member_invite_accepted_email(member_source_type, member_id)
       return unless member_exists?
       return unless member.created_by
 
-      member_email_with_layout(
+      email_with_layout(
         to: member.created_by.notification_email_for(notification_group),
         subject: subject('Invitation accepted'))
     end
@@ -111,7 +111,7 @@ def member_invite_declined_email(member_source_type, source_id, invite_email, cr
 
       user = User.find(created_by_id)
 
-      member_email_with_layout(
+      email_with_layout(
         to: user.notification_email_for(notification_group),
         subject: subject('Invitation declined'))
     end
@@ -128,7 +128,7 @@ def member_expiration_date_updated_email(member_source_type, member_id)
                   _('Group membership expiration date removed')
                 end
 
-      member_email_with_layout(
+      email_with_layout(
         to: member.user.notification_email_for(notification_group),
         subject: subject(subject))
     end
@@ -176,13 +176,6 @@ def member_exists?
     def member_source_class
       @member_source_type.classify.constantize
     end
-
-    def member_email_with_layout(to:, subject:, layout: 'mailer')
-      mail(to: to, subject: subject) do |format|
-        format.html { render layout: layout }
-        format.text { render layout: layout }
-      end
-    end
   end
 end
 
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 5cbc3c9ef9c852a3df5c167bcdde6051240def90..83d37a365de7a1e820fe815d4c2026e19b615d74 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -149,10 +149,9 @@ def merge_requests_csv_email(user, project, csv_data, export_status)
 
       filename = "#{project.full_path.parameterize}_merge_requests_#{Date.current.iso8601}.csv"
       attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
-      mail(to: user.notification_email_for(@project.group), subject: subject("Exported merge requests")) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: user.notification_email_for(@project.group),
+        subject: subject("Exported merge requests"))
     end
 
     def approved_merge_request_email(recipient_id, merge_request_id, approved_by_user_id, reason = nil)
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index 5363ad637715f424d9eca72cfbcbb79a0d62c4fd..463f5d3943ab2274c9f8b7b8b0d8b168d65814df 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -30,11 +30,9 @@ def pipeline_mail(pipeline, recipient, status)
 
       add_headers
 
-      mail(to: recipient,
-           subject: subject(pipeline_subject(status))) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: recipient,
+        subject: subject(pipeline_subject(status)))
     end
 
     def add_headers
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 31fcc7c15cb8e3cb52b1dda0cbc58c7f0ab019c7..81f082b9680fa3a6c57323d9340c51fa813e3af2 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -13,7 +13,7 @@ def instance_access_request_email(user, recipient)
       @user = user
       @recipient = recipient
 
-      profile_email_with_layout(
+      email_with_layout(
         to: recipient.notification_email_or_default,
         subject: subject(_("GitLab Account Request")))
     end
@@ -21,7 +21,7 @@ def instance_access_request_email(user, recipient)
     def user_admin_rejection_email(name, email)
       @name = name
 
-      profile_email_with_layout(
+      email_with_layout(
         to: email,
         subject: subject(_("GitLab account request rejected")))
     end
@@ -29,7 +29,7 @@ def user_admin_rejection_email(name, email)
     def user_deactivated_email(name, email)
       @name = name
 
-      profile_email_with_layout(
+      email_with_layout(
         to: email,
         subject: subject(_('Your account has been deactivated')))
     end
@@ -125,7 +125,7 @@ def unknown_sign_in_email(user, ip, time)
       @target_url = edit_profile_password_url
 
       Gitlab::I18n.with_locale(@user.preferred_language) do
-        profile_email_with_layout(
+        email_with_layout(
           to: @user.notification_email_or_default,
           subject: subject(_("%{host} sign-in from new location") % { host: Gitlab.config.gitlab.host }))
       end
@@ -151,15 +151,6 @@ def new_email_address_added_email(user, email)
         mail(to: @user.notification_email_or_default, subject: subject(_("New email address added")))
       end
     end
-
-    private
-
-    def profile_email_with_layout(to:, subject:, layout: 'mailer')
-      mail(to: to, subject: subject) do |format|
-        format.html { render layout: layout }
-        format.text { render layout: layout }
-      end
-    end
   end
 end
 
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index efc6ce163c04066c6ebddbc02e022841c273d8d6..ed3fa28b15fe5e315aedfefcb5f96cd69a8877ba 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -75,11 +75,9 @@ def inactive_project_deletion_warning_email(project, user, deletion_date)
       subject_text = "Action required: Project #{project.name} is scheduled to be deleted on " \
       "#{deletion_date} due to inactivity"
 
-      mail(to: user.notification_email_for(project.group),
-           subject: subject(subject_text)) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: user.notification_email_for(project.group),
+        subject: subject(subject_text))
     end
 
     private
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 03b70fffde1cc88e50160a49c8fe7d3d57d72c4c..b70ce1d365502f6f3958f758b1e79798e956e84f 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -222,6 +222,13 @@ def add_unsubscription_headers_and_links
     headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',')
     @unsubscribe_url = unsubscribe_sent_notification_url(@sent_notification)
   end
+
+  def email_with_layout(to:, subject:, layout: 'mailer')
+    mail(to: to, subject: subject) do |format|
+      format.html { render layout: layout }
+      format.text { render layout: layout }
+    end
+  end
 end
 
 Notify.prepend_mod_with('Notify')
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 60d594651655d4ec41372a91f4994bb626bb9983..61456ef79c82b7b754c7ff6ba238d506d78af379 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -205,6 +205,10 @@ def inactive_project_deletion_warning
     Notify.inactive_project_deletion_warning_email(project, user, '2022-04-22').message
   end
 
+  def user_auto_banned_email
+    ::Notify.user_auto_banned_email(user.id, user.id, max_project_downloads: 5, within_seconds: 600).message
+  end
+
   private
 
   def project
diff --git a/app/views/notify/user_auto_banned_email.html.haml b/app/views/notify/user_auto_banned_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..d88c06526eb918e69b83565a266725812b2bd2f5
--- /dev/null
+++ b/app/views/notify/user_auto_banned_email.html.haml
@@ -0,0 +1,9 @@
+- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
+- link_end = '</a>'.html_safe
+= email_default_heading(_("We've detected some unusual activity"))
+%p
+  = _('We want to let you know %{username} has been banned from your GitLab instance due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes.') % { username: sanitize_name(@user.name), max_project_downloads: @max_project_downloads, within_minutes: @within_minutes }
+%p
+  = _('If this is a mistake, you can %{link_start}unban them%{link_end}.').html_safe % { link_start: link_start % { url: admin_users_url(filter: 'banned') }, link_end: link_end }
+%p
+  = _('You can adjust rules on auto-banning %{link_start}here%{link_end}.').html_safe % { link_start: link_start % { url: network_admin_application_settings_url(anchor: 'js-ip-limits-settings') }, link_end: link_end }
diff --git a/app/views/notify/user_auto_banned_email.text.erb b/app/views/notify/user_auto_banned_email.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0469ee9788ce0a8aa48bf62c4dc04b019ef7e194
--- /dev/null
+++ b/app/views/notify/user_auto_banned_email.text.erb
@@ -0,0 +1,7 @@
+<%= _("We've detected some unusual activity") %>
+
+<%= _('We want to let you know %{username} has been banned from your GitLab instance due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes.') % { username: sanitize_name(@user.name), max_project_downloads: @max_project_downloads, within_minutes: @within_minutes } %>
+
+<%= _('If this is a mistake, you can unban them: %{url}.') % { url: admin_users_url(filter: 'banned') } %>
+
+<%= _('You can adjust rules on auto-banning here: %{url}.') % { url: network_admin_application_settings_url(anchor: 'js-ip-limits-settings') } %>
diff --git a/ee/app/mailers/ee/emails/members.rb b/ee/app/mailers/ee/emails/members.rb
index 0b4db548e7a7214c7e4e082027e587d09af1ab42..98b35736e3bf33ab73e48a0fdb2f19e330d11bb5 100644
--- a/ee/app/mailers/ee/emails/members.rb
+++ b/ee/app/mailers/ee/emails/members.rb
@@ -10,7 +10,7 @@ def provisioned_member_access_granted_email(member_id)
 
         @user = member.user
 
-        member_email_with_layout(
+        email_with_layout(
           to: member.user.email,
           subject: subject("Welcome to GitLab"))
       end
diff --git a/ee/app/mailers/ee/emails/projects.rb b/ee/app/mailers/ee/emails/projects.rb
index 7d7930b5bf55dd580c6d3b62052d6b9ed5ac50d8..3a27827fc45731c9e61f30b0f4d49bfd5b009eaf 100644
--- a/ee/app/mailers/ee/emails/projects.rb
+++ b/ee/app/mailers/ee/emails/projects.rb
@@ -36,10 +36,9 @@ def user_escalation_rule_deleted_email(user, project, rules, recipient)
         @project = project
         @rules = rules
 
-        mail(to: recipient.notification_email_for(@project.group), subject: subject('User removed from escalation policy')) do |format|
-          format.html { render layout: 'mailer' }
-          format.text { render layout: 'mailer' }
-        end
+        email_with_layout(
+          to: recipient.notification_email_for(@project.group),
+          subject: subject('User removed from escalation policy'))
       end
 
       def incident_escalation_fired_email(project, user, issue)
diff --git a/ee/app/mailers/emails/group_memberships.rb b/ee/app/mailers/emails/group_memberships.rb
index 3bbb75b62ce9082deffb896f6ccb104c250376eb..3772aaa07833db415493de6c61838e9225e3ea1d 100644
--- a/ee/app/mailers/emails/group_memberships.rb
+++ b/ee/app/mailers/emails/group_memberships.rb
@@ -7,10 +7,9 @@ def memberships_export_email(csv_data:, requested_by:, group:)
 
       filename = "#{group.full_path.parameterize}_group_memberships_#{Date.current.iso8601}.csv"
       attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
-      mail(to: requested_by.notification_email_for(group), subject: "Exported group membership list") do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: requested_by.notification_email_for(group),
+        subject: "Exported group membership list")
     end
   end
 end
diff --git a/ee/app/mailers/emails/oncall_rotation.rb b/ee/app/mailers/emails/oncall_rotation.rb
index fb7a91809274c228b885c23d83686ea7ee9b5187..df5213eab9de1fdbff6e2f0f8a53859f48140ac2 100644
--- a/ee/app/mailers/emails/oncall_rotation.rb
+++ b/ee/app/mailers/emails/oncall_rotation.rb
@@ -10,10 +10,9 @@ def user_removed_from_rotation_email(user, rotation, recipients)
       @schedule = rotation.schedule
       @project = rotation.project
 
-      mail(to: recipients.map(&:email), subject: subject('User removed from On-call rotation')) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: recipients.map(&:email),
+        subject: subject('User removed from On-call rotation'))
     end
   end
 end
diff --git a/ee/app/mailers/emails/requirements.rb b/ee/app/mailers/emails/requirements.rb
index 0460755a31bd9c4e7c8403de20e17ccbe00ef745..54475a477ad36d58b3eea81588944895ec9dbb66 100644
--- a/ee/app/mailers/emails/requirements.rb
+++ b/ee/app/mailers/emails/requirements.rb
@@ -7,7 +7,9 @@ def import_requirements_csv_email(user_id, project_id, results)
       @project = Project.find(project_id)
       @results = results
 
-      requirement_email_with_layout(@user, @project.group, _('Imported requirements'))
+      email_with_layout(
+        to: @user.notification_email_for(@project.group),
+        subject: subject(_('Imported requirements')))
     end
 
     def requirements_csv_email(user, project, csv_data, export_status)
@@ -18,14 +20,9 @@ def requirements_csv_email(user, project, csv_data, export_status)
       filename = "#{project.full_path.parameterize}_requirements_#{Date.current.iso8601}.csv"
       attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
 
-      requirement_email_with_layout(user, @project.group, _('Exported requirements'))
-    end
-
-    def requirement_email_with_layout(user, group, subj)
-      mail(to: user.notification_email_for(group), subject: subject(subj)) do |format|
-        format.html { render layout: 'mailer' }
-        format.text { render layout: 'mailer' }
-      end
+      email_with_layout(
+        to: user.notification_email_for(@project.group),
+        subject: subject(_('Exported requirements')))
     end
   end
 end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4171fc4687f487871d59381264af991412e4cdf3..314af57bb857987e28cd1d1d33309ffa571952b5 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19162,6 +19162,12 @@ msgstr ""
 msgid "If this email was added in error, you can remove it here: %{profile_emails_url}"
 msgstr ""
 
+msgid "If this is a mistake, you can %{link_start}unban them%{link_end}."
+msgstr ""
+
+msgid "If this is a mistake, you can unban them: %{url}."
+msgstr ""
+
 msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}."
 msgstr ""
 
@@ -42441,6 +42447,9 @@ msgstr ""
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "We want to let you know %{username} has been banned from your GitLab instance due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes."
+msgstr ""
+
 msgid "We will notify %{inviter} that you declined their invitation to join GitLab. You will stop receiving reminders."
 msgstr ""
 
@@ -42456,6 +42465,12 @@ msgstr ""
 msgid "We're experiencing difficulties and this tab content is currently unavailable."
 msgstr ""
 
+msgid "We've detected some unusual activity"
+msgstr ""
+
+msgid "We've detected unusual activity"
+msgstr ""
+
 msgid "We've found no vulnerabilities"
 msgstr ""
 
@@ -43276,6 +43291,12 @@ msgstr ""
 msgid "You can %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}."
 msgstr ""
 
+msgid "You can adjust rules on auto-banning %{link_start}here%{link_end}."
+msgstr ""
+
+msgid "You can adjust rules on auto-banning here: %{url}."
+msgstr ""
+
 msgid "You can also create a project from the command line."
 msgstr ""
 
diff --git a/spec/mailers/emails/admin_notification_spec.rb b/spec/mailers/emails/admin_notification_spec.rb
index 90381eb8ffdafe378c36ef852685ff6e00a16a00..a233be86a83dce1bf03b8c3b8a7a3735c40824c2 100644
--- a/spec/mailers/emails/admin_notification_spec.rb
+++ b/spec/mailers/emails/admin_notification_spec.rb
@@ -3,9 +3,62 @@
 require 'spec_helper'
 
 RSpec.describe Emails::AdminNotification do
+  include EmailSpec::Matchers
+  include_context 'gitlab email notification'
+
   it 'adds email methods to Notify' do
     subject.instance_methods.each do |email_method|
       expect(Notify).to be_respond_to(email_method)
     end
   end
+
+  describe 'user_auto_banned_email' do
+    let_it_be(:admin) { create(:user) }
+    let_it_be(:user) { create(:user) }
+
+    let(:max_project_downloads) { 5 }
+    let(:time_period) { 600 }
+
+    subject do
+      Notify.user_auto_banned_email(
+        admin.id, user.id,
+        max_project_downloads: max_project_downloads,
+        within_seconds: time_period
+      )
+    end
+
+    it_behaves_like 'an email sent from GitLab'
+    it_behaves_like 'it should not have Gmail Actions links'
+    it_behaves_like 'a user cannot unsubscribe through footer link'
+    it_behaves_like 'appearance header and footer enabled'
+    it_behaves_like 'appearance header and footer not enabled'
+
+    it 'is sent to the administrator' do
+      is_expected.to deliver_to admin.email
+    end
+
+    it 'has the correct subject' do
+      is_expected.to have_subject "We've detected unusual activity"
+    end
+
+    it 'includes the name of the user' do
+      is_expected.to have_body_text user.name
+    end
+
+    it 'includes the reason' do
+      is_expected.to have_body_text "due to them downloading more than 5 project repositories within 10 minutes"
+    end
+
+    it 'includes a link to unban the user' do
+      is_expected.to have_body_text admin_users_url(filter: 'banned')
+    end
+
+    it 'includes a link to change the settings' do
+      is_expected.to have_body_text network_admin_application_settings_url(anchor: 'js-ip-limits-settings')
+    end
+
+    it 'includes the email reason' do
+      is_expected.to have_body_text "You're receiving this email because of your account on localhost"
+    end
+  end
 end