Set users out of office settings with Microsoft Graph

Context

Last week, a close friend of mine asked me a question: “Hey Yves, I would like to set some out of office settings like mailbox and calendar to the users after leave request approvals in my custom web application, how can I do that?”. My answer was quick: “Microsoft Graph is life!”. Microsoft Graph is the gateway to data in Microsoft 365 and it’s a really powerfull platform to interconnect modern cloud and/or on-premises applications. In this article, I’ll show you the key steps to create a JSON batch request to set users out of office settings and use it in all your applications, let’s go!

Microsoft Graph and Microsoft 365 Platform architecture.

JSON batching

With Microsoft Graph JSON batching, you can combine multiple requests in one with dependencies, this is a very good point to optimize network traffic when you have lots of different data types to retrieve from the API to your apps. In my case, I’ll create a batch request with two dependant requests inside it:

  1. Set user’s auto reply with custom message during leave request.
  2. Create an out of office event in user’s calendar for the leave request period.

Azure AD app registration

First, I need to register the app in Azure Active Directory (Azure AD) and add the following Application permissions (principle of least privilege):

  • MailboxSettings.ReadWrite
  • Calendars.ReadWrite
For Application permissions, we need to grant admin consent for the tenant.

Don’t forget to generate a client secret (and copy it!) for the registered app in Azure AD:

Construct the batch request

Then I can construct the batch request (headers + body) and POST it to the Microsoft Graph batch endpoint which is:

https://graph.microsoft.com/v1.0/$batch

In this article, I’m using a client credentials authorization flow to keep it simple but you can use other flows like On-Behalf-Of with a backend web API used by your custom leave request client application or by implicit grant for example.

I’m using Postman to create, manage and debug all my Microsoft Graph requests before using them in my applications or solutions. Here is the request’s body I created for this batch request:

{
    "requests": [
        {
            "id": "100",
            "url": "/users/{{UserId}}/mailboxSettings",
            "method": "PATCH",
            "headers": {
                "Content-Type": "{{JSON}}"
            },
            "body": {
                "automaticRepliesSetting": {
                    "status": "scheduled",
                    "externalAudience": "all",
                    "internalReplyMessage": "<html>\n<body>\n<p>I'm at our company's worldwide reunion and will respond to your message as soon as I return.<br>\n</p></body>\n</html>\n",
                    "externalReplyMessage": "<html>\n<body>\n<p>I'm at the Contoso worldwide reunion and will respond to your message as soon as I return.<br>\n</p></body>\n</html>\n",
                    "scheduledStartDateTime": {
                        "dateTime": "2020-12-21T00:00:00",
                        "timeZone": "{{TimeZone}}"
                    },
                    "scheduledEndDateTime": {
                        "dateTime": "2021-01-03T23:59:00",
                        "timeZone": "{{TimeZone}}"
                    }
                }
            }
        },
        {
            "id": "200",
            "dependsOn": [
                "100"
            ],
            "url": "/users/{{UserId}}/calendar/events",
            "method": "POST",
            "headers": {
                "Content-Type": "{{JSON}}"
            },
            "body": {
                "subject": "OOF",
                "body": {
                    "contentType": "HTML",
                    "content": "Out of office."
                },
                "start": {
                    "dateTime": "2020-12-21T00:00:00",
                    "timeZone": "{{TimeZone}}"
                },
                "end": {
                    "dateTime": "2021-01-03T23:59:00",
                    "timeZone": "{{TimeZone}}"
                },
                "isReminderOn": false
            }
        }
    ]
}

Don’t forget to add the mandatory headers to the batch request:

Accept: {{JSON}}
Content-Type: {{JSON}}
Authorization: Bearer {{AppAccessToken}}

Replace the following items (that’s my environnement variables in Postman which is a good practice to use with Microsoft Graph developments):

  • {{UserId}} : user ID or user principal name (example: yves.habersaat@contoso.com).
  • {{JSON}} : application/json (JSON MIME type).
  • {{TimeZone}} : Microsoft Graph time zone (default is UTC).
  • {{AppAccessToken}} : Azure AD access token.
  • Start/end datetimes for auto reply and calendar event.

You can have a look at Microsoft Graph complete list of time zones here:
https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0

Note that the attribute dependsOn si used to create a dependency between requests, if one of the requests specified in this attribute fails, the status code of the single request will be HTTP 424 (Failed Dependency). So in my case, if the request to set auto reply fails, the event calendar isn’t created!

Test and check the batch request

Now if you send the request, you’ll see the response body with two different HTTP statuses that confirm the batch request success:

  • 200 (OK) for the auto reply setting.
  • 201 (Created) for the event calendar created.
{
    "responses": [
        {
            "id": "100",
            "status": 200,
            "headers": {
                "Cache-Control": "private",
                "Content-Type": "application/json; odata.metadata=minimal; odata.streaming=true; IEEE754Compatible=false; charset=utf-8"
            },
            "body": {
                "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('92a498a3-44d1-4fc5-b249-3860c30c4d45')/mailboxSettings",
                "automaticRepliesSetting": {
                    "status": "scheduled",
                    "externalAudience": "all",
                    "internalReplyMessage": "<html>\n<body>\n<p>I'm at our company's worldwide reunion and will respond to your message as soon as I return.<br>\n</p></body>\n</html>\n",
                    "externalReplyMessage": "<html>\n<body>\n<p>I'm at the Contoso worldwide reunion and will respond to your message as soon as I return.<br>\n</p></body>\n</html>\n",
                    "scheduledStartDateTime": {
                        "dateTime": "2020-12-20T23:00:00.0000000",
                        "timeZone": "UTC"
                    },
                    "scheduledEndDateTime": {
                        "dateTime": "2021-01-03T22:59:00.0000000",
                        "timeZone": "UTC"
                    }
                }
            }
        },
        {
            "id": "200",
            "status": 201,
            "headers": {
                "Location": "https://graph.microsoft.com/v1.0/users('92a498a3-44d1-4fc5-b249-3860c30c4d45')/events('AAMkAGJiMTIwYmYwLTZkNDAtNDhlNC1hZTFkLTg4ZGMxYjUxN2RiZgBGAAAAAAB3vU6XLfzDQLYggGJgMIHpBwA3g3Mv9vomRL_JUKDvDC7ZAAAAAAENAAA3g3Mv9vomRL_JUKDvDC7ZAAAD1qavAAA=')",
                "Cache-Control": "private",
                "Content-Type": "application/json; odata.metadata=minimal; odata.streaming=true; IEEE754Compatible=false; charset=utf-8"
            },
            "body": {
                "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('92a498a3-44d1-4fc5-b249-3860c30c4d45')/calendar/events/$entity",
                "@odata.etag": "W/\"N4NzL/b6JkS/iVCg7wwu2QAAA9RoDg==\"",
                "id": "AAMkAGJiMTIwYmYwLTZkNDAtNDhlNC1hZTFkLTg4ZGMxYjUxN2RiZgBGAAAAAAB3vU6XLfzDQLYggGJgMIHpBwA3g3Mv9vomRL_JUKDvDC7ZAAAAAAENAAA3g3Mv9vomRL_JUKDvDC7ZAAAD1qavAAA=",
                "createdDateTime": "2020-11-28T12:55:33.3252923Z",
                "lastModifiedDateTime": "2020-11-28T12:55:33.3611352Z",
                "changeKey": "N4NzL/b6JkS/iVCg7wwu2QAAA9RoDg==",
                "categories": [],
                "transactionId": null,
                "originalStartTimeZone": "Europe/Paris",
                "originalEndTimeZone": "Europe/Paris",
                "iCalUId": "040000008200E00074C5B7101A82E008000000001D1A30C285C5D601000000000000000010000000A8187A83ACD9574DAC714087C2E686AD",
                "reminderMinutesBeforeStart": 15,
                "isReminderOn": false,
                "hasAttachments": false,
                "subject": "OOF",
                "bodyPreview": "Out of office.",
                "importance": "normal",
                "sensitivity": "normal",
                "isAllDay": false,
                "isCancelled": false,
                "isOrganizer": true,
                "responseRequested": true,
                "seriesMasterId": null,
                "showAs": "busy",
                "type": "singleInstance",
                "webLink": "https://outlook.office365.com/owa/?itemid=AAMkAGJiMTIwYmYwLTZkNDAtNDhlNC1hZTFkLTg4ZGMxYjUxN2RiZgBGAAAAAAB3vU6XLfzDQLYggGJgMIHpBwA3g3Mv9vomRL%2BJUKDvDC7ZAAAAAAENAAA3g3Mv9vomRL%2BJUKDvDC7ZAAAD1qavAAA%3D&exvsurl=1&path=/calendar/item",
                "onlineMeetingUrl": null,
                "isOnlineMeeting": false,
                "onlineMeetingProvider": "unknown",
                "allowNewTimeProposals": true,
                "isDraft": false,
                "hideAttendees": false,
                "responseStatus": {
                    "response": "organizer",
                    "time": "0001-01-01T00:00:00Z"
                },
                "body": {
                    "contentType": "html",
                    "content": "<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n<meta content=\"text/html; charset=us-ascii\">\r\n</head>\r\n<body>\r\nOut of office.\r\n</body>\r\n</html>\r\n"
                },
                "start": {
                    "dateTime": "2020-12-21T00:00:00.0000000",
                    "timeZone": "Europe/Paris"
                },
                "end": {
                    "dateTime": "2021-01-03T23:59:00.0000000",
                    "timeZone": "Europe/Paris"
                },
                "location": {
                    "displayName": "",
                    "locationType": "default",
                    "uniqueIdType": "unknown",
                    "address": {},
                    "coordinates": {}
                },
                "locations": [],
                "recurrence": null,
                "attendees": [],
                "organizer": {
                    "emailAddress": {
                        "name": "Yves Habersaat",
                        "address": "yh@yhabersaat.onmicrosoft.com"
                    }
                },
                "onlineMeeting": null
            }
        }
    ]
}

We can easily check inside the user account if everything was setted correctly as expected:

Outlook mailbox auto reply.
Outlook calendar event.

Common use cases

So what’s next now? You can call this batch request in your line of business apps you just have to replace user ID/user principal name and start/end datetimes dynamically and that’s it!

The common use cases are for example:

  • Leave request Power Apps and Power Automate.
  • Dynamics 365 Human Resources (leave and absence) or Finance and Operations (projects timesheets) with alert rules, business events or Dataverse trigger for Power Automate.
  • Microsoft Lists and Power Automate.
  • Custom line of business SharePoint Online/Teams app with Microsoft Graph client.
  • Third-party or legacy line of business apps.

My friend is using a custom web app written long time ago in PHP/JavaScript and from now he’s using Microsoft Graph JSON batching to set users out of office settings automatically after leave request approvals, happy ending.

Happy coding everyone!

Resources

https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

https://docs.microsoft.com/en-us/graph/overview

https://docs.microsoft.com/en-us/graph/json-batching

https://docs.microsoft.com/en-us/graph/known-issues#json-batching

3 thoughts on “Set users out of office settings with Microsoft Graph

  1. Hello Yves,

    We want to achieve exactly what you do above. But our system administrator dont want to grant admin consent for the tenant. Is there any other way to do this? or How can I persuade him?

    Liked by 1 person

    1. Hi Mustafa,

      If the Application permissions are too permissive for your system admin, you can try to use the Delegated permissions and your app’s access is limited by the privileges of the signed-in user which is better.

      Another way would be to use a manual process from the user side (with a calendar invite attached to an email for example) but in this case, we are losing some automation which is the added value of the solution.

      I hope it helps!

      Like

Leave a comment