Microsoft 365 Groups, Conversations, Threads, and Posts

Microsoft 365 Groups Have Some Graph API Support, but not Mailbox API Support

After writing about why Microsoft Graph doesn’t support Group mailboxes, some readers argued that this statement is misleading because the Graph includes the conversation and threads APIs, which my critics held to be analogous to listing items in user or shared mailboxes. I hold to my assertion because the available Graph APIs do not support all the elements of group mailboxes available through the Outlook mailbox API. However, an explanation of these APIs and their use is warranted.

The History of Outlook Groups

First, let’s set the background by explaining some history. Microsoft launched Office 365 Groups at the Ignite conference in May 2015. The idea was to facilitate email-based collaboration, with group members (including external guests) being able to contribute by sending email to the group to create new conversations or add responses to existing threads. Microsoft wanted a replacement for public folders (still alive and kicking) and was influenced by the apparent success of Yammer, bought in May 2012 as an enterprise social networking play. Yammer, now Viva Engage, organizes its conversations into topics and threads.

Over time, Office 365 Groups became Microsoft 365 Groups, with the few groups used for email-based collaboration being referred to as “Outlook groups.” The remainder of the groups acted as a membership and resource management layer for Teams, which rapidly ate Yammer’s lunch after its 2017 introduction.

Conversations and Threads

When users access an Outlook group, they see items arranged in descending date order. These items are posts, and they belong to threads. Threads belong to conversations, as described by Microsoft, a conversation is:

“a collection of threads, and a thread contains posts to that thread. All threads and posts in a conversation share the same subject.”

In practical terms, a conversation often contains a single thread, and the posts that make up the thread comprise the entire conversation. This hierarchy reflects the underlying Exchange Online implementation rather than the way most people think about email discussions. Figure 1 shows the Outlook groups interface in OWA. Three separate conversations are visible with a post to the Copilot PAYG conversation visible in the reading pane.

Using OWA to read posts in a group conversation.

Outlook groups.
Figure 1: Using OWA to read posts in a group conversation

Finding Conversations with the Graph SDK

For the purpose of these examples, we assume that the $Group variable holds the details of the target Outlook group. Unhappily, the List conversations API used by the Get-MgGroupConversation cmdlet doesn’t support server-side filtering against the Topic property. To find a specific conversation, we use search instead. A search might return several matching items, so we select the first:

[array]$Conversations = Get-MgGroupConversation -GroupId $Group.Id -Search "Copilot PAYG"
$Conversation = $Conversations[0]

To find the threads in the conversation, run the Get-MgGroupConversationThread cmdlet. The data returned includes a preview of the body for the posts and the names of the unique contributors (senders) to the thread:

[array]$Thread = Get-MgGroupConversationThread -ConversationId $Conversation.id -GroupId $Group.Id

And then extract the posts for the thread using the Get-MgGroupThreadPost cmdlet:

[array]$Posts = Get-MgGroupThreadPost -ConversationThreadId $Thread.id -GroupId $Group.Id

To extract the HTML body content from the individual posts, we can do something like this:

Add-Type -AssemblyName System.Web

$Posts | ForEach-Object {
  $Text = [System.Web.HttpUtility]::HtmlDecode($_.Body.Content)
  $Text = $Text -replace '<[^>]+>', ' '
  $Text = $Text -replace '\s+', ' '
  $Text.Trim()
}

TIL PAYG budgets for copilot are "informative" only. Makes sense to combine this with the black box that 99% of the agents are. https://www.reddit.com/r/copilotstudio/comments/1ueqedl/woke_up_to_a_47k_bill_after_deploying_one_copilot/
Oooooh……

Creating and Responding to Posts

To create a new conversation, create a request body that describes the post, thread, and conversation (the post is in the thread, and the thread is in the conversation). This example also shows how to add an extra contributor to the conversation. Outlook groups support the RequireSenderAuthenticationEnabled property. When the value is $False, Exchange Online accepts messages from external senders for delivery to the group because sender authentication is not required.

$EmailAddress = [pscustomobject]@{
    Name    = 'Buddy.Russo'
    Address = 'Buddy.Russo@o365maestro.onmicrosoft.com'
}

$Participant = [pscustomobject]@{
    EmailAddress = $EmailAddress
}

$Body = [pscustomobject]@{
    ContentType = 'html'
    Content     = '<h2>We care about you: Rest and Recharge</h2><p>Despite your misgivings, we really want you to rest and recover during your days off work. Turn off your devices and do not respond to email. You know it makes sense!<p>Your Wellness team</p>'
}

$Post = [pscustomobject]@{
    Body            = $Body
    NewParticipants = @($Participant)
}

$Thread = [pscustomobject]@{
    Posts = @($Post)
}

$NewConversationDetails = [pscustomobject]@{
    Topic   = 'Be sure to take your wellness days and rest'
    Threads = @($Thread)
}

Now run the New-MgGroupConversation cmdlet to create the new conversation (including the thread and post):

$NewConversation = New-MgGroupConversation -GroupId $group.Id -BodyParameter ($NewConversationDetails | ConvertTo-JSon -Depth 10)

The New-MgGroupConversation cmdlet does not accept a PSCustomObject directly for the BodyParameter parameter. Converting the object to JSON allows the cmdlet to process the request successfully (at least, it does in SDK V2.39).

To reply to a post, create a request body containing the response content, and run the Invoke-MgReplyGroupThreadPost cmdlet. You need to find the conversation, thread, and post identifiers first.

$Conversation = Get-MgGroupConversation -GroupId $Group.Id -Top 1
$Thread = Get-MgGroupConversationThread -ConversationId $Conversation.Id -GroupId $Group.Id
$Post = Get-MgGroupThreadPost -ConversationThreadId $Thread.id -GroupId $Group.Id -Top 1

Now build a request body. The body is very simple because it only holds the HTML content for the response:

$BodyParameter = @{
    post = @{
        body = @{
            contentType = 'html'
            content     = "Rest is all very well, but when will we get <b>some time off?</b>"
        }
    }
}

Finally, run the Invoke-MgReplyGroupThreadPost cmdlet to post the response:

Invoke-MgReplyGroupThreadPost -GroupId $Group.Id -ConversationThreadId $Thread.Id -PostId $PostId -BodyParameter $BodyParameter

Figure 2 shows the response and the original post as viewed through OWA.

Figure 2: A response to a post in an Outlook group conversation

In Summary

Using the Graph to interact with Outlook Groups is very different to using the Outlook mailbox API to process data found in user and shared mailboxes. Different APIs, different structures, and different cmdlets combine to create a bump in the road to understanding how the Graph deals with Outlook Groups. It’s manageable and just takes time. And if you really want to go deep and combine Exchange Online cmdlets, the Graph Import-Export API, and some coding gymnastics, you will be able to retrieve information from group mailbox folders other than the Inbox. But life’s much too short for that.


Need help to write and manage PowerShell scripts for Microsoft 365, including Azure Automation runbooks? Get a copy of the Automating Microsoft 365 with PowerShell eBook, available standalone or as part of the Microsoft 365 for IT Pros eBook bundle.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.