EDIT 25Oct2021:
This article discusses Send-MgUserMessage, though I’ve since realized Send-MgUserMail might be a more efficient option in many cases.
EDIT 27Oct2021:
I have written a follow on-article for Send-MgUserMail. Check it out when you’re done with this one.
Original Post:
I’ve been incorporating the new “SDK” cmdlets into my work lately, and though I’m not entirely convinced using them is any easier than just working with Graph directly, I wanted to update an old script that used Send-Mailmessage, and managing tokens and HTTP headers felt like overkill. Speaking of Send-MailMessage, you may have noticed this harshly worded message on its help page:
Warning
The Send-MailMessage cmdlet is obsolete. This cmdlet does not guarantee secure connections to SMTP servers. While there is no immediate replacement available in PowerShell, we recommend you do not use Send-MailMessage. For more information, see Platform Compatibility note DE0005.
Send-MgUserMessage is arguably the most direct replacement (see 25Oct2021 edit), but it is more difficult to use, due to the fact we need to build several custom objects, whereas with Send-MailMessage, the parameters did the work for us.
In my scenario, I also wanted this task to run as an application, defined in Azure Active Directory, using application permissions – not delegated through my personal account. Additionally, I wanted to use a certificate to authenticate to Azure instead of managing a “client secret”. I’m choosing not to walk through those prerequisites here, because they are already well documented:
- Use app-only authentication with the Microsoft Graph PowerShell SDK
- Create a self-signed public certificate to authenticate your application
To get started, install the following PowerShell Modules. I should also point out that if you already have these installed, be sure to upgrade to the latest version (1.7.0 at time of writing), since there have been significant changes in how this cmdlet works.
Install-Module Microsoft.Graph.Authentication
Install-Module Microsoft.Graph.Mail
Install-Module Microsoft.Graph.Users.Actions
Once you’ve got those, you can start sending email. Here is a “simple” example that uses a HTML body, as well as uploads a small attachment (attachments above 3MB are more complicated). In the below example, I’m using a sample book1.xlsx file.
#requires -modules Microsoft.Graph.Authentication,Microsoft.Graph.Mail,Microsoft.Graph.Users
#connect with CBA
# https://docs.microsoft.com/en-us/graph/powershell/app-only?tabs=azure-portal
# https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-self-signed-certificate
$ConnectParams = @{
ClientId = '84af9676-<not real>4194bb6d9e3'
TenantId = 'ef508849-9d0<not real>5d800549'
CertificateThumbprint = '96546bf89<not real>67332c703e15123b07'
}
Connect-Graph @ConnectParams
#recipients
$EmailAddress = @{address = 'user1@recipientDomain.com'} # https://docs.microsoft.com/en-us/graph/api/resources/recipient?view=graph-rest-1.0
$Recipient = @{EmailAddress = $EmailAddress} # https://docs.microsoft.com/en-us/graph/api/resources/emailaddress?view=graph-rest-1.0
#Body
$body = @{
content = '<html>hello <b>world</b></html>'
ContentType = 'html'
}
#Attachments
# If over ~3MB: https://docs.microsoft.com/en-us/graph/outlook-large-attachments?tabs=http
$AttachmentPath = 'C:\tmp\Book1.xlsx'
$EncodedAttachment = [convert]::ToBase64String((Get-Content $AttachmentPath -Encoding byte))
$Attachment = @{
"@odata.type"= "#microsoft.graph.fileAttachment"
name = ($AttachmentPath -split '\\')[-1]
contentBytes = $EncodedAttachment
}
#Create Message (goes to drafts)
$Message = New-MgUserMessage -UserId me@mydomain.com -Body $body -ToRecipients $Recipient -Subject Subject1 -Attachments $Attachment
#Send Message
Send-MgUserMessage -UserId me@mydomain.com -MessageId $Message.Id
#end
Once you’re done, the message should look like this in the Sent Items folder of whoever you used to do the sending:
As I mentioned in the article, attachments over 3MB require additional code. It seems Glen Scales recently wrote a function to take the guess work out of this. Check it out here: https://gsexdev.blogspot.com/2021/09/sending-message-with-large-attachment.html
Hello Mike,
Thx for this example script, Microsoft hasn’t made the manual very clear. I have one unexpected issue. I try to send an email to an external email address for the tenant.
source email: smtp@domain1.com
email address (target): user@domain2.com (other tenant then domain1.com)
The email returns immediately with this error:
user@domain2.com
Your message wasn’t delivered because the recipient’s email provider rejected it.
user@domain2.com
Remote Server returned ‘550 5.7.708 Service unavailable. Access denied, traffic not accepted from this IP. For more information please go to http://go.microsoft.com/fwlink/?LinkId=526653 AS(7230) [DBXXXXXXXXXX.EURP192.PROD.OUTLOOK.COM]’
The IP is not blacklisted, but I requested a delist anyway, that did not help.
When I send from OWA from: smtp@domain1.com to: user@domain2.com it works perfectly.
I have configured the Microsoft Graph SPN permissions and granted these:
Mail.Read
Mail.ReadBasic
Mail.ReadBasic.All
Mail.ReadWrite
Mail.Send
For other purpose the SPN also has:
Calendars.Read
Directory.ReadAll
IdentityRiskyUsers.Read.All
Do you have any clue what could fix this?
thx.
Stefan Peters
Hmm, without more context I don’t have any answers. I’d check out message tracking in your tenant and also test with other to/from domains to rule out this app.
Very useful article. Is there a way to inline attachments?
Yes, I think so. I’ve not tried it with the cmdlet, but its definitly possible with Graph, so it’d just be a matter of figuring out the structure. Look for ‘isInline’ in this article for reference:
attachment resource type
https://docs.microsoft.com/en-us/graph/api/resources/attachment
I just tested using Graph now, it’s fairly straight forward, though it would be a lot easier to have it in Send-MgUserMessage.
Figured it out, just needed to add 2 lines (contentID and ContentType):
$Attachment = @{
“@odata.type”= “#microsoft.graph.fileAttachment”
name = ($AttachmentPath -split ‘\\’)[-1]
contentBytes = $EncodedAttachment
contentID = “image1”
contentType = “image/png”
}
Then in the body use the contentID:
Now I just need to work out how to do multiple attachments.
Nice! Checkout my function ConvertTo-IMicrosoftGraphAttachment which may help, though it isn’t an exact match for this structure.
https://mikecrowley.us/2021/10/27/sending-email-with-send-mgusermail-microsoft-graph-powershell/
I did, and I used the other function for multiple recipients, works great!