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:
- Support TLS/SSL authentication;
- Compose optional HTML body content;
- Load and attach multiple files to the message;
- Specify multiple
To
,CC
andBCC
address fields for recipients; - Specify optional
Reply-to
fields, which is different from theFrom
address.
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:
-
System.Net.Mail.SmtpClient
- Allows applications to send email by using the Simple Mail Transfer Protocol (SMTP); -
System.Net.Mail.MailAddress
- Represents the address of an electronic mail sender or recipient; -
System.Net.Mail.MailMessage
- Represents an email message that can be sent using theSmtpClient
class; -
System.Net.Mail.Attachment
- Represents an attachment to an email. -
System.Net.NetworkCredential
- Provides credentials for password-based authentication schemes.
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:
-
The utility functions
makeSMTPClient
andmakeMessage
construct theSmtpClient
and theMailMessage
objects respectively. -
The first
SmtpClient.Send
function invocation dispatches theMailMessage
object. -
The second
SmtpClient.Send
function invocation does not use aMailMessage
object for sending messages. In this case, we can only send plain text messages without attachments. -
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:
-
Hard coding user names and passwords in a script is not particular safe practice for a production environment. If you are planning to store user credentials in plain text format inside a script, check with your IT department's security policy first. These scripts should run on a locked-down server the very least.
-
Remember that email messages are potentially stored in plain text format by default on the recipient's mail server. You may need to depend on third-party cryptographic libraries to protect sensitive information in the message body.
-
Most email service providers, such as Google, implement aggressive anti-spamming measures. As a consequence, you might experience problems using their services. Don't try to spoof the
From
address, especially if you are authenticating and interfacing with Google's SMTP server. Otherwise, Gmail recipients will see your messages marked as either "spam" or "untrusted". -
Google may block any attempts of authentication schemes that relies on basic user name and password verification. You may need to configure your Gmail account to allow less secure apps [3]. Also note that connecting through a VPN could also cause Gmail to superfluously alert the account owner about suspicious activity.
-
Don't turn your PowerShell script into an emailing fire hose. Flooding SMTP servers with messages is a sure way for getting your email account suspended. Your email address (and potentially the entire domain in that address) will be also blacklisted by third-party email service providers.
-
Many countries now enforce regulations regarding unsolicited emails. Be aware of potential legal implications when sending messages to your recipients.
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
-
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 -
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 -
Let less secure apps use your account, Google, Date Retrieved 21 March 2018
https://support.google.com/accounts/answer/6010255?hl=en -
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 -
Send-MailMessage
, Microsoft Documentation, 2018, Date Retrieved 14 March 2018
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage -
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
- Complete Source Code - GitHub