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 |
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 app registration (once)
Section titled “The app registration (once)”The person setting this up needs to be allowed to register apps in Entra ID. Everyone else only signs in.
- 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 defaultorganizationsendpoint refuses a single-tenant app (AADSTS50194). - Accounts in any organizational directory: then the default
--tenant organizationsworks.
- Accounts in this organizational directory only: then always pass
- 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 onlocalhostredirect URIs, so the local server’s port doesn’t matter. - 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.
- Calendar:
ChannelMessage.Read.Allalways 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.- Copy the Application (client) ID from the app’s overview. It is the
--client-idbelow.
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.
Calendar
Section titled “Calendar”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:
me calendar agenda --from 2026-10-21T00:00:00-04:00 --to 2026-10-22T00:00:00-04:00me calendar freebusy --from 2026-10-21T08:00:00-04:00 --to 2026-10-21T18:00:00-04:00me 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
--limittakes 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 |
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_SECONDSon the plugin (minimum 30).me integration test conn_…andme teams syncsay which mode the connection is in (push, orpolling: <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 completeme integration add com.me.teamsat all. If consent is withdrawn later, only chats sync: channels are skipped, and the sync result says so inchannels_note. - An @-mention of you is recorded as a
chat.mention.receivedevent.
me teams sync --connection conn_… # fetch now instead of waitingme comms conversation listme comms message listReading only: Me doesn’t send Teams messages yet.
Coming soon:
| 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 |
Mail: your mailbox or a shared one
Section titled “Mail: your mailbox or a shared one”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
--mailboxit is your own. With--mailboxit 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_…andme mail syncsay which (push, orpolling: <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.
me mail sync --connection conn_… # fetch nowme comms message listReading 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 |
An assistant mailbox (app-only)
Section titled “An assistant mailbox (app-only)”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.
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 promptLimitation on
main: push also needs anotification_urlin the connection’s secret, andme integration addhas 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, usecom.me.mail.m365instead.