How to send an email with attachment using Graph API and Powershell

I was recently working on a task to send an email with attachment using Microsoft Graph API. I included the Microsoft Graph API script code at the end of my powershell script. The script sends an email to a given email address with attachment and also have a table of contents in the body of the email.

I would be using an Azure service principal for sending an email using graph API. However you can modify the script according to your own requirements and use a user account as well instead of service principal.

There are prerequisite of the script which needs to be met before execute the code.

  • Azure service principal with mail.send permissions
  • Client ID of the application
  • Client secret
  • Tenant ID

Let’s see how to gather the prerequisite information first:

Azure service principal with mail.send permissions

You will need to create an Azure service principal from Azure active directory. Please follow below steps to create an Azure service principal.

  • Login on Microsoft Azure portal (https://portal.azure.com)
  • Go to Azure active directory.
  • Click on App registration on the left hand side
  • Click on + New registration

On Register an application, provide a Name of the application and Select Supported account types. Mostly we go for “Accounts in this organization directory only (XXXX only -single tenant). Then click on Register.

After you register the application, the application will show under App registration. Click on it to open and the click on API permissions on the left hand side. Click on + Add a permission link.

API Permissions mail.send

Click on Microsoft Graph -> Application Permissions -> search for mail.send. Select Mail.send and click on Add permission.

Microsoft Graph API permission Azure mail.send

Click on Grant admin consent for <your org name> to Grant admin consent to this application permission.

Grant admin consent for Azure App registration mail.send

Client ID, Client Secret and tenant ID Information

We also need client ID and Client secret information for the script. Therefore, you need to click on Certificates & Secrets from the application and generate a New client secret. Please follow below steps to generate a client secret of this application.

  • Login on Microsoft Azure portal.
  • Go to Azure active directory
  • Click on App registrations on the left hand side
  • Find and click the application created in previous step.
  • Click on Certificates & secrets and then click on + New client secret.

Provide a description and expiry date of the client secret. Copy the value of the client secret in a notepad. This value is visible only for a short period of time after than this will be hidden. So make sure to copy the value now and keep it secure. If you lose the client secret value, you have to create a new client secret from here which will generate a new value for the client secret.

Generate a client secret in Azure App registration

For client ID and tenant ID information, you will need to click on Overview tab of the application and copy Application (client) ID and Directory (tenant) ID information from there.

Copy Cliend ID and Tenant ID from Azure Application

Powershell code to send email using Graph API

First you need to store the client ID, client secret and tenant ID in a variable.

Store the client ID, client secret and tenant ID in a variable.

$clientid = "30ccc130-1613-4a09-8c6f-78hhsjbjekk1"
$clientsecret = "TWr8Q~yMNFKsobLjPNZ2mKo-egt8U49Hhbjdsk"
$tenantid = "97659d97-8dab-4122-80bd-caddddddd7"

Connect to Graph API

Use below code to connect to Graph API. Please note that this will require clientid and client secret information as you have it stored in $clientid and $clientsecret powershell variables.

Connect to Graph API

#Connection to MS GRAPH API
$tokenBody = @{
    Grant_Type    = "client_credentials"
    Scope         = "https://graph.microsoft.com/.default"
    Client_Id     = $clientId
    Client_Secret = $clientSecret
}

$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $tokenBody


$headers = @{
    "Authorization" = "Bearer $($tokenResponse.access_token)"
    "Content-type"  = "application/json"
}

Email Attachment variable

Next, create a variable called $attachment and provide the path of the attachment you want to send with the email. For example: $Attachment = “c:\temp\listofusers.csv”. You can modify the location according to where your email attachment is stored.

Email Attachment variable

$Attachment="C:\temp\ListofUsers.csv"

Get File name and contents and store it in variables $FileName and $base64string.

Get File name and contents

#Get File Name and Base64 string
$FileName=(Get-Item -Path $Attachment).name
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($Attachment))

Get the message parameters and covert it to json. $htmlobject is a powershell object. You can create a Powershell object and pipe it to Convertto-html cmdlet and use that object in the content. The content of $htmlobject will create email body. Please also make sure you have a sender email address stored in $FromAddress powershell variable. For example: $FromAddress = “testsender@gmail.com”

Get the message parameters and covert it to json

$MessageParams = @{
    "URI"         = "https://graph.microsoft.com/v1.0/users/$FromAddress/sendMail"
    "Headers"     = $headers
    "Method"      = "POST"
    "ContentType" = 'application/json'
    "Body" = (@{
          "message" = @{
          "subject" = "Userlist Report"
          "body"    = @{
               "contentType" = 'HTML' 
               "content"     = $htmloutput} 

          "toRecipients" = @(
          @{
          "emailAddress" = @{"address" = "xyz@gmail.com" }
        } )
        "attachments" = @(      
              @{      
                "@odata.type" = "#microsoft.graph.fileAttachment"
                "name" = $FileName
                "contentType" = "text/plain"
                "contentBytes" = "$base64string"          
                }        
        )
}
}) | ConvertTo-JSON -Depth 6
}

Send the message

Last command is to send an email using Invoke-RestMethod command.

Invoke-RestMethod

# Send the message
Invoke-RestMethod @Messageparams

Complete Script

#Connection to MS GRAPH API
$tokenBody = @{
    Grant_Type    = "client_credentials"
    Scope         = "https://graph.microsoft.com/.default"
    Client_Id     = $clientId
    Client_Secret = $clientSecret
}

$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $tokenBody

$headers = @{
    "Authorization" = "Bearer $($tokenResponse.access_token)"
    "Content-type"  = "application/json"
}

$Attachment=".\SPONotCompliantFileList.csv"
#Get File Name and Base64 string
$FileName=(Get-Item -Path $Attachment).name
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($Attachment))

$MessageParams = @{
    "URI"         = "https://graph.microsoft.com/v1.0/users/$FromAddress/sendMail"
    "Headers"     = $headers
    "Method"      = "POST"
    "ContentType" = 'application/json'
    "Body" = (@{
          "message" = @{
          "subject" = "Userlist Report"
          "body"    = @{
               "contentType" = 'HTML' 
               "content"     = $htmloutput} 

          "toRecipients" = @(
          @{
          "emailAddress" = @{"address" = "xyz@gmail.com" }
        } )
        "attachments" = @(      
              @{      
                "@odata.type" = "#microsoft.graph.fileAttachment"
                "name" = $FileName
                "contentType" = "text/plain"
                "contentBytes" = "$base64string"          
                }        
        )
}
}) | ConvertTo-JSON -Depth 6
}
# Send the message
Invoke-RestMethod @Messageparams

Conclusion

In this blog post, we have seen how to send an email using Microsoft graph API using Azure service principal. Make sure to make mail.send permissions added to the service principal and copy client ID, client secret and tenant ID values ready to be used in the script. I have tested this code and used it in few of my powershell scripts which sends an email with attachment saved locally.