Using the Outlook Reaction's feature in the Microsoft Graph
The cloud hosting era, while delivering on its promise of rapid feature deployment, often presents a frustrating disconnect: these advancements aren't always readily accessible through public APIs. This creates an engineering paradox where significant investment in developing and deploying new features is undermined by restricted developer access, limiting their utility and potential. Consequently, this can stifle innovation by preventing those with the agility to experiment from leveraging these new capabilities in novel and valuable ways.
Outlook provides several examples of user-facing features that, while beneficial, may not immediately expose their underlying functionality through public APIs. These include roaming signatures, flexible working hours integration, and pronouns. The focus of this post, however, is Reactions in Outlook, a feature that builds upon the Likes functionality first introduced in 2015. Fundamentally, this feature enables users to quickly respond to emails with an emoji, offering a lightweight alternative to composing a full reply."
The scarcity of documentation for this feature is a significant drawback. The robust engineering evident in their cross-tenant functionality indicates a sophisticated implementation extending beyond just setting new MAPI properties on a message. Consequently, the lack of developer APIs for such a well-engineered feature is puzzling.
Extended Properties for Reactions in Microsoft Graph
While the whole feature encompasses more than just a few extended properties, this is how it is accessible to client apps and how you can utilize it in Microsoft Graph to display how people have reacted to messages. The one piece of official information about the properties in use comes from this blog https://techcommunity.microsoft.com/blog/outlook/reactions-in-outlook-public-usability-update-september-2023/3928103 in the Graph these properties look like
- **SystemTime {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name OwnerReactionTime**
The time the mailbox owner reacted to the particular message. This property will not be present if the current mailbox owner has not reacted to the message.
- **String {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name OwnerReactionType**
The type of reaction made by the current mailbox owner. This property will not be present if the current mailbox owner has not reacted to the message.
- **Binary {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name MapiReactionsBlob**
This is a JSON document containing all the reactions on a message. Note that there are specific rules regarding reactions from BCC recipients.
- **Binary {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name ReactionsHistory**
This is a similar to the MapiReactionsBlob but some message will have a MapiReactionsBlob while others have a ReactionsHistory (see the link above about the state of these properites)
- **Integer {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name ReactionsCount**
The total number of reactions on a message.
- **Binary {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name ReactionsSummary**
Current state of reactions on this message with names of reactors and timestamps similar to MapiReactionsBlob but in a compressed format
Based on information from that nearly two-year-old blog post, ReactionsSummary
appears to be the most reliable property to utilize. The other reaction-related properties may be deprecated in the future and currently seem to exhibit client-dependent behaviour based on my testing.
Using these properties in Microsoft Graph
Filtering message that have a reaction of any kind
If you want to filter messages that have been reacted to you can use a filter on the ReactionsCount property eg
$filter=singleValueExtendedProperties/any(ep: ep/id eq 'Integer {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name ReactionsCount' and cast(ep/value, Edm.Int32) gt 0)
This would return messages where the ReactionsCount
property exists and its value is greater than 0.
Making the ReactionsSummary property value usable
One of the harder things about trying to tackle using this feature via the extended properties is dealing with what was referred to as a “compressed format” used as the property value. Usually things in Mapi are documented well in the Protocol documentation https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxprotlp/30c90a39-9adf-472b-8b5b-03c282304a83 . However this is one thing that isn’t documented if you look at the value in a Mapi editor it looks like
In this instance, it appears that simple binary serialization was employed rather than compression. While investigating documentation for this property, I discovered the MSGReader library on GitHub (https://github.com/Sicos1977/MSGReader), a useful free .NET Outlook MSG reader. Notably, it already included parsing logic for the ReactionsSummary
property, as seen in (https://github.com/Sicos1977/MSGReader/blob/master/MsgReaderCore/Outlook/Reaction.cs). Leveraging this existing work, and with assistance from GitHub Copilot, I successfully ported this functionality to PowerShell. After some refinement, the resulting script effectively converts the byte array value returned by this property into a usable object, for example:
This leads to a functional script, which I've shared at https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/GraphSDK/Reactions.ps1, enabling you to display emails with reactions and the corresponding reactions within a given mailbox.Invoke-GetReactionDetailsForMessages -MailboxName gscales@datarumble.com -StartTime (Get-Date).AddDays(-900) | select reactions*,Subject,ReceivedDateTime | fl
will return something like
you can also look at the owner properties to see how the current mailbox responded eg
Invoke-GetReactionDetailsForMessages -MailboxName gscales@datarumble.com -StartTime (Get-Date).AddDays(-900) -FolderId SentItems -OwnerOnly | select owner*,reactions*,Subject,ReceivedDateTime
which then changes the filter like eg
if($OwnerOnly){
$filter = "singleValueExtendedProperties/any(ep: ep/id eq 'String {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name OwnerReactionType' and ep/value ne null)"
}else{
$filter = "singleValueExtendedProperties/any(ep: ep/id eq 'Integer {41F28F13-83F4-4114-A584-EEDB5A6B0BFF} name ReactionsCount' and cast(ep/value, Edm.Int32) gt 0)"
}
Get the Reactions on a particular Message
If there in one message you sent and want to track the reactions for you can use the following if you known the internetmessageId (note the uses Get-MgUserMessage rather then the folder based Get-MgUserMailFolderMessage)
Invoke-GetReactionsDetailsOnMessage -MailboxName gscales@datarumble.com -MessageId '<cm.1432369568207.shrjikt.iupbildt.d@xxx.com>' | select owner*,reactions*,Subject,ReceivedDateTime,Internet* | fl
By ordering the reactions, you can easily identify the first and last person to react to a specific message (and give a prize to the first person who like something etc). eg
$Message.ReactionsSummary | Sort-Object -Property DateTime
or last to first
$Message.ReactionsSummary | Sort-Object -Property DateTime -Descending
Filtering by a particular Reaction
Filtering directly on the complex property ReactionsSummary
isn't possible. Therefore, to achieve this (e.g., showing emails with a "sad" reaction), client-side filtering would be required.
Invoke-GetReactionDetailsForMessages -MailboxName gscales@datarumble.com -StartTime (Get-Date).AddDays(-900) -FolderId Inbox | Where-Object {($_.ReactionsSummary.type -eq "sad")}| select owner*,reactions*,Subject,ReceivedDateTime,Internet* | fl
What about responding with your own Reactions
The lack of a public API for Outlook Reactions remains a frustrating limitation. As highlighted above, developers could leverage this feature in numerous innovative ways. Currently, even my new theoretical AI can only passively observe reactions (e.g., "likes") without the ability to programmatically respond or generate its own.
Conclusion
Reactions in Outlook is one of the standout feature that adds a unique touch to an otherwise highly commoditized service. It's a simple yet impactful way to foster engagement and bridge the generational divide.