Skip to content
covey

Microsoft 365

A runbook for the m365 plugin. The agent gets a Microsoft 365 user of its own and works in it the way a colleague does: it reads and answers its mail, keeps its calendar, and finds, edits and files documents in OneDrive and SharePoint. Everything goes through the Microsoft Graph API with an Entra ID app registration.

Short version: a licensed user for the agent, an app registration with application permissions, an Exchange scope that fences the app into that one mailbox, the system activated in covey, two secrets, one line in ACCESS.md, three egress hosts. Section 2 walks through every click.

How it differs from its neighbours:

m365 email sharepoint
Mail Exchange Online through Graph any IMAP/SMTP server, password login —
Calendar yes — —
Files own OneDrive + every site the app may reach — one library behind a share link

Exchange Online no longer accepts password logins over IMAP, so for a Microsoft 365 mailbox m365 is the way, not email.


1. What the agent can do

  • Mail — list a folder (unread only, or by search words), read a message as text with its attachment list, put an attachment into the sandbox, reply or reply to all (with sandbox files attached), send a new mail, leave a draft for a person to check and send, move a mail into a folder, mark it read.
  • Calendar — read the calendar between two times (series expanded), create an event with attendees or as a Teams meeting, accept / decline / tentatively accept an invitation, cancel an event it organises, look up colleagues' free/busy times.
  • Files — find SharePoint sites, list a site's libraries, list and search a library or its own OneDrive (drive_id me), read a text file, download a file into the sandbox, upload one from it, write a text file, create folders. There is no delete action, on purpose.
  • Links — open_link takes a SharePoint, OneDrive or Teams-files link (a sharing link or the plain address of a file or folder) and answers the drive and path the file actions take. "Please add to the minutes here: " is a complete assignment.
  • Office documents — the plugin does not parse them. The agent downloads a .docx, .xlsx or .pptx, edits it in the sandbox with python3 (docx, openpyxl, pptx are in the base workplace) and uploads it onto the same path.

Sending mail and inviting people reaches real people. The guard-rail subjects are one per action (m365:send_mail, m365:create_event, …), so an organisation that wants a human to look first puts require_approval on exactly those and leaves reading free.


2. Setup

Two roles' work, in this order: first a Microsoft 365 admin (user, app, Exchange scope, SharePoint), then a covey admin (activate, secrets, egress, hire). Plan half an hour. Keep a scratch note open for five values you collect on the way:

Value From Used in
Agent address 2.1 m365_url, Exchange scope
Directory (tenant) ID 2.2 m365_token
Application (client) ID 2.2 m365_token, Exchange, SharePoint
Client secret value 2.2 m365_token
Enterprise application object ID 2.3 New-ServicePrincipal

2.1 A user for the agent (Microsoft 365 admin centre)

  1. admin.microsoft.com → Users → Active users → Add a user.
  2. Name it after its job (e.g. Office Agent), username e.g. office@contoso.com. Assign a licence that includes Exchange Online and OneDrive (Microsoft 365 Business Basic is enough).
  3. The password is never used by covey — set a long random one and do not hand it out. The agent signs in as the app, not as this user.
  4. Sign in once at outlook.office.com as this user (or have the mailbox opened by an admin) and create the folders your playbook names, e.g. Erledigt. The plugin does not create mail folders.

A shared mailbox works for mail and calendar but has no OneDrive; use a licensed user when the agent should have files of its own.

2.2 The app registration (Entra ID)

  1. entra.microsoft.com → Applications → App registrations → New registration. Name: e.g. covey office agent. Supported account types: this organisational directory only. Redirect URI: leave empty. Register.

  2. On the overview page copy Application (client) ID and Directory (tenant) ID.

  3. API permissions → Add a permission → Microsoft Graph → Application permissions (not Delegated). Add:

    Permission For
    Mail.ReadWrite, Mail.Send the mailbox
    Calendars.ReadWrite the calendar
    Files.ReadWrite.All OneDrive and document libraries
    Sites.ReadWrite.All or Sites.Selected finding sites — see 2.4

    Then Grant admin consent for . Every row must show a green Granted status; a permission without consent does nothing.

  4. Certificates & secrets → Client secrets → New client secret. Description covey, expiry as your policy allows (at most 24 months). Copy the Value column right away — it is shown once. The Secret ID next to it is not the secret; using it gives invalid_client (AADSTS7000215).

  5. Put the expiry date in a calendar. When it passes, every action fails with invalid_client; renew under the same page and replace m365_token in covey.

2.3 Fence the app into the agent's mailbox (Exchange Online PowerShell)

Application permissions hold tenant-wide: after 2.2 the app could read and send from every mailbox in the organisation. Exchange's RBAC for Applications narrows mail and calendar to the one user. Do not skip this step.

  1. Find the Enterprise application object ID: Entra ID → Applications → Enterprise applications → search the app's name → Object ID. This is a different ID from the one on the app registration page; the registration's object ID makes New-ServicePrincipal fail or bind the wrong object.

  2. Open PowerShell (Windows PowerShell 5.1 or PowerShell 7) as an Exchange administrator:

    Install-Module ExchangeOnlineManagement -Scope CurrentUser   # once
    Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
    
  3. Create the scope, register the app with Exchange, and give it the three roles inside the scope:

    New-ManagementScope -Name "covey-office" `
      -RecipientRestrictionFilter "PrimarySmtpAddress -eq 'office@contoso.com'"
    
    New-ServicePrincipal -AppId <client-id> -ObjectId <enterprise-app-object-id> `
      -DisplayName "covey office agent"
    
    New-ManagementRoleAssignment -App <client-id> -Role "Application Mail.ReadWrite" `
      -CustomResourceScope "covey-office"
    New-ManagementRoleAssignment -App <client-id> -Role "Application Mail.Send" `
      -CustomResourceScope "covey-office"
    New-ManagementRoleAssignment -App <client-id> -Role "Application Calendars.ReadWrite" `
      -CustomResourceScope "covey-office"
    
  4. Back in Entra ID → the app registration → API permissions: remove Mail.ReadWrite, Mail.Send and Calendars.ReadWrite there. Grants in Entra ID and in Exchange add up — as long as the tenant-wide ones stay, the scope narrows nothing.

  5. Check it:

    Test-ServicePrincipalAuthorization -Identity <client-id> -Resource office@contoso.com
    Test-ServicePrincipalAuthorization -Identity <client-id> -Resource someone.else@contoso.com
    

    The first answers InScope : True for the three roles, the second False. Changes can take up to two hours to apply in Exchange; a 403 right after this step is usually that delay.

The plugin itself only ever addresses the mailbox in m365_url. That is a convention of the code, not a boundary: the boundary is this step.

2.4 SharePoint: which sites the agent may reach

Decide first where the agent will work, and especially where the links it is sent will point. The agent opens a link as the app, not as its user: sharing a file with office@contoso.com does not let the app in.

Choice Effect
Sites.ReadWrite.All every site and every link in the tenant opens. Simplest; the app reaches the whole SharePoint estate
Sites.Selected only sites granted to the app one by one. A link into any other site ends in a 403 that says so

With Sites.Selected, grant each site. Either way below works; you need a SharePoint administrator.

PnP PowerShell (shortest):

Install-Module PnP.PowerShell -Scope CurrentUser   # once
Connect-PnPOnline -Url https://contoso.sharepoint.com/sites/Team -Interactive -ClientId <your PnP admin app>
Grant-PnPAzureADAppSitePermission -AppId <client-id> -DisplayName "covey office agent" `
  -Site https://contoso.sharepoint.com/sites/Team -Permissions Write

Graph Explorer (aka.ms/ge, signed in as an admin who has consented to Sites.FullControl.All there):

  1. Site ID: GET https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/Team → copy id (three parts, comma-separated).

  2. Grant:

    POST https://graph.microsoft.com/v1.0/sites/{site-id}/permissions
    { "roles": ["write"],
      "grantedToIdentities": [{ "application": { "id": "<client-id>", "displayName": "covey office agent" } }] }
    

Repeat per site. list_sites in covey then shows exactly those sites. The agent's own OneDrive needs no grant — Files.ReadWrite.All covers it.

2.5 In covey

  1. Activate the system for the organisation: Target systems → Microsoft 365 → Activate. A compiled plugin is off per organisation until then, and an agent's ACCESS.md line alone does not switch it on.

  2. Store the secrets at the agent: Agent → Settings → Secrets:

    m365_url   = office@contoso.com
    m365_token = <tenant-id>:<client-id>:<client-secret-value>
    

    For national clouds m365_url takes the endpoints after the address: office@contoso.us graph=https://graph.microsoft.us login=https://login.microsoftonline.us.

  3. Access — in the agent's ACCESS.md:

    - system: m365   scope: read,write
    
  4. Egress — Egress → Templates: adopt Microsoft 365 from the catalogue, then tick it on the agent page, tab Egress. Or add the hosts as the agent's own:

    Host Why
    graph.microsoft.com the API
    login.microsoftonline.com the token
    *.sharepoint.com file downloads: /content redirects to the tenant's SharePoint host

2.6 Testing

  1. Connection test (Target systems → Microsoft 365 → Test connection): it reads the inbox and answers mailbox office@contoso.com reachable: 3 unread of 41 in the inbox.
  2. Hire the agent, then send its address a short assignment from a colleague's mailbox. Within the heartbeat interval a run starts; its recording shows list_mail, get_mail, and the answer it sent.
  3. Send it a SharePoint link in a second mail ("please add a line to this document: ") to check open_link and the site grants.

2.7 When something fails

Symptom Cause Fix
entra id token: HTTP 401 … invalid_client / AADSTS7000215 wrong secret (the Secret ID was copied) or the secret expired new client secret, copy its Value, replace m365_token (2.2)
AADSTS700016 client ID does not exist in this tenant check tenant-id:client-id order and values
HTTP 403 … ErrorAccessDenied on mail or calendar role assignment missing, scope does not cover the address, or the change has not applied yet run Test-ServicePrincipalAuthorization (2.3); wait up to two hours after a change
HTTP 403 on every mailbox, also the agent's admin consent missing, or the Graph mail permissions were removed before the Exchange roles existed 2.2 step 3, 2.3 step 3
HTTP 403 from open_link or list_drives Sites.Selected without a grant for that site grant the site (2.4)
list_sites empty Sites.Selected with no site granted yet grant at least one site (2.4)
listing works, download_file ends in Forbidden egress blocks the SharePoint download host *.sharepoint.com (2.5 step 4); the egress log shows the refused CONNECT
m365 is not activated, or the agent's prompt lists no Microsoft 365 actions system not activated for the organisation 2.5 step 1
no m365_token stored for this agent secrets stored, but not at this agent 2.5 step 2

3. Intake

There is no webhook: Graph change notifications need a publicly validated subscription that has to be renewed every few days. The intake is a heartbeat with a pre-check that costs one call:

- alle: 10m nur-wenn: m365 titel: Look after the mailbox
  aufgabe: Read the unread mail (list_mail unread:true) and work on it by the playbook.

nur-wenn: m365 asks for the unread mail in the inbox (nur-wenn: m365:<folder> for another folder). It reports one item per message (mail:<id>@<last modified>), so it fires when a mail arrives or changes and stays quiet while the inbox stands still, and a workflow on only_if: m365 runs one case per mail. The agent's own mark_read, move_mail and reply_mail are known to move the signature and do not wake it again.

Ids are Graph's immutable ids: a mail keeps its id when it is moved, so a note or a workflow case that names it stays true.


4. Limits

What Limit
Attachments on an outgoing mail 3 MB together (Graph refuses a request over 4 MB) — a larger file goes to OneDrive with upload_file, and the mail carries its link
An attachment fetched into the sandbox COVEY_M365_ATTACHMENT_MAX_MB, default 25
An upload COVEY_M365_UPLOAD_MAX_MB, default and maximum 250 (simple upload)
read_file text up to 1 MB; anything else through download_file
list_mail 50 per call