Get incremental changes to messages in a folder
Delta query lets you query for additions, deletions, or updates to messages in a folder by way of a series of delta function calls. Delta data enables you to maintain and synchronize a local store of a user's messages without having to fetch the entire set of the user's messages from the server every time.
Synchronizing message items in a local store can use the delta query for the initial full synchronization and subsequent incremental synchronizations. Typically, you would do an initial full synchronization of all the messages in a folder (for example, the user's Inbox), and then, get incremental changes to that folder periodically.
To get incremental changes of only a certain type - messages that are created, updated, or deleted since the initial sync - do an initial round of synchronization of all the messages in the folder, and then get incremental changes of a specific desired type in subsequent rounds. Specify the desired change type as a query option in the initial delta request; Microsoft Graph automatically encodes any OData and custom query options into the @odata.nextLink or @odata.deltaLink provided in the response.
Track message changes in a folder
Delta query is a per-folder operation. To track the changes of the messages in a folder hierarchy, you need to track each folder individually.
Tracking message changes in a mail folder typically is a round of one or more GET requests with the delta function. The initial GET request is very much like the way you get messages, except that you include the delta function:
GET https://graph.microsoft.com/v1.0/me/mailFolders/{id}/messages/delta
A GET request with the delta function returns either:
- A
@odata.nextLink
(that contains a URL with a delta function call and a skipToken), or - A
@odata.deltaLink
(that contains a URL with a delta function call and deltaToken).
These tokens are state tokens that are opaque to the client.
To proceed with a round of change tracking, copy and apply the URL returned from the last GET
request to the next delta function call for the same folder. A @odata.deltaLink
returned in a response
signifies that the current round of change tracking is complete. You can save and use the @odata.deltaLink
URL
when you begin the next round.
The rest of this article includes 2 examples:
- See example 1 to learn how to use the
@odata.nextLink
and@odata.deltaLink
URLs. - See example 2 to learn how to incrementally get only messages created since the initial round.
Use query parameters in a delta query for messages
- You can use a
$select
query parameter as in any GET request to specify only the properties your need for best performance. Theid
property is always returned. - Delta query support
$select
,$top
, and$expand
for messages. - There is limited support for
$filter
and$orderby
:- The only supported
$filter
expressions are$filter=receivedDateTime+ge+{value}
or$filter=receivedDateTime+gt+{value}
. - Applying
$filter
in a delta query returns only up to 5,000 messages. - The only supported
$orderby
expression is$orderby=receivedDateTime+desc
. If you don't include an$orderby
expression, the return order isn't guaranteed.
- The only supported
- There is no support for
$search
.
Additionally, to return only certain type of changes (created, updated or deleted) in the delta query's response, you can optionally filter the desired type of change using a custom query option changeType
. Possible values are created
, updated
or deleted
.
GET /me/mailfolders/{id}/messages/delta?changeType=created
GET /me/mailfolders/{id}/messages/delta?changeType=updated
GET /me/mailfolders/{id}/messages/delta?changeType=deleted
Optional request header
Each delta query GET request returns a collection of one or more messages in the response. You can optionally specify
the request header, Prefer: odata.maxpagesize={x}
, to set the maximum number of messages in a response.
Example 1: synchronize messages in a folder
The following example shows 2 rounds of synchronization of a specific folder that initially contains 5 messages.
The first round involves a series of 3 requests to synchronize all 5 messages in the folder:
- Sample initial request and response
- Sample second request and response
- Sample third request and final response
After the first round, one of the messages is deleted, and another is marked as read. The second round of synchronization returns only the delta (the deletion and update), without returning the other messages that have remained the same.
Sample initial request
In this example, the specified folder is being synchronized for the first time, so the initial sync request doesn't include any state token. This round returns all the messages in that folder.
The first request specifies the following:
- A
$select
parameter to return thesubject
,sender
, andisRead
properties for each message in the response. - The optional request header, odata.maxpagesize, returning 2 messages at a time.
GET https://graph.microsoft.com/v1.0/me/mailfolders/AQMkADNkNAAAgEMAAAA/messages/delta?$select=subject,sender,isRead HTTP/1.1
Prefer: odata.maxpagesize=2
Sample initial response
The response includes two messages and an @odata.nextLink
response header.
The @odata.nextLink
URL indicates there are more messages in the folder to get.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(message)",
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/mailfolders('AQMkADNkNAAAgEMAAAA')/messages/delta?$skiptoken=GwcBoTmPuoTQWfcsAbkYM",
"value": [
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAAARn2vdzPFjSbaPPxzjlzOTAAASsKZz\"",
"subject": "Holiday hours update",
"isRead": false,
"sender": {
"emailAddress": {
"name": "Dana Swope",
"address": "[email protected]"
}
},
"id": "AAMkADNkNAAASq35xAAA="
},
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAAARn2vdzPFjSbaPPxzjlzOTAAAEfYB/\"",
"subject": "Holiday promotion sale",
"isRead": true,
"sender": {
"emailAddress": {
"name": "Samantha Booth",
"address": "[email protected]"
}
},
"id": "AQMkADNkNAAAVRMKAAAAA=="
}
]
}
Sample second request
The second request specifies the @odata.nextLink
URL returned from the previous response. Notice that it no longer has to specify
the same $select
parameter as in the initial request, as the skipToken
in the @odata.nextLink
URL encodes and includes it.
GET https://graph.microsoft.com/v1.0/me/mailfolders/AQMkADNkNAAAgEMAAAA/messages/delta?$skiptoken=GwcBoTmPuoTQWfcsAbkYM HTTP/1.1
Prefer: odata.maxpagesize=2
Sample second response
The second response returns the next 2 messages in the folder and another @odata.nextLink
, indicating there are
more messages to get from the folder.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(message)",
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/mailfolders('AQMkADNkNAAAgEMAAAA')/messages/delta?$skiptoken=GwcBoTmPKILK4jLH7mAd1lLU",
"value": [
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAAARn2vdzPFjSbaPPxzjlqfdAAAEfYB+\"",
"subject": "Microsoft Virtual Academy at Contoso",
"isRead": true,
"sender": {
"emailAddress": {
"name": "Elliot Hyde",
"address": "[email protected]"
}
},
"id": "AQMkADNkNAAAgWkAAAA"
},
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAAARn2vdzPFjSbaPPxzjlzOTAAAEfYB+\"",
"subject": "New or modified user account information",
"isRead": true,
"sender": {
"emailAddress": {
"name": "Randi Welch",
"address": "[email protected]"
}
},
"id": "AQMkADNkNAAAgWJAAAA"
}
]
}
Sample third request
The third request continues to use the latest @odata.nextLink
URL returned from the last sync request.
GET https://graph.microsoft.com/v1.0/me/mailFolders/AQMkADNkNAAAgEMAAAA/messages/delta?$skiptoken=GwcBoTmPKILK4jLH7mAd1lLU HTTP/1.1
Prefer: odata.maxpagesize=2
Sample third and final response
The third response returns the only remaining message in the folder, and a @odata.deltaLink
URL that indicates
synchronization is complete for the time being for this folder. Save and use the @odata.deltaLink
URL to
synchronize the same folder in the next round.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(message)",
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/mailfolders('AQMkADNkNAAAgEMAAAA')/messages/delta?$deltatoken=GwcBoTmPuoGNlgXgF1nyUNMXY",
"value": [
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAAARn2vdzFPjSbaPPxzjlzOTAAAEfYB+\"",
"subject": "Fabric CDN now available",
"isRead": true,
"sender": {
"emailAddress": {
"name": "Jodie Sharp",
"address": "[email protected]"
}
},
"id": "AAMkADk0MGFkODE3LWEAAA="
}
]
}
Synchronize messages in the same folder in the next round
Using the @odata.deltaLink
from the last request in the last round,
you are able to get only those messages that have changed (by being added, deleted, or updated) in that folder since then.
Your first request in the next round will look like the following, assuming you prefer to keep the same maximum page size in the response:
GET https://graph.microsoft.com/v1.0/me/mailfolders/AQMkADNkNAAAgEMAAAA/messages/delta?$deltatoken=GwcBoTmPuoGNlgXgF1nyUNMXY HTTP/1.1
Prefer: odata.maxpagesize=2
The response contains a @odata.deltaLink
. This indicates that all changes in the remote mail folder are now synchronized. One message was deleted and the other message was changed.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(message)",
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/mailfolders('AQMkADNkNAAAgEMAAAA')/messages/delta?$deltatoken=GwcBoTmPuoGNlgXgF1nyUNMXY",
"value": [
{
"@odata.type": "#microsoft.graph.message",
"id": "AAMkADk0MGFkODE3LWE4MmYtNDRhOS0Dh_6qB-pB2Sa2pUum19a6YAAKnLuxoAAA=",
"@removed": {
"reason": "deleted"
}
},
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAAARn2vdzPFjSbaPPxzjlzOTAAASsKZz\"",
"subject": "Holiday hours update",
"isRead": "true",
"sender": {
"emailAddress": {
"name": "Dana Swope",
"address": "[email protected]"
}
},
"id": "AAMkADNkNAAASq35xAAA="
}
]
}
Example 2: Synchronize messages in a folder based on change type
The following example shows getting only messages that are created in a specific folder since the initial sync. The example involves 2 rounds of synchronization of that folder that initially contains 4 messages.
The first round involves a series of 2 requests to synchronize all 4 messages in the folder:
- Sample initial request with specified change type and response
- Sample second request with specified change type and response
After the first round, two more messages are created, one message is deleted, and another is marked as read.
The second round of synchronization returns only the changes in the folder of the created
change type (the two new messages created), without returning the other messages that have remained the same, deleted, or updated since the last sync.
Sample initial request with specified change type
In this example, the specified folder is being synchronized for the first time, so the initial sync request doesn't include any state token. This round returns all the messages in that folder.
The first request specifies the following:
- A
changeType
parameter to return only the created messages in the subsequent delta response. - A
$select
parameter to return thesubject
,sender
, andisRead
properties for each message in the response. - The optional request header, odata.maxpagesize, returning 2 messages at a time.
GET https://graph.microsoft.com/v1.0/me/mailFolders/AAMkAGUwNc4LTMzAAA=/messages/delta?changeType=created&$select=subject,sender,isRead HTTP/1.1
Prefer: odata.maxpagesize=2
Sample initial response with specified change type
The response includes two messages and an @odata.nextLink
response header.
The @odata.nextLink
URL indicates there are more messages in the folder to get.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(message)",
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/mailFolders/AAMkAGUwNc4LTMzAAA=/messages/delta?$skiptoken=P4lmXpjPRrjB6haAQzSkpK89jYTVD2kVtOeXNRnfYzPbCs",
"value": [
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAACQ2fKdhq8oSKEDSVrdi3lRAAId0MBP\"",
"subject": "Inline Attachments Again",
"isRead": true,
"id": "AAMkAGUwNjQ4ZjIxLTQ3Y2YtNDViMi1iZjc4LT2fKdhq8oSKEDSVrdi3lRAAIei5gdAAA=",
"sender": {
"emailAddress": {
"name": "Megan Brown",
"address": "[email protected]"
}
}
},
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAACQ2fKdhq8oSKEDSVrdi3lRAAId0MBR\"",
"subject": "RE: Test Outlook TimeZone",
"isRead": true,
"id": "AAMkAGUwNjQ4ZjIxLTQ3Y2YtNDViMi1iZjc4LTMKdhq8oSKEDSVrdi3lRAAIei5geAAA=",
"sender": {
"emailAddress": {
"name": "Megan Brown",
"address": "[email protected]"
}
}
}
]
}
Sample second request with specified change type
The second request specifies the @odata.nextLink
URL returned from the previous response. Notice that it no longer has to specify
the same $select
or the changeType
parameter as in the initial request, as the skipToken
in the @odata.nextLink
URL encodes and includes it.
GET https://graph.microsoft.com/v1.0/me/mailFolders/AAMkAGUwNc4LTMzAAA=/messages/delta?$skiptoken=P4lmXpjPRrjB6haAQzSkpK89jYTVD2kVtOeXNRnfYzPbCs HTTP/1.1
Prefer: odata.maxpagesize=2
Sample second response with specified change type
The second response returns the next 2 messages in the folder and @odata.deltaLink
URL that indicates
synchronization is complete for the time being for this folder. Save and use the @odata.deltaLink
URL to
synchronize the same folder in the next round.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(message)",
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/mailFolders/AAMkAGUwNc4LTMzAAA=/messages/delta?$deltatoken=P4lmXpjPRrjB6haAQ_37roqIbjXe66KoV7SMlLH--Jgi8",
"value": [
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAACQ2fKdhq8oSKEDSVrdi3lRAAId0MBu\"",
"subject": "Your preview of the new Briefing email",
"isRead": true,
"id": "AAMkAGUwNjQ4ZjIxLTQ3Y2YtNDViMi1iZjc4LTMzNjMwNWM0ZGE2YQBGAAAAAADbrwBIJ",
"sender": {
"emailAddress": {
"name": "Cortana",
"address": "[email protected]"
}
}
},
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAACQ2fKdhq8oSKEDSVrdi3lRAAId0MBw\"",
"subject": "Char Coding HTML",
"isRead": true,
"id": "AAMkAGUwNjQ4ZjIxLTQ3Y2YtNDViMi1iZjc4LTMzNjMwNWM0ZGE2YQBGAAAAAADbrwBA=",
"sender": {
"emailAddress": {
"name": "John Doe",
"address": "[email protected]"
}
}
}
]
}
Synchronize messages in the same folder in the next round based on specified change type
Using the @odata.deltaLink
from the last response in the last round,
you are able to get only those messages that have been added in that folder since then.
Your first request in the next round will look like the following, assuming you prefer to keep the same maximum page size in the response:
GET https://graph.microsoft.com/v1.0/me/mailFolders/AAMkAGUwNc4LTMzAAA=/messages/delta?$deltatoken=P4lmXpjPRrjB6haAQ_37roqIbjXe66KoV7SMlLH--Jgi8 HTTP/1.1
Prefer: odata.maxpagesize=2
The response contains a @odata.deltaLink
. This indicates that all changes in the remote mail folder are now synchronized. Two messages were added since the last sync. The messages updated & deleted since the last sync aren't returned in this delta response.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(message)",
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/mailFolders/AAMkAGUwNc4LTMzAAA=/messages/delta?$skiptoken=EPuhZPRDHo-r3EBfscYE444fuGSBRV1eXex3JZkLzT9fRM",
"value": [
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAACQ2fKdhq8oSKEDSVrdi3lRAAId0MCP\"",
"subject": "Nested Attachment",
"isRead": true,
"id": "AAMkAGUwNjQ4ZjIxLTQ3Y2YtNDViMi1iZjc4LTMzNjMwNWM0ZGE2YQBGAAAAAADbrwBIJ",
"sender": {
"emailAddress": {
"name": "Patti Fernandez",
"address": "[email protected]"
}
}
},
{
"@odata.type": "#microsoft.graph.message",
"@odata.etag": "W/\"CQAAABYAAACQ2fKdhq8oSKEDSVrdi3lRAAId0MCN\"",
"subject": "Attachment Testing",
"isRead": true,
"id": "AAMkAGUwNjQ4ZjIxLTQ3Y2YtNDViMi1iZjc4LTMzNjMwNWM0ZGE2YQBGAAAAAADbrwZA=",
"sender": {
"emailAddress": {
"name": "Patti Fernandez",
"address": "[email protected]"
}
}
}
]
}