diff --git a/Gemfile b/Gemfile
index add7b36066e73e93afefc0e743a2fcfc4c60fff0..74672ad06ae91399380c80b843c29815529630fd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -109,6 +109,9 @@ group :test do
   # RSpec progress bar formatter
   gem 'fuubar', '~> 2.5'
 
+  # RSpec helpers for email specs
+  gem 'email_spec'
+
   # Extra RSpec extenion methods and helpers for sidekiq
   gem 'rspec-sidekiq', '~> 4.0'
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 20c958e2e0f2c22d3b71aa9c18853db31c2f27c5..beec8b39cc3630e38c339b6e43c7f8b7dfc3927c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -260,6 +260,10 @@ GEM
     elasticsearch-transport (7.13.3)
       faraday (~> 1)
       multi_json
+    email_spec (2.2.2)
+      htmlentities (~> 4.3.3)
+      launchy (~> 2.1)
+      mail (~> 2.7)
     encryptor (3.0.0)
     erubi (1.12.0)
     et-orbi (1.2.7)
@@ -853,6 +857,7 @@ DEPENDENCIES
   doorkeeper (~> 5.6)
   dotenv-rails (~> 2.8)
   ed25519 (~> 1.3)
+  email_spec
   fabrication (~> 2.30)
   faker (~> 3.2)
   fast_blank (~> 1.0)
diff --git a/spec/mailers/admin_mailer_spec.rb b/spec/mailers/admin_mailer_spec.rb
index 423dce88ab09ee1be287feb1b409f822928d8040..9f0d899963162237f6cc2aa2afb4b357aac2655d 100644
--- a/spec/mailers/admin_mailer_spec.rb
+++ b/spec/mailers/admin_mailer_spec.rb
@@ -13,14 +13,13 @@ RSpec.describe AdminMailer do
       recipient.user.update(locale: :en)
     end
 
-    it 'renders the headers' do
-      expect(mail.subject).to eq("New report for cb6e6126.ngrok.io (##{report.id})")
-      expect(mail.to).to eq [recipient.user_email]
-      expect(mail.from).to eq ['notifications@localhost']
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to eq("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n")
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(deliver_to(recipient.user_email))
+        .and(deliver_from('notifications@localhost'))
+        .and(have_subject("New report for cb6e6126.ngrok.io (##{report.id})"))
+        .and(have_body_text("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n"))
     end
   end
 
@@ -33,14 +32,13 @@ RSpec.describe AdminMailer do
       recipient.user.update(locale: :en)
     end
 
-    it 'renders the headers' do
-      expect(mail.subject).to eq("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io")
-      expect(mail.to).to eq [recipient.user_email]
-      expect(mail.from).to eq ['notifications@localhost']
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match "#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(deliver_to(recipient.user_email))
+        .and(deliver_from('notifications@localhost'))
+        .and(have_subject("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io"))
+        .and(have_body_text("#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"))
     end
   end
 
@@ -53,14 +51,13 @@ RSpec.describe AdminMailer do
       recipient.user.update(locale: :en)
     end
 
-    it 'renders the headers' do
-      expect(mail.subject).to eq("New account up for review on cb6e6126.ngrok.io (#{user.account.username})")
-      expect(mail.to).to eq [recipient.user_email]
-      expect(mail.from).to eq ['notifications@localhost']
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match 'The details of the new account are below. You can approve or reject this application.'
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(deliver_to(recipient.user_email))
+        .and(deliver_from('notifications@localhost'))
+        .and(have_subject("New account up for review on cb6e6126.ngrok.io (#{user.account.username})"))
+        .and(have_body_text('The details of the new account are below. You can approve or reject this application.'))
     end
   end
 
@@ -75,14 +72,13 @@ RSpec.describe AdminMailer do
       recipient.user.update(locale: :en)
     end
 
-    it 'renders the headers' do
-      expect(mail.subject).to eq('New trends up for review on cb6e6126.ngrok.io')
-      expect(mail.to).to eq [recipient.user_email]
-      expect(mail.from).to eq ['notifications@localhost']
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match 'The following items need a review before they can be displayed publicly'
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(deliver_to(recipient.user_email))
+        .and(deliver_from('notifications@localhost'))
+        .and(have_subject('New trends up for review on cb6e6126.ngrok.io'))
+        .and(have_body_text('The following items need a review before they can be displayed publicly'))
     end
   end
 
@@ -94,14 +90,13 @@ RSpec.describe AdminMailer do
       recipient.user.update(locale: :en)
     end
 
-    it 'renders the headers' do
-      expect(mail.subject).to eq('New Mastodon versions are available for cb6e6126.ngrok.io!')
-      expect(mail.to).to eq [recipient.user_email]
-      expect(mail.from).to eq ['notifications@localhost']
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match 'New Mastodon versions have been released, you may want to update!'
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(deliver_to(recipient.user_email))
+        .and(deliver_from('notifications@localhost'))
+        .and(have_subject('New Mastodon versions are available for cb6e6126.ngrok.io!'))
+        .and(have_body_text('New Mastodon versions have been released, you may want to update!'))
     end
   end
 
@@ -113,18 +108,16 @@ RSpec.describe AdminMailer do
       recipient.user.update(locale: :en)
     end
 
-    it 'renders the headers', :aggregate_failures do
-      expect(mail.subject).to eq('Critical Mastodon updates are available for cb6e6126.ngrok.io!')
-      expect(mail.to).to eq [recipient.user_email]
-      expect(mail.from).to eq ['notifications@localhost']
-
-      expect(mail['Importance'].value).to eq 'high'
-      expect(mail['Priority'].value).to eq 'urgent'
-      expect(mail['X-Priority'].value).to eq '1'
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match 'New critical versions of Mastodon have been released, you may want to update as soon as possible!'
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(deliver_to(recipient.user_email))
+        .and(deliver_from('notifications@localhost'))
+        .and(have_subject('Critical Mastodon updates are available for cb6e6126.ngrok.io!'))
+        .and(have_body_text('New critical versions of Mastodon have been released, you may want to update as soon as possible!'))
+        .and(have_header('Importance', 'high'))
+        .and(have_header('Priority', 'urgent'))
+        .and(have_header('X-Priority', '1'))
     end
   end
 end
diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb
index 78a497c06bd44fb235939e0ab36e0fc36058fc10..eab196166d32194d6eac4e80c89a620ccc442aa4 100644
--- a/spec/mailers/notification_mailer_spec.rb
+++ b/spec/mailers/notification_mailer_spec.rb
@@ -8,24 +8,27 @@ RSpec.describe NotificationMailer do
   let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
   let(:own_status)     { Fabricate(:status, account: receiver.account, text: 'The body of the own status') }
 
-  shared_examples 'headers' do |type, thread|
-    it 'renders the to and from headers' do
-      expect(mail[:to].value).to eq "#{receiver.account.username} <#{receiver.email}>"
-      expect(mail.from).to eq ['notifications@localhost']
-    end
-
-    it 'renders the list headers' do
-      expect(mail['List-ID'].value).to eq "<#{type}.alice.cb6e6126.ngrok.io>"
-      expect(mail['List-Unsubscribe'].value).to match(%r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>})
-      expect(mail['List-Unsubscribe'].value).to match("&type=#{type}")
-      expect(mail['List-Unsubscribe-Post'].value).to eq 'List-Unsubscribe=One-Click'
+  shared_examples 'standard headers' do |type|
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(have_header('To', "#{receiver.account.username} <#{receiver.email}>"))
+        .and(have_header('List-ID', "<#{type}.alice.cb6e6126.ngrok.io>"))
+        .and(have_header('List-Unsubscribe', %r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>}))
+        .and(have_header('List-Unsubscribe', /&type=#{type}/))
+        .and(have_header('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click'))
+        .and(deliver_to("#{receiver.account.username} <#{receiver.email}>"))
+        .and(deliver_from('notifications@localhost'))
     end
+  end
 
-    if thread
-      it 'renders the thread headers' do
-        expect(mail['In-Reply-To'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
-        expect(mail['References'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
-      end
+  shared_examples 'thread headers' do
+    it 'renders the email with conversation thread headers' do
+      conversation_header_regex = /<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/
+      expect(mail)
+        .to be_present
+        .and(have_header('In-Reply-To', conversation_header_regex))
+        .and(have_header('References', conversation_header_regex))
     end
   end
 
@@ -35,15 +38,15 @@ RSpec.describe NotificationMailer do
     let(:mail) { prepared_mailer_for(receiver.account).mention }
 
     include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'
-    include_examples 'headers', 'mention', true
-
-    it 'renders the subject' do
-      expect(mail.subject).to eq('You were mentioned by bob')
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match('You were mentioned by bob')
-      expect(mail.body.encoded).to include 'The body of the foreign status'
+    include_examples 'standard headers', 'mention'
+    include_examples 'thread headers'
+
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(have_subject('You were mentioned by bob'))
+        .and(have_body_text('You were mentioned by bob'))
+        .and(have_body_text('The body of the foreign status'))
     end
   end
 
@@ -53,14 +56,13 @@ RSpec.describe NotificationMailer do
     let(:mail) { prepared_mailer_for(receiver.account).follow }
 
     include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'
-    include_examples 'headers', 'follow', false
-
-    it 'renders the subject' do
-      expect(mail.subject).to eq('bob is now following you')
-    end
+    include_examples 'standard headers', 'follow'
 
-    it 'renders the body' do
-      expect(mail.body.encoded).to match('bob is now following you')
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(have_subject('bob is now following you'))
+        .and(have_body_text('bob is now following you'))
     end
   end
 
@@ -70,15 +72,15 @@ RSpec.describe NotificationMailer do
     let(:mail) { prepared_mailer_for(own_status.account).favourite }
 
     include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'
-    include_examples 'headers', 'favourite', true
-
-    it 'renders the subject' do
-      expect(mail.subject).to eq('bob favorited your post')
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match('Your post was favorited by bob')
-      expect(mail.body.encoded).to include 'The body of the own status'
+    include_examples 'standard headers', 'favourite'
+    include_examples 'thread headers'
+
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(have_subject('bob favorited your post'))
+        .and(have_body_text('Your post was favorited by bob'))
+        .and(have_body_text('The body of the own status'))
     end
   end
 
@@ -88,15 +90,15 @@ RSpec.describe NotificationMailer do
     let(:mail) { prepared_mailer_for(own_status.account).reblog }
 
     include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'
-    include_examples 'headers', 'reblog', true
-
-    it 'renders the subject' do
-      expect(mail.subject).to eq('bob boosted your post')
-    end
-
-    it 'renders the body' do
-      expect(mail.body.encoded).to match('Your post was boosted by bob')
-      expect(mail.body.encoded).to include 'The body of the own status'
+    include_examples 'standard headers', 'reblog'
+    include_examples 'thread headers'
+
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(have_subject('bob boosted your post'))
+        .and(have_body_text('Your post was boosted by bob'))
+        .and(have_body_text('The body of the own status'))
     end
   end
 
@@ -106,14 +108,13 @@ RSpec.describe NotificationMailer do
     let(:mail) { prepared_mailer_for(receiver.account).follow_request }
 
     include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'
-    include_examples 'headers', 'follow_request', false
-
-    it 'renders the subject' do
-      expect(mail.subject).to eq('Pending follower: bob')
-    end
+    include_examples 'standard headers', 'follow_request'
 
-    it 'renders the body' do
-      expect(mail.body.encoded).to match('bob has requested to follow you')
+    it 'renders the email' do
+      expect(mail)
+        .to be_present
+        .and(have_subject('Pending follower: bob'))
+        .and(have_body_text('bob has requested to follow you'))
     end
   end
 
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index c661f5bbda552d7a145d2c3ca4c10f2574ea710f..4a4392824804869bb4712517a259b51b334a8c50 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -10,9 +10,12 @@ describe UserMailer do
 
     it 'renders confirmation instructions' do
       receiver.update!(locale: nil)
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.confirmation_instructions.title')
-      expect(mail.body.encoded).to include 'spec'
-      expect(mail.body.encoded).to include Rails.configuration.x.local_domain
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('devise.mailer.confirmation_instructions.title')))
+        .and(have_body_text('spec'))
+        .and(have_body_text(Rails.configuration.x.local_domain))
     end
 
     include_examples 'localized subject',
@@ -25,13 +28,17 @@ describe UserMailer do
 
     it 'renders reconfirmation instructions' do
       receiver.update!(email: 'new-email@example.com', locale: nil)
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.reconfirmation_instructions.title')
-      expect(mail.body.encoded).to include 'spec'
-      expect(mail.body.encoded).to include Rails.configuration.x.local_domain
-      expect(mail.subject).to eq I18n.t('devise.mailer.reconfirmation_instructions.subject',
-                                        instance: Rails.configuration.x.local_domain,
-                                        locale: I18n.default_locale)
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('devise.mailer.reconfirmation_instructions.title')))
+        .and(have_body_text('spec'))
+        .and(have_body_text(Rails.configuration.x.local_domain))
     end
+
+    include_examples 'localized subject',
+                     'devise.mailer.confirmation_instructions.subject',
+                     instance: Rails.configuration.x.local_domain
   end
 
   describe '#reset_password_instructions' do
@@ -39,8 +46,11 @@ describe UserMailer do
 
     it 'renders reset password instructions' do
       receiver.update!(locale: nil)
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.reset_password_instructions.title')
-      expect(mail.body.encoded).to include 'spec'
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('devise.mailer.reset_password_instructions.title')))
+        .and(have_body_text('spec'))
     end
 
     include_examples 'localized subject',
@@ -52,7 +62,10 @@ describe UserMailer do
 
     it 'renders password change notification' do
       receiver.update!(locale: nil)
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.password_change.title')
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('devise.mailer.password_change.title')))
     end
 
     include_examples 'localized subject',
@@ -64,7 +77,10 @@ describe UserMailer do
 
     it 'renders email change notification' do
       receiver.update!(locale: nil)
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.email_changed.title')
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('devise.mailer.email_changed.title')))
     end
 
     include_examples 'localized subject',
@@ -77,8 +93,11 @@ describe UserMailer do
 
     it 'renders warning notification' do
       receiver.update!(locale: nil)
-      expect(mail.body.encoded).to include I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)
-      expect(mail.body.encoded).to include strike.text
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)))
+        .and(have_body_text(strike.text))
     end
   end
 
@@ -88,7 +107,10 @@ describe UserMailer do
 
     it 'renders webauthn credential deleted notification' do
       receiver.update!(locale: nil)
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.deleted.title')
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('devise.mailer.webauthn_credential.deleted.title')))
     end
 
     include_examples 'localized subject',
@@ -103,7 +125,10 @@ describe UserMailer do
 
     it 'renders suspicious sign in notification' do
       receiver.update!(locale: nil)
-      expect(mail.body.encoded).to include I18n.t('user_mailer.suspicious_sign_in.explanation')
+
+      expect(mail)
+        .to be_present
+        .and(have_body_text(I18n.t('user_mailer.suspicious_sign_in.explanation')))
     end
 
     include_examples 'localized subject',
@@ -115,8 +140,10 @@ describe UserMailer do
     let(:mail) { described_class.appeal_approved(receiver, appeal) }
 
     it 'renders appeal_approved notification' do
-      expect(mail.subject).to eq I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))
-      expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_approved.title')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))))
+        .and(have_body_text(I18n.t('user_mailer.appeal_approved.title')))
     end
   end
 
@@ -125,8 +152,10 @@ describe UserMailer do
     let(:mail) { described_class.appeal_rejected(receiver, appeal) }
 
     it 'renders appeal_rejected notification' do
-      expect(mail.subject).to eq I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))
-      expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_rejected.title')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))))
+        .and(have_body_text(I18n.t('user_mailer.appeal_rejected.title')))
     end
   end
 
@@ -134,8 +163,10 @@ describe UserMailer do
     let(:mail) { described_class.two_factor_enabled(receiver) }
 
     it 'renders two_factor_enabled mail' do
-      expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_enabled.subject')
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_enabled.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('devise.mailer.two_factor_enabled.subject')))
+        .and(have_body_text(I18n.t('devise.mailer.two_factor_enabled.explanation')))
     end
   end
 
@@ -143,8 +174,10 @@ describe UserMailer do
     let(:mail) { described_class.two_factor_disabled(receiver) }
 
     it 'renders two_factor_disabled mail' do
-      expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_disabled.subject')
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_disabled.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('devise.mailer.two_factor_disabled.subject')))
+        .and(have_body_text(I18n.t('devise.mailer.two_factor_disabled.explanation')))
     end
   end
 
@@ -152,8 +185,10 @@ describe UserMailer do
     let(:mail) { described_class.webauthn_enabled(receiver) }
 
     it 'renders webauthn_enabled mail' do
-      expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_enabled.subject')
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_enabled.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('devise.mailer.webauthn_enabled.subject')))
+        .and(have_body_text(I18n.t('devise.mailer.webauthn_enabled.explanation')))
     end
   end
 
@@ -161,8 +196,10 @@ describe UserMailer do
     let(:mail) { described_class.webauthn_disabled(receiver) }
 
     it 'renders webauthn_disabled mail' do
-      expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_disabled.subject')
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_disabled.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('devise.mailer.webauthn_disabled.subject')))
+        .and(have_body_text(I18n.t('devise.mailer.webauthn_disabled.explanation')))
     end
   end
 
@@ -170,8 +207,10 @@ describe UserMailer do
     let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) }
 
     it 'renders two_factor_recovery_codes_changed mail' do
-      expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')))
+        .and(have_body_text(I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')))
     end
   end
 
@@ -180,8 +219,10 @@ describe UserMailer do
     let(:mail) { described_class.webauthn_credential_added(receiver, credential) }
 
     it 'renders webauthn_credential_added mail' do
-      expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_credential.added.subject')
-      expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('devise.mailer.webauthn_credential.added.subject')))
+        .and(have_body_text(I18n.t('devise.mailer.webauthn_credential.added.explanation')))
     end
   end
 
@@ -189,8 +230,10 @@ describe UserMailer do
     let(:mail) { described_class.welcome(receiver) }
 
     it 'renders welcome mail' do
-      expect(mail.subject).to eq I18n.t('user_mailer.welcome.subject')
-      expect(mail.body.encoded).to include I18n.t('user_mailer.welcome.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('user_mailer.welcome.subject')))
+        .and(have_body_text(I18n.t('user_mailer.welcome.explanation')))
     end
   end
 
@@ -199,8 +242,10 @@ describe UserMailer do
     let(:mail) { described_class.backup_ready(receiver, backup) }
 
     it 'renders backup_ready mail' do
-      expect(mail.subject).to eq I18n.t('user_mailer.backup_ready.subject')
-      expect(mail.body.encoded).to include I18n.t('user_mailer.backup_ready.explanation')
+      expect(mail)
+        .to be_present
+        .and(have_subject(I18n.t('user_mailer.backup_ready.subject')))
+        .and(have_body_text(I18n.t('user_mailer.backup_ready.explanation')))
     end
   end
 end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 79f98f2e2241b0f1289ca96f5f229690c1d897b1..68023b70df23e42823a0a5cc65f83321504354aa 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -21,6 +21,7 @@ require 'webmock/rspec'
 require 'paperclip/matchers'
 require 'capybara/rspec'
 require 'chewy/rspec'
+require 'email_spec/rspec'
 
 Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
 
diff --git a/spec/requests/api/v1/admin/account_actions_spec.rb b/spec/requests/api/v1/admin/account_actions_spec.rb
index bdf1f08e43b75c4a5666e41f22940e3fefa2025c..c14e08c21c96d4dc794edfd4d57745f710ea048c 100644
--- a/spec/requests/api/v1/admin/account_actions_spec.rb
+++ b/spec/requests/api/v1/admin/account_actions_spec.rb
@@ -8,18 +8,12 @@ RSpec.describe 'Account actions' do
   let(:scopes)  { 'admin:write admin:write:accounts' }
   let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
-  let(:mailer)  { instance_double(ActionMailer::MessageDelivery, deliver_later!: nil) }
-
-  before do
-    allow(UserMailer).to receive(:warning).with(target_account.user, anything).and_return(mailer)
-  end
 
   shared_examples 'a successful notification delivery' do
     it 'notifies the user about the action taken' do
-      subject
-
-      expect(UserMailer).to have_received(:warning).with(target_account.user, anything).once
-      expect(mailer).to have_received(:deliver_later!).once
+      expect { subject }
+        .to have_enqueued_job(ActionMailer::MailDeliveryJob)
+        .with('UserMailer', 'warning', 'deliver_now!', args: [User, AccountWarning])
     end
   end