Connect Microsoft 365 with Slack

Implementation Guide

Overview: Connecting Microsoft 365 and Slack

Microsoft 365 and Slack serve complementary but distinct functions in the enterprise communication stack. Microsoft 365 manages structured, persistent data flows: email correspondence in Outlook, scheduled events in Calendar, documents and files in SharePoint and OneDrive, and meeting infrastructure in Teams. Slack, by contrast, serves as the real-time conversational layer where teams coordinate work, make rapid decisions, and respond to operational events. The Microsoft 365-to-Slack integration bridges these two communication paradigms, enabling organisations to route critical information from the Microsoft 365 ecosystem into Slack channels where the relevant teams are already working, without requiring users to context-switch between applications.

The practical outcome of this integration is a significant reduction in notification latency and missed information. When a high-priority email arrives in a monitored Outlook mailbox, the integration can post a formatted summary to a dedicated Slack channel, eliminating the need for team members to poll a shared inbox manually. When a calendar event is created for a customer meeting, the integration can post a pre-meeting briefing card to the assigned sales channel 15 minutes before start time. When a colleague uploads a contract to a SharePoint document library, the integration can notify the legal team's Slack channel with a direct link to the file. These workflows collectively reduce the cognitive overhead of managing two separate notification surfaces.

Core Prerequisites

On the Microsoft 365 side, you must register an application in Azure Active Directory via the Azure Portal under Azure AD > App Registrations > New Registration. The application must support the Authorization Code OAuth 2.0 flow with a redirect URI pointing to your integration middleware or iPaaS platform's OAuth callback URL. The following Microsoft Graph API permissions are required and their type (delegated vs. application) determines whether Global Administrator consent is needed. For automated server-side workflows without a signed-in user, use application permissions: Mail.Read (application), Calendars.Read (application), Files.Read.All (application), and User.Read.All (application). Application-level permissions bypass per-user authorization but require a Global Administrator to grant admin consent in Azure AD > Enterprise Applications > your app > Permissions > Grant Admin Consent. For delegated flows where a specific user's mailbox is being monitored with that user's consent, use delegated permissions for the same scopes. Note that delegated Mail.Read requires the user to interactively re-authorize if the refresh token expires or is revoked.

On the Slack side, create a Slack App at api.slack.com/apps and install it to the target workspace. The app requires the following Bot Token OAuth scopes: chat:write (to post messages to channels), channels:read (to enumerate public channel IDs), channels:write (to create new channels programmatically), im:write (to send direct messages), users:read (to resolve Slack user IDs from email addresses, required for DMs and mentions), and users:read.email (required alongside users:read to look up users by their email address). The Bot User OAuth Token beginning with xoxb- is the credential used for all outbound Slack API calls. For posting to private channels, the bot must be explicitly invited to those channels before the token has write access—a channel_not_found or not_in_channel error in the Slack API response is the reliable symptom when this step is omitted.

Top Enterprise Use Cases

The most widely deployed use case for this integration is email-to-Slack notification routing for shared operational mailboxes. Operations and support teams monitor inboxes such as [email protected] or [email protected] for inbound requests from key accounts or trigger-based alerts from third-party services. Instead of requiring team members to poll these inboxes, the integration listens for new messages via the Microsoft Graph Change Notifications API and posts structured Block Kit notifications to a designated Slack channel, including the sender name, subject line, a truncated body preview, and a deep-link button back to the full email in Outlook Web.

The second critical use case is calendar-driven meeting preparation notifications. When a new calendar event is created in Microsoft 365 for a meeting with an external attendee, the integration posts a pre-meeting context card to the relevant team's Slack channel 15 minutes before the scheduled start time. This card can include the meeting title, the external attendee's name and company, a link to the relevant CRM record, and any shared agenda documents from OneDrive.

A third major use case is SharePoint and OneDrive document notification routing. When a contract, legal document, or compliance artefact is uploaded to a specific monitored SharePoint document library, the integration posts a card to a designated Slack channel with the file name, the uploader's display name, the modification timestamp, and a shareable direct link. This enables document-centric approval and review workflows without requiring all stakeholders to configure SharePoint notification subscriptions independently.

Step-by-Step Implementation Guide

The production-grade implementation of Microsoft 365 event detection relies on the Microsoft Graph Change Notifications API, which uses a webhook-style subscription model and is significantly more efficient than interval polling. To create a subscription that watches for new messages in a specific Outlook mailbox folder, your integration issues the following POST request:

curl -X POST "https://graph.microsoft.com/v1.0/subscriptions" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d "{
    \"changeType\": \"created\",
    \"notificationUrl\": \"https://your-integration.example.com/graph-webhook\",
    \"resource\": \"me/mailFolders('Inbox')/messages\",
    \"expirationDateTime\": \"2025-11-01T18:23:45.9356913Z\",
    \"clientState\": \"your-secret-client-state-token\"
  }"

A critical implementation constraint is that Microsoft Graph subscriptions expire. The maximum expiry duration for me/messages and mailbox subscriptions is 4230 minutes (approximately 3 days). Your integration must run a scheduled renewal job that PATCHes the expirationDateTime of each active subscription before it lapses. A subscription that expires stops delivering notifications silently—there is no error thrown and events are simply dropped with no recovery mechanism. The renewal endpoint is https://graph.microsoft.com/v1.0/subscriptions/{subscriptionId} and the PATCH body contains only the new expirationDateTime value.

When the Graph API delivers a change notification to your registered notificationUrl, it sends a POST request with a body in the following format:

{
  "value": [
    {
      "id": "lsgTZMr9KwAAA",
      "subscriptionId": "8a23cc23-7b0e-4ba5-b4ef-b6a2f82d7d5e",
      "changeType": "created",
      "clientState": "your-secret-client-state-token",
      "resource": "Users/[email protected]/Messages/AAMkAGVmMyExampleMessageId",
      "resourceData": {
        "@odata.type": "#Microsoft.Graph.Message",
        "@odata.id": "Users/[email protected]/Messages/AAMkAGVmMyExampleMessageId",
        "id": "AAMkAGVmMyExampleMessageId"
      }
    }
  ]
}

As with Xero's webhook pattern, the notification payload contains only a resource ID and not the full message object. Your integration must make a secondary GET request to retrieve the content needed to construct the Slack notification:

curl -X GET "https://graph.microsoft.com/v1.0/me/messages/AAMkAGVmMyExampleMessageId?\$select=subject,from,bodyPreview,receivedDateTime,webLink" \
  -H "Authorization: Bearer {access_token}"

The Slack message is then delivered using the chat.postMessage Web API method. For structured notifications, use Block Kit rather than plain text strings. The following Block Kit payload creates a clean, actionable email notification card in Slack:

{
  "channel": "C012AB3CD",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "New Email: Renewal discussion for Q4 contract"
      }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*From:*\[email protected]" },
        { "type": "mrkdwn", "text": "*Received:*\nOct 14, 2025 09:32 UTC" }
      ]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "Please find attached the updated terms for review ahead of the Q4 renewal window..."
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "Open in Outlook" },
          "url": "{webLink_from_graph_response}"
        }
      ]
    }
  ]
}

In Make, the module sequence begins with a Microsoft 365 Email > Watch Emails trigger module pointed at the relevant mailbox folder. This module uses polling under the hood, so for the lowest possible latency (sub-1-minute), use a Custom Webhook module in combination with a serverless relay that receives Graph Change Notifications and forwards them to the Make webhook URL. For the Slack output, add a Slack > Create a Message module, select the target channel by ID, enable the Use Blocks toggle, and paste in the Block Kit JSON structure with dynamic field references mapped from the email trigger output. For calendar-based reminders, use the Microsoft 365 Calendar > Watch Events trigger and a Tools > Get a Date module to compute the notification offset before routing to Slack.

In Zapier, the Microsoft Outlook > New Email trigger combined with the Slack > Send Channel Message action provides a straightforward starting point. For conditional routing—for example, only notifying for emails from specific sender domains—insert a Filter by Zapier step between the trigger and action, configured to check that the From Email field ends with the target domain. For calendar notifications, the Microsoft Outlook > New Calendar Event trigger combined with a Delay by Zapier step (set to fire 15 minutes before the event start time) creates an effective pre-meeting Slack reminder.

Common Pitfalls & Troubleshooting

A 401 Unauthorized from the Microsoft Graph API most commonly occurs because the Azure AD access token has expired (default TTL is 1 hour) or was obtained with insufficient scopes. Graph API error responses include a structured error.code and error.message field in the JSON body that provides precise diagnostic detail. For expired tokens, implement the OAuth 2.0 refresh token grant against https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token with grant_type=refresh_token. For scope deficiencies, decode the JWT access token and inspect the scp claim to confirm which permissions are actually present.

A 403 Forbidden from Graph API with error code Authorization_RequestDenied typically indicates that admin consent has not been granted for the required application-level permissions. Individual users cannot grant consent for permissions flagged as requiring administrator approval in the Azure AD portal. A Global Administrator must navigate to Azure AD > Enterprise Applications > select your application > Permissions > Grant Admin Consent for the entire organisation.

A 429 Too Many Requests from Microsoft Graph indicates the per-application throttling threshold has been exceeded. Graph API uses a complex token-bucket throttling model that varies by resource type and tenant size. The response will include a Retry-After header. At scale with many monitored mailboxes, implement request queuing with a rate-limited worker pool that respects the retry window before resubmitting failed requests.

On the Slack side, a channel_not_found error in the chat.postMessage API response means the channel ID is either invalid or the bot has not been invited to that channel. Run slack.conversations.info with the channel ID to verify it exists. A not_in_channel error specifically confirms the bot is excluded and must be added by a workspace member using /invite @your-bot-name in the target channel.