How to Send Email Securely with PowerShell

Adam Listek

Adam Listek

Read more posts by this author.

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'
Installing MailKit & MimeKit
Installing MailKit & MimeKit

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()

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()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 of 587
  • 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!

Subscribe to Adam the Automator

Get the latest posts delivered right to your inbox

Looks like you're offline!