Post

How to send mail with Azure Automation Account with Graph API

In this post, I will show you how to send mail with an Azure Automation Account with the Graph API. This is a very simple solution, but it is very useful when you have to send mail from a script wit Azure Automation Account. I hate to use service users with a password, so I will use the system assigned managed identity to authenticate and send mail with the Graph API. Quick and easy, to allow the managed identity to send mail, but this in Secure way! Let’s start!

Prerequisites

  • Azure Subscription with coins
  • License for exchange online

Create Automation Account

First, we have to create an Automation Account. Go to the Azure portal and create a new resource. Search for “Automation Account” and create a new one. I will use the name “demoaa” for this solution. When we create the Automation Account, we have to enable the system assigned managed identity.

AA Create
AA Create

than we have to import two modules: “Microsoft.Graph.Authentication”, “Microsoft.Graph.Users”. Go to the Automation Account, Modules, and add these modules.

AA Create
AA Create
AA Create
AA Create

Wait until the modules are imported.

AA Create

Automation Account is ready, let’s give the necessary permissions to the managed identity.

Give permissions to the managed identity

First navigate to the Automation Account, and go to the “Identity” menu. Copy the Object ID of the managed identity.

Permission assignment

After that, open a cloud shell and run the following command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Connect-AzureAD

# Service Principal ID of the application that will be granted the permissions
$ServicePrincipalId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

# Get the Graph API Service Principal
$GraphApiResource = Get-AzureADServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"  

# Array what contains all the permissions
$Permissions = @('User.Read.All', 'Mail.Send')

# For each permission, we will create a new role assignment
foreach ($Permission in $Permissions) {
    $AppRole = $GraphApiResource.AppRoles | Where-Object {$_.Value -eq $Permission}
    New-AzureADServiceAppRoleAssignment -ObjectId $ServicePrincipalId -PrincipalId $ServicePrincipalId -Id $AppRole.Id -ResourceId $GraphApiResource.ObjectId
}

Good and Bad news. The good news is that we have the permissions, the bad news is now this service principal can send mail with the Graph API in any mailbox name. We have to restrict this.

Restrict the permissions

Next step to create ApplicationAccessPolicy for the managed identity. We have to restrict the permissions to the managed identity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Connect to Azure AD
Connect-AzureAD

# Service Principal ID of the application
$ServicePrincipalId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

# Get the Service Principal details
$ServicePrincipal = Get-AzureADServicePrincipal -ObjectId $ServicePrincipalId

# Get the AppId of the Service Principal
$AppId = $ServicePrincipal.AppId

# Connect to Exchange Online
Connect-ExchangeOnline

# Create a new application access policy
New-ApplicationAccessPolicy -AccessRight RestrictAccess -AppId $AppId -PolicyScopeGroupId "otto@gudszent.hu" -Description "Restrict access to the application" 

# Test the application access policy
Test-ApplicationAccessPolicy -Identity otto@gudszent.hu -AppId $AppId

Changes to application access policies can take longer than 1 hour to take effect in Microsoft Graph REST API calls, even when Test-ApplicationAccessPolicy shows positive results. source: Microsoft Docs

Create the runbook

Now we have to create a runbook. Go to the Automation Account, and create a new runbook. I will use the name “SendMail” for this solution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function Send-GraphApiEmail {
    param (
        [string]$From,
        [string[]]$To,
        [string]$Subject,
        [string]$BodyContent
    )

     try {
         # Get the token using a managed identity and connect to graph using that token
         Connect-AzAccount -Identity -ErrorAction Stop | Out-Null
         $AccessToken = Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com" -AsSecureString -ErrorAction Stop 

         # Connect to Microsoft Graph using the access token
         Connect-MgGraph -AccessToken $AccessToken.Token -NoWelcome -ErrorAction Stop | Out-Null

         # Verify the connection by getting user details
         Get-MgUser -UserId $From | Select-Object DisplayName, UserPrincipalName, UserType, AccountEnabled
     } catch {
         Write-Error $_.Exception.Message -ErrorAction Stop
     }

    # Define the email message
    $emailMessage = @{
        message = @{
            subject = $Subject
            body = @{
                contentType = "Text"
                content     = $BodyContent
            }
            toRecipients = @()
        }
    }

    # Add each recipient to the toRecipients array
    foreach ($recipient in $To) {
        $emailMessage.message.toRecipients += @{
            emailAddress = @{
                address = $recipient
            }
        }
    }

    # Convert the email message to JSON
    $emailMessageJson = $emailMessage | ConvertTo-Json -Depth 4

    # Send the email on behalf of the user
    Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/users/$From/sendMail" -Body $emailMessageJson -ContentType "application/json" -Headers @{ Authorization = "Bearer $AccessToken.Token" }
}

# Example usage
$recipients = @("recipient@gmail.com")
Send-GraphApiEmail -From "otto@gudszent.hu" -To $recipients -Subject "Test message" -BodyContent "Test message from Graph API"

Logs

To check the logs, we have to enable the diagnostic settings for Entra ID. Go to Diagnostic settings, and enable the logs for Graph API.

Permission assignment

After few minutes(15-20) we can check the logs in the Log Analytics workspace. With this KQL query, we can check the logs.

1
2
3
4
5
6
MicrosoftGraphActivityLogs 
| where TimeGenerated >= ago(30d) 
| where Roles has "Mail.Send" 
| where RequestUri contains "/sendMail" 
| extend EmailSendFrom = split(RequestUri, "/")[-2] 
| project AppId, EmailSendFrom, TimeGenerated

Conclusion

That’s all. We have a solution to send mail with the Graph API from an Azure Automation Account. This is a very simple solution, but very effective, never forget to limit the permissions to the managed identity, because the next audit will be very painful.

This post is licensed under CC BY 4.0 by the author.