Connectors
Outlook · Calendar · OneDrive · Teams · SharePoint
provider="microsoft"

Connect Microsoft: Outlook, Calendar and OneDrive

Register an app in Microsoft Entra ID so your Python automations can work with each person's own Outlook mail and calendar. A single-tenant setup for your company, with the errors you're likely to hit.

To let a script read someone's Outlook inbox, file their mail or check their calendar, it needs their permission through Microsoft's sign-in. Before anyone can sign in, you register an app in Microsoft Entra ID (formerly Azure Active Directory). The registration says who you are, what you're asking for, and where Microsoft sends people back to.

This guide sets up a single-tenant app: one only people in your company can use. That's the Microsoft version of a Google "Internal" app, with no review by Microsoft, no user cap, and no sign-ins that expire every week. It takes about fifteen minutes.

Before you start

  • A Microsoft 365 work account in your company's tenant (the company's Microsoft directory).
  • Permission to register apps. Most companies allow everyone to by default. If New registration is greyed out, ask an admin for the Application Developer role.
  • An admin for one step. Granting consent for the whole company (step 5) needs a Global Administrator or Cloud Application Administrator. If that isn't you, line them up now. It's one click for them.

1. Register the app

  1. Go to entra.microsoft.com and open Entra ID → App registrations → New registration.
  2. Name: what people will see when they sign in, such as "Ops automations".
  3. Supported account types: Accounts in this organizational directory only (Single tenant).
  4. Redirect URI: platform Web, value https://callables.app/connections/callback.
  5. Click Register.

The Overview page has two values you'll need:

  • Application (client) ID: your client ID.
  • Directory (tenant) ID: which company the app belongs to.

2. Add the local redirect

If you'll test on your own machine with call connect, open Authentication, add http://localhost:8765/callback under the Web platform, and save. Microsoft allows plain http only for localhost.

3. Create a client secret

Open Certificates & secrets → Client secrets → New client secret, add a description and pick an expiry (24 months at most).

Copy the Value column, not the Secret ID, and copy it now: Microsoft hides it once you leave the page. Pasting the Secret ID instead is the most common setup mistake.

Put the expiry date in your calendar. When the secret expires, nobody's connection can refresh. The fix is quick: create a new secret and update your project secret. Nobody has to reconnect.

4. Add permissions

Open API permissions → Add a permission → Microsoft Graph → Delegated permissions. Delegated means the app acts as the person who signed in, which is what you want for scripts that work on each person's own mailbox.

You want toPermission
Read mailMail.Read
Read, move, archive and delete mailMail.ReadWrite
Send mail, such as unsubscribe requestsMail.Send
Read calendarsCalendars.Read

Leave User.Read, which is there already: Callables uses it to show which account someone connected. Callables also asks for offline_access itself, which lets it refresh tokens.

5. Grant admin consent

Many companies don't let people approve apps themselves. If yours doesn't, everyone who tries to connect sees Need admin approval and gets stuck.

So have an admin, a Global Administrator or Cloud Application Administrator, open API permissions and click Grant admin consent for (your company). That approves the app's permissions for everyone in the company at once. People connect without a prompt, and the status column turns green.

Do this again whenever you add a permission.

6. Declare the connection

from callables import Connection, Group, Secret

outlook = Connection(
    "outlook",
    provider="microsoft",
    scopes=["Mail.ReadWrite", "Mail.Send"],
    client_id=Secret("MS_CLIENT_ID"),
    client_secret=Secret("MS_CLIENT_SECRET"),
    values={"tenant": Secret("MS_TENANT_ID")},
)
mail = Group("outlook", title="Outlook", connections=[outlook])


@mail.deploy(title="Unread count")
def unread_count() -> str:
    """How many unread emails are in your inbox."""
    import requests

    resp = requests.get("https://graph.microsoft.com/v1.0/me/mailFolders/inbox",
                        headers={"Authorization": f"Bearer {outlook.token}"})
    resp.raise_for_status()
    return f"{resp.json()['unreadItemCount']} unread"

Nothing about your app is in the code. Each Secret("...") names a project secret on Callables, and an environment variable locally:

MS_CLIENT_ID=your Application (client) ID
MS_CLIENT_SECRET=the secret's Value
MS_TENANT_ID=your Directory (tenant) ID

Set them as project secrets in Callables, and put the same lines in .env to test locally. The names are yours to choose. Leave out client_id and client_secret and the connection reads OUTLOOK_CLIENT_ID and OUTLOOK_CLIENT_SECRET, named after the connection.

values={"tenant": ...} fills the {tenant} in Microsoft's sign-in URLs, which points sign-in at your company's directory. A single-tenant app needs it. Without it, sign-in goes through Microsoft's shared common endpoint, which only multi-tenant apps can use.

Because everything comes from secrets, the same project works for any company: each one sets its own three values.

7. Test, then share

call connect           # pick outlook, sign in with your work account
call outlook unread_count

Then push. Teammates see Connect Microsoft on the form the first time, and with admin consent granted they're straight through.

When something goes wrong

What you seeFix
AADSTS50194: the app isn't configured as multi-tenantSet MS_TENANT_ID to your Directory (tenant) ID, and pass it in values
AADSTS50011: redirect URI mismatchAdd the exact URI under Authentication, platform Web
AADSTS7000215: invalid client secretYou copied the Secret ID instead of the Value, or the secret expired
Need admin approval, or AADSTS65001An admin needs to grant consent (step 5)
AADSTS700016: application not found in the directoryYou signed in with an account from another company, or the tenant ID is wrong
MailboxNotEnabledForRESTAPIThat account has no Exchange Online mailbox, usually a missing license

Good to know

  • Disconnect deletes the person's tokens from Callables. Microsoft doesn't offer a simple way to revoke them, so to remove the app's access completely, people can remove it at myapps.microsoft.com.
  • Graph limits each mailbox to about 10,000 requests per 10 minutes per app. That's plenty for normal use, but scripts that scan thousands of messages should pace themselves.

Next

See how to share a script that runs as each user, or try the Outlook examples in the cookbook. The full reference is in the connections docs.

Working examples in the cookbook
The outlook/ folder: a complete project you can fork, test with call connect and deploy.

Run your scripts on each person's own account

Declare a connection, push, and share. Everyone connects once and every run uses their account.

Read the connections docs

Other connectors