Need to notify your team on a failed service, only to find that your PowerShell email has bounced? Unauthenticated email has become difficult to pass in many mail systems. You don’t want to miss an important email notification because you relied on outdated PowerShell cmdlets. The built-in cmdlet Send-MailMessage
no longer covers sending email securely.
So as useful as this cmdlet is, why is it no longer considered secure? Underneath, this PowerShell cmdlet uses the SmtpClient
.NET class. Unfortunately, this class does not support many of the modern encryption protocols and therefore, cannot guarantee a secure connection. The crux of the matter is that SmtpClient
is not going to be further developed to add features, such as for Opportunistic TLS, the official recommendation has been to no longer use this product.
What may be slightly confusing is that the classes and methods are still present even in the latest .NET Core releases. Though you may still use these methods,, the official recommendation is to move to alternative email sending methods. There are generally two methods now of sending Powershell email.
- .NET Library
- REST API
With those two methods in mind, let’s jump into some alternatives!
.NET MailKit
The most generic method that would replace SmtpClient and Send-MailMessage would be the recommended replacement, which is MailKit. This is a third-party, open-source library but maintained by a Microsoft employee and officially recommended for use in the documentation. This is similar to how Newtonsoft.JSON became a core part of the .NET platform despite being an open-source product.
Installing MailKit & MimeKit
Since MailKit and it’s core dependency of MimeKit are not native libraries available to .NET, we will need to install them first.
Make sure to run Install-Package
as an administrator or else the packages may not install properly.
Using the Install-Package
cmdlet, install MailKit
from the [nuget.org](<http://nuget.org>)
repository. If the installation works properly, you will see a number of dependencies installed alongside MailKit, including MimeKit.
Install-Package -Name 'MailKit' -Source 'nuget.org'
Sending an PowerShell Email via MailKit
The example below shows how we can send an email using the Gmail SMTP server but using MailKit. In this example, PowerShell 7.0.1 is the underlying version and we are using the latest .NET Standard 2.0 version of the DLL. Other DLL versions are available in the parent directory, if needed.
As you can see, we need to load both the MailKit DLL and the MimeKit DLL. If you only load MailKit, an error may not occur, but this will not work. The next steps are pretty similar to how the original SmtpClient
works in setting up the various configurations.
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MailKit.2.8.0\lib\netstandard2.0\MailKit.dll"
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MimeKit.2.9.1\lib\netstandard2.0\MimeKit.dll"
$SMTP = New-Object MailKit.Net.Smtp.SmtpClient
$Message = New-Object MimeKit.MimeMessage
$TextPart = [MimeKit.TextPart]::new("plain")
$TextPart.Text = "This is a test."
$Message.From.Add("[email protected]")
$Message.To.Add("[email protected]")
$Message.Subject = 'Test Message'
$Message.Body = $TextPart
$SMTP.Connect('smtp.gmail.com', 587, $False)
$SMTP.Authenticate('[email protected]', 'appspecificpassword' )
$SMTP.Send($Message)
$SMTP.Disconnect($true)
$SMTP.Dispose()
The reason that the password for the authentication step says
appspecificpassword
is that if you are properly using two-factor authentication, you will need to generate an app-specific password for your applications.
Emulating Send-MailMessage with MailKit
Although this does not have a direct correlation with Send-MailMessage
, what if we wanted to create a quick and easy function that wrapped the MailKit functionality into an alternative to the built-in cmdlet? In this example, we can create a Send-MailkitMessage
function to do a similar series of steps. Keep in mind that this does not replicate all of the functions and is vastly simplified.
Function Send-MailkitMessage {
[CmdletBinding(
SupportsShouldProcess = $true,
ConfirmImpact = "Low"
)] # Terminate CmdletBinding
Param(
[Parameter( Position = 0, Mandatory = $True )][String]$To,
[Parameter( Position = 1, Mandatory = $True )][String]$Subject,
[Parameter( Position = 2, Mandatory = $True )][String]$Body,
[Parameter( Position = 3 )][Alias("ComputerName")][String]$SmtpServer = $PSEmailServer,
[Parameter( Mandatory = $True )][String]$From,
[String]$CC,
[String]$BCC,
[Switch]$BodyAsHtml,
$Credential,
[Int32]$Port = 25
)
Process {
$SMTP = New-Object MailKit.Net.Smtp.SmtpClient
$Message = New-Object MimeKit.MimeMessage
If ($BodyAsHtml) {
$TextPart = [MimeKit.TextPart]::new("html")
} Else {
$TextPart = [MimeKit.TextPart]::new("plain")
}
$TextPart.Text = $Body
$Message.From.Add($From)
$Message.To.Add($To)
If ($CC) {
$Message.CC.Add($CC)
}
If ($BCC) {
$Message.BCC.Add($BCC)
}
$Message.Subject = $Subject
$Message.Body = $TextPart
$SMTP.Connect($SmtpServer, $Port, $False)
If ($Credential) {
$SMTP.Authenticate($Credential.UserName, $Credential.GetNetworkCredential().Password)
}
If ($PSCmdlet.ShouldProcess('Send the mail message via MailKit.')) {
$SMTP.Send($Message)
}
$SMTP.Disconnect($true)
$SMTP.Dispose()
}
}
Sending the actual message is as simple as calling the function that we just created and passing in the correct parameters.
$Params = @{
"To" = '[email protected]'
"From" = '[email protected]'
"Subject" = 'Test Email'
"Body" = 'This is a test.'
"SmtpServer" = 'smtp.gmail.com'
"Credential" = $Creds
"Port" = 587
}
Send-MailkitMessage @Params
Direct Send
To follow-up the use of MailKit, and how it can be used in a more practical sense in a modern environment that uses Office 365, we can take advantage of the Direct Send functionality available to users of Office 365. The original method using, Send-MailMessage
can be read in this article as well. There are a couple of caveats to this specific method of sending though.
- No external recipients are allowed
- Uses Port
25
instead of587
- Sender does not need a valid mailbox, but should if NDRs or replies are needed
Similar to how we used MailKit to send to Gmail, we are going to make one change to the existing code. With the Connect
method, we are adding the [MailKit.Security.SecureSocketOptions]::StartTls
option to make sure that TLS is used.
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MailKit.2.8.0\lib\netstandard2.0\MailKit.dll"
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MimeKit.2.9.1\lib\netstandard2.0\MimeKit.dll"
$SMTP = New-Object MailKit.Net.Smtp.SmtpClient
$Message = New-Object MimeKit.MimeMessage
$TextPart = [MimeKit.TextPart]::new("plain")
$TextPart.Text = "This is a test."
$Message.From.Add("[email protected]")
$Message.To.Add("[email protected]")
$Message.Subject = 'Test Message'
$Message.Body = $TextPart
$SMTP.Connect('{tenant}.mail.protection.outlook.com', 25, [MailKit.Security.SecureSocketOptions]::StartTls, $False)
$SMTP.Authenticate('[email protected]', 'mypassword' )
$SMTP.Send($Message)
$SMTP.Disconnect($true)
$SMTP.Dispose()
Amazon SES
Another example of using MailKit to send emails is using Amazon SES (Simple Email Service). To learn more about the overall setup and configuration, read this article, but after that has been done you can see how simple it is to send an email via MailKit. By utilizing the correct endpoint and TLS, we can simply send emails via MailKit.
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MailKit.2.8.0\lib\netstandard2.0\MailKit.dll"
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MimeKit.2.9.1\lib\netstandard2.0\MimeKit.dll"
$SMTP = New-Object MailKit.Net.Smtp.SmtpClient
$Message = New-Object MimeKit.MimeMessage
$TextPart = [MimeKit.TextPart]::new("plain")
$TextPart.Text = "This is a test."
$Message.From.Add("[email protected]")
$Message.To.Add("[email protected]")
$Message.Subject = 'Test Message'
$Message.Body = $TextPart
$SMTP.Connect('email-smtp.ap-southeast-2.amazonaws.com', 587, [MailKit.Security.SecureSocketOptions]::StartTls, $False)
$SMTP.Authenticate('[email protected]', 'mypassword' )
$SMTP.Send($Message)
$SMTP.Disconnect($true)
$SMTP.Dispose()
The .NET MailKit library is incredibly useful, but many modern services now allow you to send mail through a REST API as seen in the following examples.
Microsoft Graph Email
The Microsoft Graph REST API is quickly becoming indispensable for Azure AD and Office 365 administrators. With that in mind, utilizing the sendMail
REST API method, we can quickly send a message using the Invoke-RestMethod
API.
You will need the
Mail.Send
permission to send the email.
There are a few prerequisites to authenticating to the Microsoft Graph API, which you can read about here. Once you have your authentication token and the correct permissions assigned, see below as to how you can send in Powershell email.
$Token = "tokencontent"
$Params = @{
"URI" = 'https://graph.microsoft.com/v1.0/me/sendMail'
"Headers" = @{
"Authorization" = ("Bearer {0}" -F $Token)
}
"Method" = "POST"
"ContentType" = 'application/json'
"Body" = (@{
"message" = @{
"subject" = 'This is a test message.'
"body" = @{
"contentType" = 'Text'
"content" = 'This is a test email'
}
"toRecipients" = @(
@{
"emailAddress" = @{
"address" = '[email protected]'
}
}
)
}
}) | ConvertTo-JSON -Depth 10
}
Invoke-RestMethod @Params
MailGun
Finally, let’s explore using another popular email service, MailGun. Using a simple API call, we can send an email through here as well. After retrieving the Private API Key from the MailGun Account Settings → API Keys section, you can use that to send to the domain you have configured.
$APIKey = 'key-asasdfd7as8fa8dfasdfasdff87sd8f8sa8sd'
$Params = @{
"URI" = 'https://api.mailgun.net/v3/mail.mydomain.com/messages'
"Form" = @{
"from" = '[email protected]'
"to" = '[email protected]'
"subject" = 'Test API Sent Email'
"text" = 'Test Body Text'
}
"Authentication" = 'Basic'
"Credential" = (New-Object System.Management.Automation.PSCredential ("api", ($APIKey | ConvertTo-SecureString -AsPlainText)))
"Method" = 'POST'
}
Invoke-RestMethod @Params
Conclusion
Creating Send-MailKitMessage
to remove the underlying .NET technology SmtpClient
from being used, allows for more secure PowerShell email sending. There are many easy to use and more powerful alternatives that can be made to work just as easily as the built-in functions. Depending on your needs and what your script or application may need to do, simply substitute for one of the many methods demonstrated above!