📧 Recipe · Exchange & Email
List Members of a Mail-Enabled Group Including Organizational Contacts
Resolve every recipient — users, guests, and org contacts — that receives mail sent to a distribution or mail-enabled group
Complexity
Intermediate
Impact
Exchange Online + Groups + Mail Flow + Microsoft Graph + Directory
Context
Why This Matters
When a user asks "who actually gets email when I send to group@contoso.com?", a simple member lookup is often not enough. Mail-enabled groups in Microsoft 365 can contain:
- User objects — internal employees with mailboxes
- Guest users — external B2B collaborators
- Other groups — nested distribution or security groups
- Organizational contacts (
orgContact) — external recipients synced from on-premises AD or created in Exchange, often representing vendors, partners, or shared aliases
Graph's /groups/{id}/members endpoint happily returns orgContact entries, but their displayName, mail, and proxyAddresses fields come back as null unless your app has OrgContact.Read.All. Admins chasing a "missing recipient" incident frequently hit this wall. This recipe walks through resolving every recipient behind a mail-enabled group, including the org-contact case.
Expected Outcomes
After running this recipe you will have:
- The group's object ID, display name, mail address, and group type (unified, security, distribution, mail-enabled security)
- A full list of direct members, with the object type for each (
user,group,orgContact,device) - Resolved display names and SMTP addresses for org-contact members (requires
OrgContact.Read.All) - A list of group owners (who can manage membership, and who receives NDRs in some configurations)
- Optional: flattened/transitive membership if the group contains nested groups
Risks & Considerations
Permission scope matters
Without OrgContact.Read.All, organizational contacts appear in /members responses but all their useful properties are null. Admins often misdiagnose this as "empty group" or "broken members". Grant the scope (admin consent required) before concluding the group is misconfigured.
Distribution vs. Microsoft 365 groups
For classic distribution lists (groupTypes is empty, mailEnabled: true, securityEnabled: false), Graph exposes membership read-only. To modify membership you must use Exchange Online PowerShell (Add-DistributionGroupMember), not Graph.
Nested groups and dynamic membership
Direct /members does not expand nested groups. Use /transitiveMembers to see every leaf recipient. Dynamic groups populate asynchronously — very recent rule changes may not be reflected.
Privacy
Enumerating recipients of an internal DL can expose sensitive org structure. Do not export full membership to external systems without approval, and redact guest/external contact addresses in tickets where possible.
Required Permissions
| Permission | Why It's Needed |
|---|---|
| Group.Read.All | Read group properties and direct/transitive membership via Microsoft Graph. |
| GroupMember.Read.All | Alternative least-privilege scope that allows reading members without other group properties. |
| OrgContact.Read.All | Required to resolve displayName, mail, and proxyAddresses for organizational contacts that appear as group members. |
| User.Read.All | Resolve details for user members (UPN, mail, accountEnabled). |
| Directory.Read.All | Fallback broad read scope if the tenant has not granted the narrower scopes above. |
The fastest way to get this done: just ask Dex. Copy the prompt below and paste it into your Dex conversation.
For IT Admins
Paste into Dex Pro
For End Users
How an employee would ask Dex for help
Using the Microsoft 365 admin center and Exchange admin center
Step 1 — Locate the group
- Sign in to the Microsoft 365 admin center as a Global Reader, Exchange Administrator, or Groups Administrator.
- Navigate .
- Select the tab matching the group type: Microsoft 365, Distribution list, Mail-enabled security, or Security.
- Search for the group by name or SMTP address and open it.
Step 2 — Review members in the admin center
- Open the Members tab. You will see internal users and guests listed with display name and email.
- Note: organizational contacts (external mail contacts) often do not render here with full details. If the count looks lower than expected, continue in the Exchange admin center.
Step 3 — Verify full recipient list in EAC
- Open the Exchange admin center → .
- Select the group and open Members. EAC reliably shows mail contacts alongside users and nested groups.
- Also check Settings → Delivery management to see whether external senders are allowed, and Message approval for moderation rules that can affect who ultimately receives mail.
Step 4 — Check nested groups
If any member is itself a group, click into it and repeat. The effective recipient list is the union of all leaf users and contacts across the tree.
Resolving all recipients via Microsoft Graph
Step 1 — Find the group by SMTP address
GET https://graph.microsoft.com/v1.0/groups?$filter=mail eq 'group@contoso.com'&$select=id,displayName,mail,groupTypes,mailEnabled,securityEnabledResponse:
{
"value": [{
"id": "0049645a-e18c-46f6-86bc-1bc2a27d22c2",
"displayName": "marketing-notif",
"mail": "group@contoso.com",
"groupTypes": [],
"mailEnabled": true,
"securityEnabled": false
}]
}An empty groupTypes array with mailEnabled: true indicates a classic distribution list.
Step 2 — List direct members
GET /v1.0/groups/{groupId}/members?$select=id,displayName,mail,userPrincipalNameLook at @odata.type on each entry. Values you will encounter:
#microsoft.graph.user— internal user or guest#microsoft.graph.group— nested group#microsoft.graph.orgContact— organizational mail contact (often returnsnullfields without the right scope)
Step 3 — Resolve org contacts (requires OrgContact.Read.All)
When an orgContact entry returns null for mail and displayName, re-query each contact by ID against the contacts endpoint:
GET /v1.0/contacts/{contactId}?$select=displayName,mail,proxyAddresses,companyNameOr batch with a filter:
GET /v1.0/contacts?$filter=id eq '{id1}' or id eq '{id2}'&$select=displayName,mail,proxyAddressesIf you receive 403 Insufficient privileges, grant OrgContact.Read.All (admin consent required) and retry.
Step 4 — Expand nested groups transitively
GET /v1.0/groups/{groupId}/transitiveMembers?$select=id,displayName,mail,userPrincipalNameThis flattens every level of nesting and returns the full leaf recipient set (users + contacts). Note that $select is still subject to the same orgContact caveat.
Step 5 — List owners
GET /v1.0/groups/{groupId}/owners?$select=displayName,mail,userPrincipalNameOwners do not receive group mail by default, but they manage membership and are useful context for the requester.
PowerShell with Microsoft.Graph
The script below accepts a group SMTP address, resolves the group, enumerates direct and transitive members, and re-queries any org contacts to pull their real mail addresses. It handles the common null-field gotcha automatically.
Prerequisites
- PowerShell 7+ and the
Microsoft.Graphmodule:Install-Module Microsoft.Graph -Scope CurrentUser - Scopes:
Group.Read.All,OrgContact.Read.All,User.Read.All
Quick interactive run
Connect-MgGraph -Scopes "Group.Read.All","OrgContact.Read.All","User.Read.All"
$group = Get-MgGroup -Filter "mail eq 'group@contoso.com'"
$members = Get-MgGroupMember -GroupId $group.Id -All
# Split by type
$users = $members | Where-Object { $_.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.user' }
$contacts = $members | Where-Object { $_.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.orgContact' }
# Re-query contacts for real mail addresses
$contacts | ForEach-Object { Get-MgContact -OrgContactId $_.Id }For a reusable version with transitive expansion, CSV export, and error handling, download the full script below.
Next steps