Skip to content

Microsoft 365: calendar, Teams and mail

Three providers connect your own Microsoft 365 account. Each one is a delegated sign-in in the browser: you consent, and Me reads only what you can see. One Entra app registration serves all three.

What Provider Permissions you consent to
Calendar com.me.calendar.m365 offline_access User.Read Calendars.ReadWrite
Teams com.me.teams offline_access User.Read Chat.Read Team.ReadBasic.All Channel.ReadBasic.All ChannelMessage.Read.All
Mail com.me.mail.m365 offline_access User.Read Mail.Read Mail.Read.Shared

A fourth provider, com.me.graph.mail, is different: it connects one assistant mailbox with app-only credentials.

Coming soon: people only sign in. The operator registers the Microsoft app once, so you never handle a client id (PR #140). Until it merges, whoever connects Microsoft 365 registers an app (once) and passes its client id.

The person setting this up needs to be allowed to register apps in Entra ID. Everyone else only signs in.

  1. Open portal.azure.com → Microsoft Entra ID → App registrations → New registration. Use any name, such as “Me”. For supported accounts, choose either:
    • Accounts in this organizational directory only: then always pass --tenant <your tenant id or domain> below. The default organizations endpoint refuses a single-tenant app (AADSTS50194).
    • Accounts in any organizational directory: then the default --tenant organizations works.
  2. Under Authentication → Add a platform → Mobile and desktop applications, add the redirect URI http://localhost/v1/oauth/callback. Turn on Allow public client flows. No client secret is needed: Me signs in as a public client with PKCE. Entra ignores the port on localhost redirect URIs, so the local server’s port doesn’t matter.
  3. Under API permissions → Add a permission → Microsoft Graph → Delegated permissions, add the permissions for what you’ll connect:
    • Calendar: User.Read, Calendars.ReadWrite.
    • Teams: Chat.Read, Team.ReadBasic.All, Channel.ReadBasic.All, ChannelMessage.Read.All.
    • Mail: Mail.Read, Mail.Read.Shared.
  4. ChannelMessage.Read.All always needs an admin’s consent, and Teams sign-in asks for it, so an admin grants consent before anyone can connect Teams. The other permissions need it only if your organization blocks user consent. To grant it: Enterprise applications → the app → Permissions → Grant admin consent.
  5. Copy the Application (client) ID from the app’s overview. It is the --client-id below.

The three sign-in providers (calendar, Teams, mail) also take --tenant. The default is organizations, meaning any work or school account, which only works with a multi-tenant app. Give your tenant id or domain to pin it to one directory, which is required for a single-tenant app. The app-only provider takes --tenant-id instead (below).

A hosted server (one that is not on loopback) uses Me:PublicUrl + /v1/oauth/callback as the redirect URI instead. See the operator guide.

Terminal window
me integration add com.me.calendar.m365 --name "Work calendar" --client-id <application id>

Your browser opens Microsoft’s sign-in. Consent, and the terminal confirms the connection. Then:

Terminal window
me calendar agenda --from 2026-10-21T00:00:00-04:00 --to 2026-10-22T00:00:00-04:00
me calendar freebusy --from 2026-10-21T08:00:00-04:00 --to 2026-10-21T18:00:00-04:00
me calendar event create --title "Plumber visit" --start 2026-10-23T09:00:00-04:00 --end 2026-10-23T10:00:00-04:00 --connection conn_…
  • Agenda lists events between two times, earliest first. Recurring events are expanded, a window spans at most 31 days, and --limit takes 1–200 (default 50).
  • Free/busy returns busy intervals only. Events marked free or working elsewhere, and cancelled ones, are left out.
  • Event create puts an event on your own calendar and sends no invitations. Retrying the same operation never makes a second event. Its risk is ExternalReversible, so it runs without an approval.
  • Nothing is copied into Me: every read goes to Microsoft Graph at the moment you ask.

Not built yet: invitations and invite responses, change notifications, presence from the calendar.

You see What to do
“The app is missing a Graph permission (User.Read, Calendars.ReadWrite).” Add the delegated permissions to the app registration, then sign in again
“Microsoft refused the sign-in for this calendar.” Run me integration add com.me.calendar.m365 … again
“A window spans at most 31 days.” Narrow --from / --to
Terminal window
me integration add com.me.teams --name "Teams" --client-id <application id>

After you sign in, the last 7 days of your chats and channel messages arrive in the inbox within a few minutes, one conversation per chat or channel. After that:

  • Push first, polling as the fallback. When the server has a public webhook address (the tunnel), Microsoft Graph notifies Me of new chat messages and of messages in up to 10 followed channels within seconds. Subscriptions are renewed automatically, and a safety sync still runs every 30 minutes. Without a public address, or if Graph refuses the subscription, Teams polls every 2 minutes; the operator can change that with ME_TEAMS_POLL_SECONDS on the plugin (minimum 30). me integration test conn_… and me teams sync say which mode the connection is in (push, or polling: <why>).
  • Your own messages are left out. So are system messages, deleted messages and empty ones.
  • Up to 50 channels are followed.
  • Sign-in needs admin consent first. The sign-in always asks for ChannelMessage.Read.All, so without an admin’s consent you can’t complete me integration add com.me.teams at all. If consent is withdrawn later, only chats sync: channels are skipped, and the sync result says so in channels_note.
  • An @-mention of you is recorded as a chat.mention.received event.
Terminal window
me teams sync --connection conn_… # fetch now instead of waiting
me comms conversation list
me comms message list

Reading only: Me doesn’t send Teams messages yet.

Coming soon:

  • me presence set: busy, do not disturb or away, until a time, with a status message (PR #133).
  • @-mentions that raise attention items and fire triggers (PR #139).
You see What to do
“The app is missing a Teams permission (Chat.Read, Team.ReadBasic.All).” Add the delegated permissions, then sign in again
“Channel messages need ChannelMessage.Read.All, which an admin must consent to for your organization; chats still sync.” Ask an admin to grant consent to the app
“Microsoft refused the sign-in for Teams.” Run me integration add com.me.teams again
Terminal window
me integration add com.me.mail.m365 --name "My mail" --client-id <application id>
me integration add com.me.mail.m365 --name "Team inbox" --client-id <application id> --mailbox team@contoso.com
  • One connection per mailbox. Without --mailbox it is your own. With --mailbox it is a shared or team mailbox that you can open in Outlook. You still sign in as yourself, and the check confirms you can open that inbox.
  • New inbox mail is pushed by Microsoft Graph within seconds when the server has a public webhook address, with a safety sync every 30 minutes. Otherwise the inbox is polled every 2 minutes (ME_MAIL_POLL_SECONDS, minimum 30). me integration test conn_… and me mail sync say which (push, or polling: <why>). The first sync reads the last 7 days. Other folders aren’t read.
  • Mail lands in the inbox as email, one conversation per Outlook thread. Mail you sent yourself is left out.
Terminal window
me mail sync --connection conn_… # fetch now
me comms message list

Reading only: this provider doesn’t send. Replies and sending through your own Microsoft 365 mailbox, other folders, and attachments are not built yet.

You see What to do
“This mailbox can’t be opened with your sign-in (no access, or Mail.Read.Shared lacks consent).” Check that you can open it in Outlook, and that the app has Mail.Read.Shared (an admin may need to consent)
“Microsoft refused the sign-in for this mailbox.” Run me integration add com.me.mail.m365 … again

com.me.graph.mail connects one mailbox, such as assistant@contoso.com, with an app-only Entra app instead of a person’s sign-in. It is meant for a mailbox that belongs to the system. It can send (me mail send, which waits for approval) and receives new inbox mail by push (a Graph subscription, renewed automatically).

Provider side: register an app with application permissions Mail.ReadWrite and Mail.Send, grant admin consent, restrict it to that one mailbox with an application access policy, and create a client secret.

Terminal window
me integration add com.me.graph.mail --name "Assistant" --mailbox assistant@contoso.com --tenant-id <tenant guid> --client-id <app guid>
# client_secret and client_state (any random string of 16+ characters) are asked at the hidden prompt

Limitation on main: push also needs a notification_url in the connection’s secret, and me integration add has no field for it. Receiving therefore fails with “The Graph connection has no notification_url.” The check after adding only proves that Entra accepted the app’s credentials. This has been reported. For your own mailbox, use com.me.mail.m365 instead.