diff --git a/app/graphql/types/work_items/email_participant_type.rb b/app/graphql/types/work_items/email_participant_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..73f9ce9c934f4e147f67cc0ad9c615b47c38851b
--- /dev/null
+++ b/app/graphql/types/work_items/email_participant_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+  module WorkItems
+    class EmailParticipantType < BaseObject
+      graphql_name 'EmailParticipantType'
+
+      # Don't use read_external_emails here, because we obfuscate emails instead.
+      authorize :read_work_item
+
+      present_using IssueEmailParticipantPresenter
+
+      field :email, GraphQL::Types::String,
+        description: 'Email address of the email participant. For guests, the email address is obfuscated.', null: false
+    end
+  end
+end
diff --git a/app/graphql/types/work_items/widgets/email_participants_type.rb b/app/graphql/types/work_items/widgets/email_participants_type.rb
index d6ee2cfd9ba0abbc76ae47a516c1ce4992969156..1ee532d612e56b1792c2f715829d1b699bf12e33 100644
--- a/app/graphql/types/work_items/widgets/email_participants_type.rb
+++ b/app/graphql/types/work_items/widgets/email_participants_type.rb
@@ -13,10 +13,10 @@ class EmailParticipantsType < BaseObject
         implements Types::WorkItems::WidgetInterface
 
         field :email_participants,
-          [GraphQL::Types::String],
+          Types::WorkItems::EmailParticipantType.connection_type,
           null: true,
           description: 'Collection of email participants associated with the work item.',
-          method: :email_participants_emails
+          method: :issue_email_participants
       end
       # rubocop:enable Graphql/AuthorizeTypes
     end
diff --git a/app/models/work_items/widgets/email_participants.rb b/app/models/work_items/widgets/email_participants.rb
index 0a0147276c59443d625ddd636b5946bdb5f1000c..37f385d8100e207bb5c94af6728308b332201f08 100644
--- a/app/models/work_items/widgets/email_participants.rb
+++ b/app/models/work_items/widgets/email_participants.rb
@@ -3,7 +3,7 @@
 module WorkItems
   module Widgets
     class EmailParticipants < Base
-      delegate :email_participants_emails, to: :work_item
+      delegate :issue_email_participants, to: :work_item
 
       def self.quick_action_commands
         [:add_email, :remove_email]
diff --git a/app/policies/issue_email_participant_policy.rb b/app/policies/issue_email_participant_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..15bd2ce3d0c444a5147ff2965e8afefcff88fc32
--- /dev/null
+++ b/app/policies/issue_email_participant_policy.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+# Model is not in a product domain namespace.
+class IssueEmailParticipantPolicy < BasePolicy # rubocop:disable Gitlab/BoundedContexts, Gitlab/NamespacedClass -- reason above
+  delegate { @subject.issue }
+end
diff --git a/app/presenters/issue_email_participant_presenter.rb b/app/presenters/issue_email_participant_presenter.rb
index 8688b9a2af10a2ed8fc1a566c2b42078276e3c94..d6d79e4ccde3f621dac031f9d3d7634312515d92 100644
--- a/app/presenters/issue_email_participant_presenter.rb
+++ b/app/presenters/issue_email_participant_presenter.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
-class IssueEmailParticipantPresenter < Gitlab::View::Presenter::Delegated
+# Model is not in a product domain namespace.
+class IssueEmailParticipantPresenter < Gitlab::View::Presenter::Delegated # rubocop:disable Gitlab/NamespacedClass -- reason above
   presents ::IssueEmailParticipant, as: :participant
 
   delegator_override :email
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 75bdd491f001e0fc0027831c90bb9a00d89e7e60..15efe808e9ee34aa7b7afbc1b8cfa9e3f87d1cc3 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -13515,6 +13515,29 @@ The edge type for [`Email`](#email).
 | <a id="emailedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
 | <a id="emailedgenode"></a>`node` | [`Email`](#email) | The item at the end of the edge. |
 
+#### `EmailParticipantTypeConnection`
+
+The connection type for [`EmailParticipantType`](#emailparticipanttype).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="emailparticipanttypeconnectionedges"></a>`edges` | [`[EmailParticipantTypeEdge]`](#emailparticipanttypeedge) | A list of edges. |
+| <a id="emailparticipanttypeconnectionnodes"></a>`nodes` | [`[EmailParticipantType]`](#emailparticipanttype) | A list of nodes. |
+| <a id="emailparticipanttypeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `EmailParticipantTypeEdge`
+
+The edge type for [`EmailParticipantType`](#emailparticipanttype).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="emailparticipanttypeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="emailparticipanttypeedgenode"></a>`node` | [`EmailParticipantType`](#emailparticipanttype) | The item at the end of the edge. |
+
 #### `EnvironmentConnection`
 
 The connection type for [`Environment`](#environment).
@@ -22446,6 +22469,14 @@ Events that describe the history and progress of a Duo Workflow.
 | <a id="emailid"></a>`id` | [`ID!`](#id) | Internal ID of the email. |
 | <a id="emailupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp the email was last updated. |
 
+### `EmailParticipantType`
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="emailparticipanttypeemail"></a>`email` | [`String!`](#string) | Email address of the email participant. For guests, the email address is obfuscated. |
+
 ### `Environment`
 
 Describes where code is deployed for a project.
@@ -36202,7 +36233,7 @@ Represents email participants widget.
 
 | Name | Type | Description |
 | ---- | ---- | ----------- |
-| <a id="workitemwidgetemailparticipantsemailparticipants"></a>`emailParticipants` | [`[String!]`](#string) | Collection of email participants associated with the work item. |
+| <a id="workitemwidgetemailparticipantsemailparticipants"></a>`emailParticipants` | [`EmailParticipantTypeConnection`](#emailparticipanttypeconnection) | Collection of email participants associated with the work item. (see [Connections](#connections)) |
 | <a id="workitemwidgetemailparticipantstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
 
 ### `WorkItemWidgetHealthStatus`
diff --git a/spec/factories/notes/notes_metadata.rb b/spec/factories/notes/notes_metadata.rb
index 555debbc0e55f4a1941e39d84c63e0243e903987..bb03e8870a17bdc5c25c58529a8dc629018fbe0c 100644
--- a/spec/factories/notes/notes_metadata.rb
+++ b/spec/factories/notes/notes_metadata.rb
@@ -3,6 +3,6 @@
 FactoryBot.define do
   factory :note_metadata, class: 'Notes::NoteMetadata' do
     note
-    email_participant { 'email@example.com' }
+    email_participant { 'user@example.com' }
   end
 end
diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb
index 863d8a074f0aaa77f392ce2c3f31f2def149786e..5e782626d76918ed8371d7c6c2db662c2e3df6b5 100644
--- a/spec/graphql/types/notes/note_type_spec.rb
+++ b/spec/graphql/types/notes/note_type_spec.rb
@@ -56,7 +56,7 @@
 
   context 'when system note with issue_email_participants action', feature_category: :service_desk do
     let_it_be(:note_text) { "added #{email}" }
-    # Create project and issue separately because we need to public project.
+    # Create project and issue separately because we need a public project.
     # rubocop:disable RSpec/FactoryBot/AvoidCreate -- Notes::RenderService updates #note and #cached_markdown_version
     let_it_be(:issue) { create(:issue, project: project) }
     let_it_be(:note) do
@@ -69,13 +69,13 @@
     describe '#body' do
       subject { resolve_field(:body, note, current_user: user) }
 
-      it_behaves_like 'a note content field with obfuscated email address'
+      it_behaves_like 'a field with obfuscated email address'
     end
 
     describe '#body_html' do
       subject { resolve_field(:body_html, note, current_user: user) }
 
-      it_behaves_like 'a note content field with obfuscated email address'
+      it_behaves_like 'a field with obfuscated email address'
     end
   end
 
@@ -83,12 +83,12 @@
     let(:note_text) { 'Note body from external participant' }
 
     let!(:note) { build(:note, note: note_text, project: project, author: Users::Internal.support_bot) }
-    let!(:note_metadata) { build(:note_metadata, note: note, email_participant: email) }
+    let!(:note_metadata) { build(:note_metadata, note: note) }
 
     describe '#external_author' do
       subject { resolve_field(:external_author, note, current_user: user) }
 
-      it_behaves_like 'a note content field with obfuscated email address'
+      it_behaves_like 'a field with obfuscated email address'
     end
   end
 
diff --git a/spec/graphql/types/work_items/email_participant_type_spec.rb b/spec/graphql/types/work_items/email_participant_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5029df657298b5538dfd2926435a53a0bde10724
--- /dev/null
+++ b/spec/graphql/types/work_items/email_participant_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::EmailParticipantType, feature_category: :service_desk do
+  include GraphqlHelpers
+
+  it 'exposes the expected fields' do
+    expected_fields = %i[email]
+
+    expected_fields.each do |field|
+      expect(described_class).to have_graphql_field(field)
+    end
+  end
+end
diff --git a/spec/models/work_items/widgets/email_participants_spec.rb b/spec/models/work_items/widgets/email_participants_spec.rb
index dc3a9fe2793781f6481fce9830d71cd9b6843744..3061cf6fd923988456124ee267e5fad8c4317310 100644
--- a/spec/models/work_items/widgets/email_participants_spec.rb
+++ b/spec/models/work_items/widgets/email_participants_spec.rb
@@ -30,9 +30,9 @@
     it { is_expected.to eq(:email_participants) }
   end
 
-  describe '#email_participants_emails' do
-    subject { described_class.new(work_item).email_participants_emails }
+  describe '#issue_email_participants' do
+    subject { described_class.new(work_item).issue_email_participants }
 
-    it { is_expected.to eq(work_item.email_participants_emails) }
+    it { is_expected.to match_array(work_item.issue_email_participants) }
   end
 end
diff --git a/spec/presenters/note_presenter_spec.rb b/spec/presenters/note_presenter_spec.rb
index 1c2c6526e8be0d2d5176286488fc686c112b64e3..b5ac542f7be5fbaabc3731ad93baefe196a2a92d 100644
--- a/spec/presenters/note_presenter_spec.rb
+++ b/spec/presenters/note_presenter_spec.rb
@@ -20,13 +20,13 @@
   describe '#note' do
     subject { presenter.note }
 
-    it_behaves_like 'a note content field with obfuscated email address'
+    it_behaves_like 'a field with obfuscated email address'
   end
 
   describe '#note_html' do
     subject { presenter.note_html }
 
-    it_behaves_like 'a note content field with obfuscated email address'
+    it_behaves_like 'a field with obfuscated email address'
 
     it 'runs post processing pipeline' do
       # Ensure post process pipeline runs
@@ -40,10 +40,10 @@
   describe '#external_author' do
     let!(:note_text) { "note body" }
     let!(:note) { build(:note, :system, author: Users::Internal.support_bot, note: note_text) }
-    let!(:note_metadata) { build(:note_metadata, note: note, email_participant: email) }
+    let!(:note_metadata) { build(:note_metadata, note: note) }
 
     subject { presenter.external_author }
 
-    it_behaves_like 'a note content field with obfuscated email address'
+    it_behaves_like 'a field with obfuscated email address'
   end
 end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index 80d97df429bab3436b015a4640cd1b7dcbeb89de..16307fbd9049624076bf44ea5d63212b7888cfca 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -1190,6 +1190,66 @@ def id_hash(object)
       end
     end
 
+    describe 'email participants widget' do
+      let_it_be(:email) { 'user@example.com' }
+      let_it_be(:obfuscated_email) { 'us*****@e*****.c**' }
+      let_it_be(:issue_email_participant) { create(:issue_email_participant, issue_id: work_item.id, email: email) }
+
+      let(:work_item_fields) do
+        <<~GRAPHQL
+          id
+          widgets {
+            type
+            ... on WorkItemWidgetEmailParticipants {
+              emailParticipants {
+                nodes {
+                  email
+                }
+              }
+            }
+          }
+        GRAPHQL
+      end
+
+      it 'contains the email' do
+        expect(work_item_data).to include(
+          'widgets' => array_including(
+            hash_including(
+              'type' => 'EMAIL_PARTICIPANTS',
+              'emailParticipants' => {
+                'nodes' => containing_exactly(
+                  hash_including(
+                    'email' => email
+                  )
+                )
+              }
+            )
+          )
+        )
+      end
+
+      context 'when user has the guest role' do
+        let(:current_user) { guest }
+
+        it 'contains the obfuscated email' do
+          expect(work_item_data).to include(
+            'widgets' => array_including(
+              hash_including(
+                'type' => 'EMAIL_PARTICIPANTS',
+                'emailParticipants' => {
+                  'nodes' => containing_exactly(
+                    hash_including(
+                      'email' => obfuscated_email
+                    )
+                  )
+                }
+              )
+            )
+          )
+        end
+      end
+    end
+
     context 'when an Issue Global ID is provided' do
       let(:global_id) { Issue.find(work_item.id).to_gid.to_s }
 
diff --git a/spec/serializers/note_entity_spec.rb b/spec/serializers/note_entity_spec.rb
index dd463b89252930fb4d3b8ae3bd5b66189888f43a..a0a3626d82b891b14357cf638a39d4b3df991884 100644
--- a/spec/serializers/note_entity_spec.rb
+++ b/spec/serializers/note_entity_spec.rb
@@ -8,50 +8,43 @@
   # rubocop:disable RSpec/FactoryBot/AvoidCreate -- Persisted records required
   let_it_be(:note) { create(:note) }
   let_it_be(:user) { create(:user) }
+  let_it_be(:email) { 'user@example.com' }
   # rubocop:enable RSpec/FactoryBot/AvoidCreate
 
   let(:request) { double('request', current_user: user, noteable: note.noteable) }
 
   let(:entity) { described_class.new(note, request: request) }
+  let(:obfuscated_email) { 'us*****@e*****.c**' }
 
   subject(:entity_hash) { entity.as_json }
 
   it_behaves_like 'note entity'
 
-  describe 'with email participant', feature_category: :service_desk do
+  context 'when note from external participant', feature_category: :service_desk do
     let!(:note_metadata) { build(:note_metadata, note: note) }
 
     subject { entity.as_json[:external_author] }
 
-    context 'with external note author' do
-      let(:obfuscated_email) { 'em*****@e*****.c**' }
-      let(:email) { 'email@example.com' }
-
-      it_behaves_like 'a note content field with obfuscated email address'
-    end
+    it_behaves_like 'a field with obfuscated email address'
   end
 
   context 'when system note with issue_email_participants action', feature_category: :service_desk do
-    let_it_be(:email) { 'user@example.com' }
     let_it_be(:note_text) { "added #{email}" }
     # rubocop:disable RSpec/FactoryBot/AvoidCreate -- Notes::RenderService updates #note and #cached_markdown_version
     let_it_be(:note) { create(:note, :system, author: Users::Internal.support_bot, note: note_text) }
     let_it_be(:system_note_metadata) { create(:system_note_metadata, note: note, action: :issue_email_participants) }
     # rubocop:enable RSpec/FactoryBot/AvoidCreate
 
-    let(:obfuscated_email) { 'us*****@e*****.c**' }
-    let(:expected_text) { obfuscated_email }
-
     describe 'note' do
       subject { entity_hash[:note] }
 
-      it_behaves_like 'a note content field with obfuscated email address'
+      it_behaves_like 'a field with obfuscated email address'
     end
 
     describe 'note_html' do
       subject { entity_hash[:note_html] }
 
-      it_behaves_like 'a note content field with obfuscated email address'
+      it_behaves_like 'a field with obfuscated email address'
     end
   end
 end
diff --git a/spec/support/shared_examples/graphql/notes_content_obfuscation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_content_obfuscation_shared_examples.rb
index 0f8f24a034f2e8825fb676bd42e83fe340c7395c..e378db0ce2a836c0d5f5307c81fe81221f68c57a 100644
--- a/spec/support/shared_examples/graphql/notes_content_obfuscation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_content_obfuscation_shared_examples.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
-RSpec.shared_examples 'a note content field with obfuscated email address' do
+RSpec.shared_examples 'a field with obfuscated email address' do
+  let(:resource_parent) { note.project }
+
   context 'when anonymous' do
     let(:user) { nil }
 
@@ -9,7 +11,7 @@
 
   context 'with signed in user' do
     before do
-      stub_member_access_level(note.project, access_level => user) if access_level
+      stub_member_access_level(resource_parent, access_level => user) if access_level
     end
 
     context 'when user has no role in project' do