Skip to main content
The Ops Playbook

Auto-Granting Shared Mailbox Access With an AI Intent Bot

An Azure OpenAI intake bot parses shared mailbox requests, checks policy, and grants Exchange Online permissions before a ticket is ever opened.

Auto-Granting Shared Mailbox Access With an AI Intent Bot
David ChenDavid Chen10 min readTier L245 min

This playbook covers

Share

#The Old Way vs the New Way

A finance analyst needs Send As rights on the “AP-Invoices” shared mailbox before a supplier payment run. The old way starts with an email to the helpdesk alias, which sits in a shared queue until an L1 technician picks it up, cannot verify management approval unaided, and forwards it to L2. L2 waits for a manager to reply “approved” in an email thread, then runs a single Exchange Online cmdlet that takes ninety seconds to execute. The ticket itself, from submission to closure, routinely spans two to four business days and consumes three separate humans for work that is genuinely five minutes of typing once approval exists.

The new way removes every human touchpoint except the one that actually needs judgement: the approval decision. A Teams-based AI intake bot, built on Azure OpenAI function calling, reads the natural-language request, extracts the mailbox name and permission type, checks the request against a self-service policy table, and either auto-grants low-risk requests or routes an adaptive card approval to the correct manager. Once approved, an Azure Automation runbook executes the Exchange Online permission grant using an app-only certificate identity, then closes the ticket and posts the evidence back into the conversation. No technician opens a ticket queue for this category of request again unless the automation itself fails.

#Prerequisites and Permissions

Get the identity and scoping decisions right before touching code. This automation grants mailbox permissions, so it must run with the narrowest possible Exchange Online role and must never inherit Global Administrator rights.

  • An Azure AD app registration with an uploaded certificate (no client secrets) for app-only Exchange Online authentication.
  • An Exchange Online custom management role group, scoped by a management scope OU or recipient filter, granting only the specific role required for permission changes.
  • An Azure OpenAI resource (GPT-4o or later) with function calling enabled, deployed in a region matching your data residency requirement.
  • A Teams bot registration (Azure Bot Service) using the Microsoft Bot Framework SDK, or an equivalent Slack app if your organisation uses Slack.
  • An approval flow tied to the requester’s manager, pulled from the Azure AD manager attribute, delivered as a Teams adaptive card with a callback action.
  • A pre-approved self-service policy table (a SharePoint list, Dataverse table, or JSON config) listing mailboxes eligible for auto-grant without manager sign-off, such as shared calendars or low-sensitivity distribution mailboxes.
  • Write access to your ticketing system’s REST API (ServiceNow Table API or equivalent) to log and auto-close the resulting ticket record.

The minimum role for a technician configuring this pipeline is Exchange Recipient Administrator combined with Application Administrator for the app registration, not Global Administrator. Test scope for the pilot should be five non-critical shared mailboxes with no financial or regulatory sensitivity, and the blast radius is limited to mailbox permission entries, not mailbox content, licensing, or forwarding rules.

AI helpdesk shared mailbox access automation

#Implementation Steps

  1. Register the automation identity. Action: create an Azure AD app registration, upload a certificate (no secret), and grant it the custom Exchange Online role group scoped to the pilot mailboxes only. Expected result: the app can authenticate to Exchange Online non-interactively and can run Add-MailboxPermission only against the five pilot mailboxes. Evidence: capture the output of Get-ManagementRoleAssignment for the new role group and store it in the change record.
    1Connect-ExchangeOnline -CertificateThumbprint "A1B2C3D4E5F6" -AppId "11111111-2222-3333-4444-555555555555" -Organization "kbytech.onmicrosoft.com"
    2
    3New-RoleGroup -Name "SharedMailboxAutomation-Pilot" -Roles "Mail Recipients" -CustomRecipientWriteScope "PilotMailboxesOU"
    4Add-RoleGroupMember -Identity "SharedMailboxAutomation-Pilot" -Member "svc-mailbox-automation"

    Expected output:

    1Name                            Roles              RoleAssignmentPolicy
    2----                            -----              --------------------
    3SharedMailboxAutomation-Pilot   {Mail Recipients}
  2. Define the function-calling schema for the AI intake bot. Action: describe the “grant_mailbox_access” function to Azure OpenAI so the model returns structured arguments instead of free text. Expected result: a message like “can I get Send As on AP-Invoices” resolves to a JSON object with mailbox, permission_type and requester fields. Evidence: log the raw model completion and the parsed arguments for every request in Application Insights.
    1{
    2  "type": "function",
    3  "function": {
    4    "name": "grant_mailbox_access",
    5    "description": "Request permission on a shared mailbox for the current Teams user.",
    6    "parameters": {
    7      "type": "object",
    8      "properties": {
    9        "mailbox": {
    10          "type": "string",
    11          "description": "The shared mailbox display name or primary SMTP address."
    12        },
    13        "permission_type": {
    14          "type": "string",
    15          "enum": ["FullAccess", "SendAs", "SendOnBehalf"]
    16        },
    17        "justification": {
    18          "type": "string",
    19          "description": "One sentence business reason supplied by the requester."
    20        }
    21      },
    22      "required": ["mailbox", "permission_type", "justification"]
    23    }
    24  }
    25}
  3. Validate the request against the self-service policy table. Action: the bot’s backend checks whether the requested mailbox is tagged auto-approve or requires manager sign-off, and confirms the requester is a licensed user in good standing. Expected result: low-risk mailboxes skip straight to provisioning; everything else generates an approval card. Evidence: the policy lookup result and decision reason are written to the automation log before any Exchange call is made.
    1def resolve_policy(mailbox, requester_upn, policy_table):
    2    entry = policy_table.get(mailbox.lower())
    3    if entry is None:
    4        return {"decision": "escalate", "reason": "mailbox not in policy table"}
    5    if entry["auto_approve"] and is_active_user(requester_upn):
    6        return {"decision": "auto_approve", "reason": "pre-approved low-risk mailbox"}
    7    return {"decision": "manager_approval", "manager": get_manager(requester_upn)}
  4. Send the adaptive card approval when required. Action: for anything outside the auto-approve list, post an adaptive card to the requester’s manager with Approve and Reject actions. Expected result: the manager receives a native Teams card within seconds, and the response is captured via the bot’s action callback. Evidence: store the manager’s decision, timestamp and Teams message ID against the request record.
    1{
    2  "type": "AdaptiveCard",
    3  "version": "1.5",
    4  "body": [
    5    {
    6      "type": "TextBlock",
    7      "text": "Approval needed: SendAs on AP-Invoices for j.morgan@kbytech.com",
    8      "wrap": true,
    9      "weight": "Bolder"
    10    },
    11    {
    12      "type": "TextBlock",
    13      "text": "Justification: Processing supplier payment run.",
    14      "wrap": true
    15    }
    16  ],
    17  "actions": [
    18    { "type": "Action.Submit", "title": "Approve", "data": { "decision": "approve" } },
    19    { "type": "Action.Submit", "title": "Reject", "data": { "decision": "reject" } }
    20  ]
    21}
  5. Provision the permission with an Azure Automation runbook. Action: once the decision is auto_approve or manager approved, trigger a runbook via webhook that runs Add-MailboxPermission or Add-RecipientPermission depending on permission_type. Expected result: the permission is live in Exchange Online within two minutes, with replication confirmed. Evidence: the runbook job output and the corresponding Get-MailboxPermission confirmation are attached to the ticket record.
    1param(
    2    [Parameter(Mandatory = $true)][string]$Mailbox,
    3    [Parameter(Mandatory = $true)][string]$Requester,
    4    [Parameter(Mandatory = $true)][ValidateSet("FullAccess", "SendAs", "SendOnBehalf")][string]$PermissionType
    5)
    6
    7Connect-ExchangeOnline -CertificateThumbprint $env:AUTOMATION_CERT_THUMBPRINT -AppId $env:AUTOMATION_APP_ID -Organization "kbytech.onmicrosoft.com"
    8
    9switch ($PermissionType) {
    10    "FullAccess"    { Add-MailboxPermission -Identity $Mailbox -User $Requester -AccessRights FullAccess -AutoMapping:$false }
    11    "SendAs"        { Add-RecipientPermission -Identity $Mailbox -Trustee $Requester -AccessRights SendAs -Confirm:$false }
    12    "SendOnBehalf"  { Set-Mailbox -Identity $Mailbox -GrantSendOnBehalfTo @{Add=$Requester} }
    13}
    14
    15Get-MailboxPermission -Identity $Mailbox | Where-Object { $_.User -like "*$Requester*" } | Format-List

    Expected output:

    1Identity     : AP-Invoices
    2User         : kbytech.com/Users/j.morgan
    3AccessRights : {SendAs}
    4Deny         : False
  6. Close the loop with the ticketing system. Action: the runbook posts a completion webhook to ServiceNow’s Table API, creating and immediately resolving an incident record with the full audit trail attached, so reporting still shows a ticket existed even though no human worked it. Expected result: a closed, auto-resolved incident appears in the queue with resolution notes, and the requester receives a Teams confirmation message. Evidence: the HTTP 201 response from ServiceNow and the ticket number returned in the payload.
    1{
    2  "short_description": "Shared mailbox access - AP-Invoices - SendAs",
    3  "category": "Access Request",
    4  "state": "Resolved",
    5  "close_code": "Solved (Automated)",
    6  "close_notes": "Auto-granted via AI intake bot after manager approval. Runbook job ID rb-20482.",
    7  "u_automation_source": "TeamsIntentBot-v2",
    8  "caller_id": "j.morgan@kbytech.com"
    9}
  7. Pilot for two weeks before widening scope. Action: run the full flow against only the five pilot mailboxes for at least ten business days, comparing auto-resolution against a shadow queue where L2 still manually reviews the same requests without acting. Expected result: zero mismatches between the automation’s decision and what the shadow reviewer would have done. Evidence: a side-by-side comparison spreadsheet exported from the automation log and the shadow queue.

#Verification and Expected Evidence

Before declaring this live for a wider mailbox set, confirm three things independently of the automation’s own logs. First, run Get-MailboxPermission and Get-RecipientPermission against each pilot mailbox and diff the result against the automation’s own record of what it granted; they must match exactly. Second, pull the Microsoft 365

unified audit log for the “Add-MailboxPermission” and “Add-RecipientPermission” operations and confirm the AppId in the audit entry matches the automation’s app registration, not an interactive admin session. Third, confirm every ticket created by the webhook carries the u_automation_source field, so reporting can separate AI-resolved tickets from manually worked ones without guesswork.

1Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -Operations "Add-MailboxPermission","Add-RecipientPermission" -ResultSize 50 |
2    Select-Object CreationDate, UserIds, Operations

#Rollback

Every grant this pipeline makes is reversible with a single cmdlet, which is precisely why it is safe to automate. If a permission was granted in error, or the policy table was misconfigured and something was auto-approved that should not have been, remove it immediately and revoke the automation’s ability to repeat the mistake.

1Remove-MailboxPermission -Identity "AP-Invoices" -User "j.morgan@kbytech.com" -AccessRights FullAccess -Confirm:$false
2Remove-RecipientPermission -Identity "AP-Invoices" -Trustee "j.morgan@kbytech.com" -AccessRights SendAs -Confirm:$false

If the fault is in the policy table itself rather than a single grant, disable the runbook’s webhook trigger in Azure Automation, set the bot’s fallback response to “Your request has been forwarded to a technician” and route all incoming requests to the human L2 queue until the policy entry is corrected and retested against the pilot mailboxes.

#Failure and Escalation Conditions

This automation should escalate to a human technician automatically, not silently retry, under any of these conditions:

Auto-Granting Shared Mailbox Access With an AI Intent Bot architecture diagram 2
  • The Azure OpenAI function call returns a mailbox name that does not resolve to an existing distribution or shared mailbox object after a Get-Recipient lookup.
  • The manager approval card receives no response within four business hours, at which point the request routes to the requester’s manager’s manager and pages the on-call L2 technician.
  • The Azure Automation runbook job status returns Failed or Suspended, which should fire a Log Analytics alert to the messaging team’s on-call channel within five minutes.
  • The certificate used for app-only authentication is within thirty days of expiry, flagged by a scheduled Azure Automation health check, not discovered when a grant silently fails.
  • Any request targets a mailbox tagged as regulated (legal hold, litigation, or finance-controlled) in the policy table; these must always route to manual review regardless of auto-approve settings.

#Measuring Ticket Deflection

Deflection here is not “fewer emails,” it is a measurable drop in average handling time and a rising share of access-request tickets closed with zero human touch. Track these numbers weekly for the first quarter after go-live:

  • Percentage of “Shared Mailbox Access” category tickets closed with close_code “Solved (Automated)” versus manually worked.
  • Median time from request to resolution, split by auto-approved versus manager-approved paths.
  • Escalation rate: the proportion of automated requests that hit one of the failure conditions above and required a human.
  • False-grant rate from the verification audit: any permission that should not have been approved, caught within seven days.
1SELECT
2    DATE_TRUNC('week', opened_at) AS week,
3    COUNT(*) AS total_requests,
4    SUM(CASE WHEN close_code = 'Solved (Automated)' THEN 1 ELSE 0 END) AS auto_resolved,
5    ROUND(100.0 * SUM(CASE WHEN close_code = 'Solved (Automated)' THEN 1 ELSE 0 END) / COUNT(*), 1) AS deflection_pct
6FROM incidents
7WHERE category = 'Shared Mailbox Access'
8GROUP BY 1
9ORDER BY 1;

A pilot running against five mailboxes for two weeks with zero mismatches and a four-hour median resolution time on the manager-approval path (down from two to four days) is a reasonable bar to clear before widening the policy table to the next fifty mailboxes. Expand in batches of the same size, re-running the verification audit after each batch rather than switching every mailbox on at once.

Evidence trail

Sources and verification

Primary documentation and external technical references used in this article.

  1. 01Add-MailboxPermission in Exchange Online PowerShelllearn.microsoft.com
  2. 02Exchange Online permissions and management role groupslearn.microsoft.com
  3. 03function calling for structured model outputlearn.microsoft.com
David Chen

David Chen

Ops Playbook Architect

David Chen is a Senior Data Engineer focused on constructing high-throughput, fault-tolerant data pipelines and real-time streaming architectures.

Published
View Profile
Reader Interaction

Comments

Add a thoughtful note on Auto-Granting Shared Mailbox Access With an AI Intent Bot. Comments are checked for spam and held for moderation before appearing.

Loading comments...
Comment submission is disabled until Cloudflare Turnstile keys are configured.

Learn More About KBY

Was this useful?

Operate smarter, with fewer recurring tickets.

Receive new operational playbooks, incident-prevention guidance, automation scripts and recovery runbooks.