How to use Microsoft Graph API to track the flow of email messages through your organization

Message trace API

As an Exchange Online administrator, you may notice that the Exchange Web Services (EWS) API is set to be retired in October 2026. As a result, Microsoft Graph API is now the recommended approach for accessing Exchange Online data and new features are gradually being added to Microsoft Graph.

One of the new features is the message trace API to track the flow of email messages. The Message Trace API replaces the Office 365 Reporting Web Service.

You can track:

  • delivery status of the message
  • date and time the message was received
  • sender and recipient email addresses
  • subject of the email
  • source and destination IP addresses
  • message-id header
  • size of the message

The status of the message can be:

  • gettingStatus
  • pending
  • failed
  • delivered
  • expanded
  • quarantined
  • filteredAsSpam

To list message traces, you can use the following endpoint:

GET /beta/admin/exchange/tracing/messageTraces

The app must be granted the ExchangeMessageTrace.Read.All delegated or application permission.

When calling the GET /beta/admin/exchange/tracing/messageTrace endpoint for the first time, you might encounter the following error:

{
  "error": {
    "code": "Unauthorized",
    "message": "Service principal-less Authentication failed: the service principal for App ID 8bd644d1-64a1-4d4b-ae52-2e0cbf64e373 was not found. Please create a service principal for this app in your tenant. Provisioning may take several hours to complete. For details, see: https://learn.microsoft.com/entra/identity-platform/retire-service-principal-less-authentication?branch=main#create-a-service-principal",
    ...
  }
}

Due to the service principal-less authentication mitigation effort, you need to create a service principal for the application with id 8bd644d1-64a1-4d4b-ae52-2e0cbf64e373.

POST /v1.0/servicePrincipals
{
  "appId": "8bd644d1-64a1-4d4b-ae52-2e0cbf64e373"
}

After creating the service principal, you should be able to call the message trace API successfully. It can take a while, so give it few minutes or hours.

Tracing will only work if the service principal is created.

The app with ID 8bd644d1-64a1-4d4b-ae52-2e0cbf64e373 is a built-in Microsoft application called Transport Data Platform

The response will look like this:

{
    "value": [
        {
            "id": "339e40d1-9a34-4bc5-adbe-08de5bf70906",
            "messageId": "<a5cf7e42-ad2f-4f81-89c5-aaaaaaab86f@VIAPPFE7A2FBFA8.EURP250.PROD.OUTLOOK.COM>",
            "status": "delivered",
            "receivedDateTime": "2026-01-25T09:49:29.967Z",
            "recipientAddress": "MartinMachacek@4wrvkx.onmicrosoft.com",
            "senderAddress": "MicrosoftExchange123abcec88ae4615bbc36ab6ce41109e@4wrvkx.onmicrosoft.com",
            "subject": "Undeliverable: Meeting",
            "size": 69376,
            "fromIP": "255.255.255.255",
            "toIP": ""
        },
        {
            "id": "eaaafa9a-788c-47f0-357c-08de5bf702e0",
            "messageId": "<AMBP250MB14920887FE274BC92A@AMBP250MB9875.EURP250.PROD.OUTLOOK.COM>",
            "status": "failed",
            "receivedDateTime": "2026-01-25T09:49:19.648Z",
            "recipientAddress": "aaa@bbb.ccccccc",
            "senderAddress": "MartinMachacek@4wrvkx.onmicrosoft.com",
            "subject": "Meeting",
            "size": 25249,
            "fromIP": "89.177.230.109",
            "toIP": ""
        },
        {
            "id": "eaaafa9a-788c-47f0-357c-08de5bf702e0",
            "messageId": "<AMBP250MB14920887FE272A7C4BC92A@AMBP250MB9875.EURP250.PROD.OUTLOOK.COM>",
            "status": "delivered",
            "receivedDateTime": "2026-01-25T09:49:19.648Z",
            "recipientAddress": "AlexW@4wrvkx.onmicrosoft.com",
            "senderAddress": "MartinMachacek@4wrvkx.onmicrosoft.com",
            "subject": "Meeting",
            "size": 39686,
            "fromIP": "89.177.230.109",
            "toIP": ""
        }
    ]
}

I sent a test email from MartinMachacek@4wrvkx.onmicrosoft.com to AlexW@4wrvkx.onmicrosoft.com and aaa@bbb.ccccccc.

The email to AlexW@4wrvkx.onmicrosoft.com was delivered successfully, while the email to aaa@bbb.ccccccc failed.

You might noticed that two message traces have the same message trace ID. This is because one message trace is created for each recipient.

You can use the message trace ID and recipient address to get more details about a specific message trace:

GET /beta/admin/exchange/tracing/messageTraces/{exchangeMessageTraceId}/getDetailsByRecipient(recipientAddress='parameterValue')

For example, let's get the details for the failed email:

GET /beta/admin/exchange/tracing/messageTraces/eaaafa9a-788c-47f0-357c-08de5bf702e0/getDetailsByRecipient(recipientAddress='aaa@bbb.ccccccc')

The response:

{
    "value": [
        {
            "id": "eaaafa9a-788c-47f0-357c-08de5bf702e0",
            "messageId": "<AMBP250MB1492088EBC4BC92A@AMBP250MB9875.EURP250.PROD.OUTLOOK.COM>",
            "dateTime": "2026-01-25T09:49:20.187Z",
            "event": "Submit",
            "action": "",
            "description": "The message was submitted.",
            "data": "<root><MEP Name=\"RcptCount\" Integer=\"2\" /><MEP Name=\"ServerHostName\" String=\"VI6PPFE7A2F83A8.EURP250.PROD.OUTLOOK.COM\" /><MEP Name=\"ClientName\" String=\"AMBP250MB1492\" /><MEP Name=\"SequenceNumber\" Long=\"0\" /></root>"
        },
        {
            "id": "eaaafa9a-788c-47f0-357c-08de5bf702e0",
            "messageId": "<AMBP250MB14920887F4BC92A@AMBP250MB9875.EURP250.PROD.OUTLOOK.COM>",
            "dateTime": "2026-01-25T09:49:26.421Z",
            "event": "Receive",
            "action": "",
            "description": "Message received by: VI6P2F83A8.EURP250.PROD.OUTLOOK.COM",
            "data": "<root><MEP Name=\"ConnectorId\" String=\"VI6P2F83A8\\Default VI6F83A8\" /><MEP Name=\"ClientIP\" String=\"2603:10a6:20b:764::22\" /><MEP Name=\"ServerHostName\" String=\"VI6PP83A8.EURP250.PROD.OUTLOOK.COM\" /><MEP Name=\"FirstForestHop\" String=\"VI6PP83A8.EURP250.PROD.OUTLOOK.COM\" /><MEP Name=\"DeliveryPriority\" String=\"Normal\" /><MEP Name=\"ReturnPath\" String=\"MartinMachacek@4wrvkx.onmicrosoft.com\" /><MEP Name=\"CustomData\" Blob=\"S:tlsversion=NONE\" /><MEP Name=\"SequenceNumber\" Long=\"0\" /></root>"
        },
        {
            "id": "eaaafa9a-788c-47f0-357c-08de5bf702e0",
            "messageId": "<AMBP250MB1EBC4BC92A@AMBP250MB9875.EURP250.PROD.OUTLOOK.COM>",
            "dateTime": "2026-01-25T09:49:28.442Z",
            "event": "TRANSFER",
            "action": "",
            "description": null,
            "data": "<root><MEP Name=\"DeliveryPriority\" String=\"Normal\" /><MEP Name=\"ReturnPath\" String=\"MartinMachacek@4wrvkx.onmicrosoft.com\" /><MEP Name=\"SequenceNumber\" Long=\"0\" /><MEP Name=\"RecipientReference\" String=\"46729082\" /></root>"
        },
        {
            "id": "eaaafa9a-788c-47f0-357c-08de5bf702e0",
            "messageId": "<AMBP250MB14922A@AMBP250MB9875.EURP250.PROD.OUTLOOK.COM>",
            "dateTime": "2026-01-25T09:49:29.981Z",
            "event": "Fail",
            "action": "",
            "description": "Reason: [{LED=550 5.4.310 DNS domain bbb.ccccccc does not exist [Message=InfoDomainNonexistent] [LastAttemptedServerName=bbb.ccccccc] [AM4PEPF0002BAA6.EURPRD83.prod.outlook.com 2026-01-25T09:49:29.944Z 08DE5AB03F707551]};{MSG=InfoDomainNonexistent};{FQDN=bbb.ccccccc};{IP=0.0.0.0};{LRT=1/25/2026 9:49:29 AM}]. OutboundProxyTargetHostName: bbb.ccccccc",
            "data": "<root><MEP Name=\"RcptCount\" Integer=\"1\" /><MEP Name=\"ConnectorId\" String=\"Outbound Proxy External Send Connector\" /><MEP Name=\"ServerHostName\" String=\"bbb.ccccccc\" /><MEP Name=\"DeliveryPriority\" String=\"Normal\" /><MEP Name=\"TotalLatency\" Integer=\"10\" /><MEP Name=\"OutboundProxyTargetHostName\" String=\"bbb.ccccccc\" /><MEP Name=\"ReturnPath\" String=\"MartinMachacek@4wrvkx.onmicrosoft.com\" /><MEP Name=\"ClientName\" String=\"VI6PA8.EURP250.PROD.OUTLOOK.COM\" /><MEP Name=\"CustomData\" Blob=\"S:OutboundProxyFrontEndName=eur.internal.map.protection.eop.outlook.com;S:IsSmtpResponseFromExternalServer=False;S:Microsoft.Exchange.Transport.MailRecipient.RequiredTlsAuthLevel=DomainValidation;S:ExternalSendLatency=10.297;S:OutboundIpPool=1102;S:OutboundIpPoolName=RegularOutboundPool\" /><MEP Name=\"SequenceNumber\" Long=\"0\" /><MEP Name=\"RecipientStatus\" String=\"[{LED=550 5.4.310 DNS domain bbb.ccccccc does not exist [Message=InfoDomainNonexistent] [LastAttemptedServerName=bbb.ccccccc] [AM4PEPF0002BAA6.EURPRD83.prod.outlook.com 2026-01-25T09:49:29.944Z 08DE5AB03F707551]};{MSG=InfoDomainNonexistent};{FQDN=bbb.ccccccc};{IP=0.0.0.0};{LRT=1/25/2026 9:49:29 AM}]\" /><MEP Name=\"RecipientReference\" String=\"&lt;a5cf7e42-ad2f-4f81-89c5-97dda6f@VI6PP3A8.EURP250.PROD.OUTLOOK.COM&gt;\" /></root>"
        }
    ]
}

The detailed events are provided in chronological order and we can see that the message was submitted, received by SMTP server, transferred to the recipient's domain, and finally failed due to non-existent domain.

The data property associated with the event, contains supplementary information specific to the event.

<root>
  <MEP Name="RcptCount" Integer="1" />
  <MEP Name="ConnectorId" String="Outbound Proxy External Send Connector" />
  <MEP Name="ServerHostName" String="bbb.ccccccc" />
  <MEP Name="DeliveryPriority" String="Normal" />
  <MEP Name="TotalLatency" Integer="10" />
  <MEP Name="OutboundProxyTargetHostName" String="bbb.ccccccc" />
  <MEP Name="ReturnPath" String="MartinMachacek@4wrvkx.onmicrosoft.com" />
  <MEP Name="ClientName" String="VI6A8.EURP250.PROD.OUTLOOK.COM" />
  <MEP
    Name="CustomData"
    Blob="S:OutboundProxyFrontEndName=eur.internal.map.protection.eop.outlook.com;S:IsSmtpResponseFromExternalServer=False;S:Microsoft.Exchange.Transport.MailRecipient.RequiredTlsAuthLevel=DomainValidation;S:ExternalSendLatency=10.297;S:OutboundIpPool=1102;S:OutboundIpPoolName=RegularOutboundPool" />
  <MEP Name="SequenceNumber" Long="0" />
  <MEP
    Name="RecipientStatus"
    String="[{LED=550 5.4.310 DNS domain bbb.ccccccc does not exist [Message=InfoDomainNonexistent] [LastAttemptedServerName=bbb.ccccccc] [AM4PEPF0002BAA6.EURPRD83.prod.outlook.com 2026-01-25T09:49:29.944Z 08DE5AB03F707551]};{MSG=InfoDomainNonexistent};{FQDN=bbb.ccccccc};{IP=0.0.0.0};{LRT=1/25/2026 9:49:29 AM}]" />
  <MEP
    Name="RecipientReference"
    String="&lt;a5cf7e42-ad2f-4f81-89c5-abc1236ab86f@VI6PPA8.EURP250.PROD.OUTLOOK.COM&gt;" />
</root>

Supported ODATA query parameters

The message trace API supports the following ODATA query parameters to filter and sort the results:

  • $filter - to filter message traces
  • $top - to specify the number of results to return. Default value is 1000. Range is 1 to 5000.
Property Supported operators
id eq
messageId eq
status eq
receivedDateTime range must be specified using the ge and le operators
recipientAddress eq
senderAddress eq
subject contains, startsWith
size -
fromIP eq
toIP eq

As you can see, only a limited set of query operators are supported for filtering.

Conclusion

Now, we can obtain Exchange Online message trace results via the Microsoft Graph API. Application permissions are supported, which is a significant advantage for automated processes. On the other hand, the API is still in beta, and its capabilities are quite limited compared to what one might expect from the Microsoft Graph API.

0
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x