DEAK Software

Using SMTP in PowerShell

Dominik Deák

1 Introduction

Sending emails in a command-line environment can be useful for monitoring scheduled tasks, generating server statistics reports, and even bridging communications between systems. Ideally, sending mail via SMTP (Simple Mail Transfer Protocol) [6] should be performed without installing a fully-fledged email client. Furthermore, we would also like to keep external library dependencies to a bare minimum, while having access to the following features:

This article will discuss some of these features in detail. Corresponding source code will be also made available in the Appendix section.

2 Emailing in PowerShell

2.1 Using Send-MailMessage

PowerShell started supporting email messaging since version 2.0 [5]. Composing and dispatching messages can be done with the Send-MailMessage command with minimal effort. In the example below, we are going send an email to a hypothetical recipient, located at the To address. The From address could be a real, or a "no-reply" address, but in this case we are going to use an address that corresponds to the SMTP server user name. Additionally, we are going to send two attachments, located at some path relative to our script file. Communications will be conducted over TLS or SSL encryption.

By default, PowerShell will use the SMTP server configuration set by the $PSEmailServer variable. If this variable is not set, one must supply the parameters -SmtpServer, -Port and -UseSsl directly to Send-MailMessage. If a custom SMTP server configuration is used, credentials must be also specified for authentication purposes.

# Outgoing email configuration used for sending messages
$outgoingUsername           = "sender.example@gmail.com"
$outgoingPassword           = "**********"
$outgoingServer             = "smtp.gmail.com"
$outgoingPortSMTP           = 587   # Normally 25 (not secure), 465 (SSL), or 587 (TLS)
$outgoingEnableSSL          = $true
$outgoingFromAddress        = $outgoingUsername
$outgoingToAddressList      = @( "recipient.example@gmail.com" )
$outgoingCCAddressList      = @()
$outgoingBCCAddressList     = @()
$outgoingAttachmentURLList  = @( "..\Attachments\File-01.pdf", "..\Attachments\File-02.svg" )

# Use Send-MailMessage command to send an email
Write-Host "Sending message 1..."

$messageSubject = "Test Subject 1"
$messageBody = "This is a test message 1."

$tempPassword = ConvertTo-SecureString -String $outgoingPassword -AsPlainText -Force
$credential = New-Object -TypeName "System.Management.Automation.PSCredential" `
   -ArgumentList $outgoingUsername, $tempPassword

Remove-Variable -Name tempPassword

$parameters = @{
   "From" = $outgoingFromAddress;
   "Subject" = $messageSubject;
   "Body" = $messageBody;
   "SmtpServer" = $outgoingServer;
   "Port" = $outgoingPortSMTP;
   "UseSsl" = $outgoingEnableSSL;
   "Credential" = $credential
   }

if ( $outgoingToAddressList )
   {
   $parameters.add( "To", $outgoingToAddressList )
   }

if ( $outgoingCCAddressList )
   {
   $parameters.add( "CC", $outgoingCCAddressList )
   }

if ( $outgoingBCCAddressList )
   {
   $parameters.add( "BCC", $outgoingBCCAddressList )
   }

if ( $outgoingAttachmentURLList )
   {
   $parameters.add( "Attachments", $outgoingAttachmentURLList )
   }

Send-MailMessage @parameters

2.2 Using .NET Frameworks

Prior version 2.0, PowerShell did not have built-in support for Send-MailMessage. However, we can lean on .NET Frameworks to extend PowerShell's functionality to support email messaging. Even if the Send-MailMessage command is available, sometimes interfacing with .NET Frameworks directly is more desirable (or even more efficient) when dealing with a large volume of emails. The problem with Send-MailMessage is the need for authenticating with the server for each message being sent, which is a performance bottleneck for sending a batch of messages to the same SMTP server.

We will be looking at a few .NET Framework classes in the System.Net.Mail and System.Net name spaces [1, 2]. For this tutorial, we will rely on the following classes:

2.2.1 Connecting to the SMTP Server

In order to communicate with the SMTP server, we need to construct a SmtpClient class, then initialise it with the following information: the SMTP server and port, credentials for authentication, and indicate whether to use TLS/SSL cryptographic protocols for communication. We can encapsulate all of those requirements into a convenient utility function, called makeSMTPClient:

function makeSMTPClient
   {
   Param
      (
      [string] $server,
      [int] $port,
      [bool] $enableSSL,
      [string] $username,
      [string] $password
      )

   $smtpClient = New-Object Net.Mail.SmtpClient( $server, $port )
   $smtpClient.enableSSL = $enableSSL

   $smtpClient.credentials = New-Object System.Net.NetworkCredential( $username, $password )

   return $smtpClient
   }

These days, almost all SMTP servers expect the connection to be secured with cryptographic protocols, such as Transport Layer Security (TLS), or Secure Sockets Layer (SSL). If encryption is enabled, the server and the client will negotiate which cryptographic scheme to use. Typically, SMTP servers open dedicated ports for each scheme, as shown in the table below. However, always check what ports are available with your email service provider.

Cryptographic Protocol $port $enableSSL
None 25 $false
Secure Sockets Layer (SSL) 465 $true
Transport Layer Security (TLS) 587 $true

Clients must also authenticate with the server before sending emails. Therefore, we need to supply a user name and a password to the SmtpClient instance. We can provide those credentials with the help of the NetworkCredential class, as shown in the makeSMTPClient function above.

Once a connection is established with the SMTP server, the SmtpClient instance can be reused to send multiple messages in a single batch operation. Note that server authentication is an expensive operation. Don't try to re-establish a connection for each message when sending a large volume of emails to the same SMTP server.

2.2.2 Creating an Email Message

The following code snippet demonstrates how to compose a message. The makeMessage utility function essentially constructs a MailMessage class, then initialises it with the required address fields, the message subject and its body. This utility function takes a lot of arguments, because email messages support many types of address fields. Typical production code might not need all those address fields, so there is plenty of room for simplification, if needed.

function makeMessage
   {
   Param
      (
      [string] $fromAddress,
      [string[]] $replyToAddressList,
      [string[]] $toAddressList,
      [string[]] $ccAddressList,
      [string[]] $bccAddressList,
      [string[]] $attachmentURLList,
      [string] $subject,
      [string] $body,
      [bool] $isHTML
      )

   $message = New-Object Net.Mail.MailMessage

   $message.from = $fromAddress

   foreach ( $replyToAddress in $replyToAddressList )
      {
      $message.replyToList.add( $replyToAddress )
      }

   foreach ( $toAddress in $toAddressList )
      {
      $message.to.add( $toAddress )
      }

   foreach ( $ccAddress in $ccAddressList )
      {
      $message.cc.add( $ccAddress )
      }

   foreach ( $bccAddress in $bccAddressList )
      {
      $message.bcc.add( $bccAddress )
      }

   foreach ( $attachmentURL in $attachmentURLList )
      {
      # Resolve relative attachment paths to absolute paths
      $attachmentURL = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $attachmentURL )

      $message.attachments.add( $attachmentURL )
      }

   $message.subject = $subject
   $message.body = $body
   $message.isBodyHTML = $isHTML

   return $message
   }

Specifying multiple Reply-to, CC, and BCC addresses is completely optional. Leave them empty if these address fields are not needed. However, specify at least one To address. The From address is not mandatory, but it's good practice to specify one. Note that some SMTP servers expect the From address to correspond with the user name credentials supplied for SmtpClient authentication.

Attaching external documents to the message is quite straightforward. The MailMessage class maintains an attachment collection, which can be accessed through the MailMessage.Attachments property. New Attachment objects can be created by adding the file URLs to the collection. Each Attachment constructor will load the file and (in most cases) deduce its MIME content type automatically.

2.2.3 Sending Messages

In the example below, we are going to use a hypothetical Gmail account (sender.example@gmail.com) to send a message. The message will be dispatched to a hypothetical recipient, located at the To address; and we also want the recipient to respond using our hypothetical Reply-to address. The From address is the same as the Gmail user name. Additionally, we are going to send two attachments, located at some path relative to our script file. Communications between our script and the server will be conducted over TLS encryption.

# Outgoing email configuration used for sending messages
$outgoingUsername           = "sender.example@gmail.com"
$outgoingPassword           = "**********"
$outgoingServer             = "smtp.gmail.com"
$outgoingPortSMTP           = 587   # Normally 25 (not secure), 465 (SSL), or 587 (TLS)
$outgoingEnableSSL          = $true
$outgoingFromAddress        = $outgoingUsername
$outgoingReplyToAddressList = @( "replyto.example@gmail.com" )
$outgoingToAddressList      = @( "recipient.example@gmail.com" )
$outgoingCCAddressList      = @()
$outgoingBCCAddressList     = @()
$outgoingAttachmentURLList  = @( "..\Attachments\File-01.pdf", "..\Attachments\File-02.svg" )

# Use .NET Frameworks to send emails
Write-Host "Connecting to SMTP server: $outgoingServer`:$outgoingPortSMTP"

$smtpClient = makeSMTPClient `
   $outgoingServer $outgoingPortSMTP $outgoingEnableSSL `
   $outgoingUsername $outgoingPassword

Remove-Variable -Name outgoingPassword

$outgoingMessage = makeMessage `
   $outgoingFromAddress $outgoingReplyToAddressList `
   $outgoingToAddressList $outgoingCCAddressList `
   $outgoingBCCAddressList $outgoingAttachmentURLList `
   "Test Subject 2" "This is test message 2." $false

try {
   Write-Host "Sending message 2..."

   $smtpClient.send( $outgoingMessage )

   Write-Host "Sending message 3..."

   $smtpClient.send(
      $outgoingFromAddress, $outgoingToAddressList[0],
      "Test Subject 3", "This is test message 3." )
   }

catch { Write-Error "Caught SMTP client exception:`n`t$PSItem" }

Write-Host "Disconnecting from SMTP server: $outgoingServer`:$outgoingPortSMTP"

$smtpClient.dispose()

Remove-Variable -Name smtpClient

The code above performs the following:

  1. The utility functions makeSMTPClient and makeMessage construct the SmtpClient and the MailMessage objects respectively.

  2. The first SmtpClient.Send function invocation dispatches the MailMessage object.

  3. The second SmtpClient.Send function invocation does not use a MailMessage object for sending messages. In this case, we can only send plain text messages without attachments.

  4. We then clean up and disconnect from the SMTP server.

Note that SmtpClient will typically delay the initial connection with the server until SmtpClient.Send was invoked. There is a distinct possibility that a connection may not succeed, which in that case SmtpClient will throw an exception. Your code should handle such exceptions gracefully.

Also worth noting that all SmtpClient.Send commands are synchronous in nature. This means that the calling thread (i.e. the PowerShell script) will be forced to wait until SmtpClient.Send completed the operation. If this is unacceptable, one can use asynchronous commands instead, such as SmtpClient.SendAsync and SmtpClient.SendMailAsync. See .NET Framework documentation for details [4].

As a final note, SmtpClient.Dispose will force a disconnection with the server. Forcing a disconnection is usually not needed unless the same variable is reused for connecting to a different server. It's also good practice to delete sensitive data with the Remove-Variable command.

2.3 Notes and Caveats

SMTP is quite easy to abuse and scripting in PowerShell also has its own perils. Therefore, we need to be aware of the following issues:

3 Conclusion

Sending messages over SMTP is actually quite straightforward, as demonstrated in this article. Microsoft provides convenient .NET Frameworks that PowerShell can take advantage of with minimal code. This makes script-based development very powerful and extensible. However, with great power comes great responsibility. Use this knowledge for the greater good. Aim for solving real-world problems and automation in business. Implement features that create a net benefit for you and for your clients' experience.

References

  1. System.Net.Mail Namespace, Microsoft Developer Network, 2018, Date Retrieved 14 March 2018
    https://msdn.microsoft.com/en-us/library/system.net.mail(v=vs.110).aspx

  2. System.Net Namespace, Microsoft Developer Network, 2018, Date Retrieved 14 March 2018
    https://msdn.microsoft.com/en-us/library/system.net(v=vs.110).aspx

  3. Let less secure apps use your account, Google, Date Retrieved 21 March 2018
    https://support.google.com/accounts/answer/6010255?hl=en

  4. SmtpClient Class, Microsoft Developer Network, 2018, Date Retrieved 14 March 2018
    https://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient(v=vs.110).aspx

  5. Send-MailMessage, Microsoft Documentation, 2018, Date Retrieved 14 March 2018
    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage

  6. Jonathan B. Postel, Simple Mail Transfer Protocol, RFC-821, Information Sciences Institute, University of Southern California, August 1982, Date Retrieved 14 March 2018
    https://tools.ietf.org/html/rfc821/

Appendix

Source Code