BakedBrie docs

Recipe: connect Microsoft 365, Outlook drafts and Teams messages

This recipe covers three things that sign in with your own Microsoft Entra app:

  • the microsoft_365 connection preset, so a board can read recent Outlook mail (no bodies), read and write Excel cells, list OneDrive files and read your Outlook calendar while it works;
  • the outlook_draft destination, which puts an approved email in your Outlook Drafts and never sends it;
  • the teams_channel_message destination, which posts the approved text to one Teams channel after a person approves the card.

Read Connect a service that signs in with OAuth first for how OAuth connections, limits, requests and receipts work.

What BakedBrie never does with Microsoft:

  • It never sends email. The connection has no mail write at all: BakedBrie refuses Outlook send, reply, forward, draft and every other mail write on every connection. The Outlook draft destination asks Microsoft only for Mail.ReadWrite, never Mail.Send, so its sign-in cannot send mail even by mistake.
  • It never posts to Teams or chats, or sends invites, through a connection. Every Teams post, chat message and Outlook calendar event write is refused. The only way a message reaches Teams is the Teams channel message destination, after a person approves that exact text.
  • Nothing wider than you tick. The connection asks for offline_access plus exactly the permissions of the operations you choose, computed for you. If Microsoft grants more than BakedBrie asked for (other than User.Read, openid, profile and email, which Microsoft may list because the app was granted them before), the sign-in is refused and removed (OAUTH_SCOPE_WIDER_THAN_REQUESTED).

Before you run it: register one app

  1. In the Microsoft Entra admin center, open App registrations, New registration. Choose Single tenant only (Microsoft recommends it for most apps). BakedBrie signs in at your own tenant's endpoint, never at common.
  2. Under Authentication, add a Web redirect URI, character for character (the same for every BakedBrie connection and destination):
   https://app.bakedbrie.com/settings/destinations/oauth/callback
  1. Under Certificates & secrets, make a New client secret. It lasts at most 24 months (Microsoft suggests less than 12). Copy the value at once: Microsoft never shows it again. Put it in your own shell, for example export MICROSOFT_CLIENT_SECRET=..., and never paste it into the chat. Type its expiry date as "Secret expires on" on the connection (client_secret_expires_on), and BakedBrie reminds you 14 days before it ends.
    • Microsoft now recommends a certificate instead of a client secret for production apps, and a tenant's app policy may block secrets. BakedBrie supports only a client secret for now.
  2. Under API permissions, add Microsoft Graph, Delegated permissions, only for what you will use (table below), plus offline_access.
  3. Copy the Directory (tenant) ID and the Application (client) ID from the app's Overview page. The tenant id is typed before sign-in (below). Keep it out of shared chats; it is not a secret, but it names your organization.
  4. Consent. By default, people can consent to permissions that do not need an admin. If your tenant turned user consent off, limits it to permissions an admin classified as low impact (and these are not in that list), or the app requires user assignment, an admin clicks Grant admin consent once on the API permissions page. None of the permissions below needs admin consent in Microsoft's permissions reference.
  5. whoami must show connections and connections_oauth on (and destinations, custom_oauth and api_workflow for the two destinations).

The connection: operations and permissions

Tick only what the board needs. The create form ticks the three reads marked "default"; writes are never ticked for you. offline_access is always added, so BakedBrie gets a refresh token.

OperationWhat it doesReads or writesPermission
outlook_list_mailUp to 25 messages received since a date, newest first: subject, sender name and address, date, read and attachment flags. No message bodies.readMail.ReadBasic
excel_read_rangeUp to 50 rows of a range in one workbook on OneDrive or SharePoint, first 8 columnsreadFiles.ReadWrite
excel_write_rangeWrites one value into one cell; never a formulawriteFiles.ReadWrite
onedrive_list (default)Up to 50 files and folders at the top of your OneDrivereadFiles.Read
onedrive_get (default)One file's name, size and date by its item idreadFiles.Read
calendar_list_events (default)Up to 50 events between two dates (UTC): subject, times, canceled. No bodies or attendeesreadCalendars.ReadBasic

Notes:

  • **Excel needs Files.ReadWrite even to read.** Microsoft lists no lower permission for reading a range, so excel_read_range is not ticked by default. The workbook item id, the worksheet name (no spaces) and a range like A1:H50 are the inputs.
  • Excel writes go to one cell, like B7. BakedBrie refuses a value that starts with =, +, - or @, so it can never become a formula.
  • Item ids are letters, digits, - and _, as OneDrive for Business uses. A single-tenant app does not sign in personal Microsoft accounts.
  • Times are UTC: date inputs become T00:00:00Z, and Microsoft answers times without an offset in UTC.

What the board sees, and the taint rule

Answers are provider data, shown to agents only as untrusted facts. Anything a person typed is free_text: mail subjects and sender names, Excel cells, file names and event subjects. A run that read a non-empty free_text field can only read in the same round (CONNECTION_WRITE_TAINTED), so "read the sheet, then write a cell" in one agent round needs a person. A sender address is kept as an email value, but it can never address a draft: draft recipients come only from a customer or contact read (QuickBooks, Xero or HubSpot).

excel_write_range runs within limits a person sets in the web app, and BakedBrie sends it at most once. Microsoft documents no idempotency key for it, so if BakedBrie cannot tell whether Microsoft accepted a write, it never sends it again by itself: check the workbook before a person chooses Send again.

The prompt: the connection

In BakedBrie, connect Microsoft 365 to the board "{{BOARD_NAME}}". Use only the BakedBrie MCP tools. Start with whoami and stop if connections or connections_oauth is off. First read /docs/recipes/connections-oauth and /docs/recipes/microsoft-365 with read_docs.

My app's client id is {{CLIENT_ID}}, its Directory (tenant) ID is {{TENANT_ID}}, and its client secret is in my environment variable MICROSOFT_CLIENT_SECRET, expiring on {{YYYY-MM-DD}}. The operations I want: {{OPERATIONS, for example onedrive_list, calendar_list_events}}.

1. Call manage_connection with action presets, preset microsoft_365 and operations [my operations]. Show me the computed scopes.
2. Call prepare_secret with kind microsoft, purpose connection and env_var MICROSOFT_CLIENT_SECRET. Run the command it returns in my shell exactly as given, then use the drop_id. Never print the secret.
3. Create it: manage_connection action create, name "Microsoft 365", preset microsoft_365, operations [my operations], client_id {{CLIENT_ID}}, client_secret_expires_on {{YYYY-MM-DD}}, credential {"drop_id": "<the drop id>"}, and a new request_id.
4. Call manage_connection action connect_oauth with typed_values {"tenant": "{{TENANT_ID}}"}. Give me the link it returns: only a person can finish signing in, and the link works once, for 10 minutes. I open it in the browser where I am signed in to BakedBrie, approve at Microsoft, and click Finish connecting. Then call oauth_status until the grant is connected, or tell me its error code.
5. Attach it: manage_board_connection action attach, handle microsoft, the operations, the agents and stages that may use it, and when_to_use.
6. Stop and tell me: "Only a person can let a board use a connection. Open the limits link and set limits." Suggest $0.00 per call, reads 10 per card and 200 per month, writes 1 per card and 20 per month.

The tenant id must be the uuid from the Overview page. BakedBrie lowercases it and puts it only into the Microsoft sign-in path, https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize and .../token; it never changes the host. common, organizations, a domain name or anything else is refused before any link is made (OAUTH_TYPED_VALUE_INVALID). Changing the tenant means signing in again.

Outlook drafts

The outlook_draft destination works like the Gmail draft in Draft invoice reminders in Gmail: after a person approves the card, BakedBrie makes exactly one draft in the signed-in person's Outlook Drafts with POST https://graph.microsoft.com/v1.0/me/messages. Nothing is ever sent.

  • Its sign-in asks for exactly offline_access Mail.ReadWrite. Mail.ReadWrite does not include permission to send mail, and BakedBrie never asks for Mail.Send, so the sign-in itself cannot send.
  • The card's result needs a bakedbrie-email block. The draft has exactly one recipient, no cc, bcc or reply-to, and the recipient must be a customer or contact email that BakedBrie read on this card (a QuickBooks customer, a Xero contact or a HubSpot contact). Anything else is refused (EMAIL_RECIPIENT_UNVERIFIED).
  • Create it with manage_destination action create, webhook {"preset": "outlook_draft", "settings": {"client_id": "<client id>", "tenant": "<tenant id>"}} and the client secret by prepare_secret (purpose destination). The token request goes to your tenant's path on login.microsoftonline.com.
  • The receipt shows the draft's message id. Open Outlook Drafts, check it, and send it yourself.

Teams channel messages

The teams_channel_message destination posts the approved result to one channel with POST https://graph.microsoft.com/v1.0/teams/<team id>/channels/<channel id>/messages, only after a person approves the card.

  • Its sign-in asks for exactly offline_access ChannelMessage.Send (no admin consent). The signed-in person must be able to post in that channel. Personal Microsoft accounts are not supported by Microsoft for this.
  • Finding the ids. In Teams, copy the channel's link. It looks like https://teams.microsoft.com/l/channel/<channel id>/<channel name>?groupId=<group id>&tenantId=<tenant id>. The channel id is the part after /channel/, URL-decoded, like 19:...@thread.tacv2. The team id is the groupId value: a team has the same id as its Microsoft 365 group.
  • Create it with manage_destination action create, webhook {"preset": "teams_channel_message", "settings": {"client_id": "<client id>", "tenant": "<tenant id>", "team_id": "<group id>", "channel_id": "19:...@thread.tacv2"}}. Only a person in the web app sets or changes the team and channel.
  • The approved text is posted exactly as approved, as plain text, at most 20,000 bytes (well under Teams' limit of about 100 KB per post), with no mentions or attachments. A newer version that nobody approved is never posted. If BakedBrie cannot tell whether Teams accepted the post, it never posts again by itself.

Sign-in facts to know

  • Access tokens usually last 60 to 90 minutes; BakedBrie uses the lifetime Microsoft sends and refreshes them itself.
  • Refresh tokens last 90 days and Microsoft replaces them on every use; BakedBrie stores the new one each time.
  • Microsoft has no revocation endpoint. Disconnecting deletes BakedBrie's copy of the sign-in and says so: "BakedBrie deleted its copy. To remove the app's access, open My Apps or ask your admin." Until you do, the refresh token stays valid at Microsoft for up to 90 days, although BakedBrie no longer holds it.
  • Adding or removing an operation changes the permissions, so the connection asks you to sign in again with exactly the new set.
  • Microsoft limits calls per app and tenant. When it answers 429, BakedBrie waits as long as Microsoft's Retry-After says.
  • Provider events (Graph change notifications) are not supported yet. Use a schedule.

Refusals you may see

CodeWhat it meansFix
OAUTH_TYPED_VALUE_INVALIDThe tenant is not a Directory (tenant) ID.Copy the uuid from the app's Overview page.
OAUTH_SCOPE_WIDER_THAN_REQUESTEDMicrosoft granted a permission BakedBrie did not ask for.Remove unused API permissions from your app registration, then sign in again.
CONNECTION_SCOPE_WIDER_THAN_OPERATIONSThe definition asks for a permission no ticked operation needs.Remove it, or tick the operation that needs it.
CONNECTION_EMAIL_ENDPOINT_REFUSEDA connection path sends or writes mail.Use the Outlook draft destination.
CONNECTION_MESSAGE_ENDPOINT_REFUSEDA connection path posts to Teams or a chat, or writes a calendar event.Use the Teams channel message destination.
CLIENT_SECRET_EXPIRINGThe client secret ends within 14 days.Make a new secret in Certificates & secrets and update the connection.

View as Markdown