How to Send email using Graph API (OAuth) - aaqibwani/M365 GitHub Wiki

Sending emails using the Microsoft Graph API is a modern, secure, and scalable approach that stands in stark contrast to the legacy SMTP client authentication model. One of the major advantages of using the Graph API over traditional SMTP client AUTH is its support for modern authentication protocols such as OAuth 2.0. With OAuth, you can implement both delegated and application-level permissions, offering a granular level of control over what actions an application can perform on behalf of a user or service.

Using application-level permission to send email using Graph API

Create an application registration in Entra ID

  • Go to Entra ID > App Registrations (using a Global Administrator or an Application Administrator)
  • Select New registration. Provide a descriptive name (e.g., "GraphEmailSender").
  • For a single-tenant solution, select Accounts in this organizational directory only.
  • Redirect URI: You may leave it blank
  • Click on Register.

image

Add Microsoft Graph API Permissions

  • In the menu for your app, click on API permissions.
  • Click the Add a permission button.
  • Under the Microsoft APIs section, select Microsoft Graph.
  • Choose Application permissions (this enables app-only/authentication).
  • Scroll down and check Mail.Send
  • Click Add permissions button to add the selected permission.

image

image

image

Grant Admin Consent to the Application

Since you’re using application permissions, you must grant tenant-wide consent:

  • In the API permissions blade, click Grant admin consent.
  • Confirm the consent when prompted.

image

image

image

Configure Client Credentials

  • In the app’s left-hand menu, click on Certificates & secrets.
  • Under Client secrets, click New client secret.
  • Provide a description (e.g., "GraphAPISecret") and choose an appropriate expiration period.
  • Click Add.
  • The Value will be displayed. IMPORTANT: Copy this value immediately as it will be hidden as soon as you move away from the page. Value is the one highlighted in the screenshot. Do not confuse it with the Secret ID.

image

image

Test the mail flow

Use the below test script to verify if you're able to send emails using Graph API:

param(
    [Parameter(Mandatory = $true)]
    [string]$TenantId,

    [Parameter(Mandatory = $true)]
    [string]$ClientId,

    [Parameter(Mandatory = $true)]
    [string]$ClientSecret,

    [Parameter(Mandatory = $true)]
    [string]$SenderEmail,

    [Parameter(Mandatory = $true)]
    [string]$RecipientEmail
)

# 1. Get an OAuth token using client credentials
$tokenEndpoint = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
$body = @{
    client_id     = $ClientId
    client_secret = $ClientSecret
    scope         = "https://graph.microsoft.com/.default"
    grant_type    = "client_credentials"
}

Write-Host "Requesting OAuth token from Azure AD..."
try {
    $tokenResponse = Invoke-RestMethod -Uri $tokenEndpoint -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"
    $accessToken = $tokenResponse.access_token
    Write-Host "✔ Access token acquired." -ForegroundColor Green
} catch {
    Write-Error "Failed to obtain access token: $_"
    exit 1
}

# 2. Create the email message as a JSON payload for Microsoft Graph
$message = @{
    message = @{
        subject = "Test Email from Graph API Using Application Permissions"
        body = @{
            contentType = "Text"
            content     = "Hello, this email was sent by an application using Microsoft Graph API and app-only authentication."
        }
        toRecipients = @(
            @{
                emailAddress = @{
                    address = $RecipientEmail
                }
            }
        )
    }
    saveToSentItems = "false"
} | ConvertTo-Json -Depth 4

# 3. Send the email via Microsoft Graph API
# Use the /users/{senderMail}/sendMail endpoint.
$graphEndpoint = "https://graph.microsoft.com/v1.0/users/$SenderEmail/sendMail"
$headers = @{
    Authorization = "Bearer $accessToken"
    "Content-Type" = "application/json"
}

Write-Host "Sending email via Microsoft Graph..." -ForegroundColor Cyan
try {
    Invoke-RestMethod -Uri $graphEndpoint -Method Post -Headers $headers -Body $message
    Write-Host "✔ Email sent successfully!" -ForegroundColor Green
} catch {
    Write-Error "Failed to send email: $_"
}
  • Save the above file as graphemail.ps1
  • Open Command Prompt and navigate to the directory where the script is saved
  • Run the below command with the details from your tenant
.\graphemail.ps1 -TenantId <> -ClientId <> -ClientSecret <> -SenderEmail [email protected] -RecipientEmail [email protected] -Verbose

Tenant ID: Your unique tenant identifier. ClientId: The Application (client ID). ClientSecret: The value we copied when creating the client secret (not the Secret ID).

image

image

image

Create an Application Access Policy in Exchange Online

An Application Access Policy in Exchange Online is used to restrict what mailboxes an app can access when it's granted application permissions (i.e., it runs without a signed-in user). Without this policy, an app with permissions like Mail.Read could potentially access every mailbox in your tenant or Mail.Send could potentially Send As any mailbox which is a big no-no for security and compliance.

Hence, to limit the app’s ability to send email from only specific mailboxes, you can create an application access policy in Exchange Online:

  • Connect to EXO PowerShell
Connect-ExchangeOnline
  • Create the Application Policy
New-ApplicationAccessPolicy -AppId "your-app-client-id" -PolicyScopeGroupId "[email protected]" -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"

Use a user mailbox or a mail-enabled security group in PolicyScopeGroupId. For Shared mailboxes, add the Shared mailbox to a mail-enabled security group and use the group in PolicyScopeGroupId.

  • Test the Application Policy

Once the application policy is created, verify that access is granted only to the scoped users.

Test-ApplicationAccessPolicy -AppId <APP ID or Client ID> -Identity <email address>

Screenshot 2025-06-18 235126

image