Sending emails via Microsofts Graph API

Christopher Talke Buscaino

If you need to send emails from a shared mailbox using the Graph API, there’s a key thing to know up front:

  • The mailbox must be licensed if you’re accessing it via an application flow (not delegated).
  • There’s no workaround for hitting endpoints like /messages or /mailFolders without a license, this is just how the API works using the application flow.

Below is a quick and dirty PowerShell script to get you going.

It requires the following permissions: Mail.ReadWrite, Mail.Send, and MailboxSettings.Read

👋 Enjoy

$tenantId = ""
$clientId = ""
$clientSecret = ""
$fromUser = ""
$toUser = ""

try {

    $body = @{
        grant_type    = "client_credentials"
        scope         = "https://graph.microsoft.com/.default"
        client_id     = $clientId
        client_secret = $clientSecret
    }
    $tokenResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Body $body
    $accessToken = $tokenResponse.access_token

    $headers = @{ Authorization = "Bearer $accessToken" }
    $mailboxSettings = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/users/$fromUser/mailboxSettings" -Headers $headers
    $mailboxSettings | Format-List

    $emailBody = @{
        message = @{
            subject = "Test Email from App"
            body = @{
                contentType = "Text"
                content     = "This is a test email sent using Microsoft Graph with application permissions."
            }
            toRecipients = @(
                @{
                    emailAddress = @{
                        address = $toUser
                    }
                }
            )
        }
        saveToSentItems = $true
    }
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$fromUser/sendMail" -Headers $headers -Method POST -Body ($emailBody | ConvertTo-Json -Depth 10) -ContentType "application/json"
    Write-Host "Email sent from $fromUser to $toUser"
} 

catch {
    Write-Host "Error occurred with user endpoint, trying alternative..." -ForegroundColor Yellow
    Write-Host "Status Code: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Red
    
    $streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
    $errorContent = $streamReader.ReadToEnd() | ConvertFrom-Json
    Write-Host "Error Details: $($errorContent.error.message)" -ForegroundColor Red
    Write-Host "Error Code: $($errorContent.error.code)" -ForegroundColor Red
}