2
votes

I have a PowerShell script that generates emails from a list of contacts imported from a CSV file:

# Get Credential
$Credential = & "C:\Powershell Scripts\Windows\Get-CredentialFromWindowsCredentialManager.ps1" ms.outlook.15:[email protected]

# Get Contacts
$contacts = Import-Csv -Path "C:\Powershell Scripts\Email\Contacts.csv"

# Compose Email for each contact
foreach( $contact in $contacts )
{
    Write-Output "Creating email for: $($contact.FirstName) $($contact.LastName)"
    $To = "$($contact.FirstName) $($contact.LastName) <$($contact.Email)>"
    $From = "My Email <[email protected]>"
    $Subject = "$($contact.FirstName), I have a suggestion for you!"
    $Body = "<html></html>"
    $SMTPServer = "smtp.office365.com"
    $Port = 587
    Send-MailMessage -To $To -From $From -Subject $Subject -SmtpServer $SMTPServer -Credential $Credential -UseSsl -Body $Body -BodyAsHtml -Port $Port

    # Due to the Message Send rate limit (30 per minute) I added this to slow the rate down
    Start-Sleep -Seconds 10
}

Every 10 minutes I get the following SMTP Exception:

Send-MailMessage : Service not available, closing transmission channel. The
server response was: 4.4.1 Connection timed out. Total session duration:
00:10:08.3716645
At C:\Powershell Scripts\Email\SendEmail.ps1:17 char:2
+     Send-MailMessage -To $To -From $From -Subject $Subject -SmtpServer $SMTPServer  ...
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpException
    + FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage

Are there any settings that I can modify or changes in code that will prevent this?

1
What did you research?Jeroen Heier
I'm only sending out about 100-200 emails (well under the limits for the Office 365 E1 plan outlined in the following article). technet.microsoft.com/en-us/library/exchange-online-limits.aspxEdC

1 Answers

2
votes

Don't take this personally, this is just a general rant, but you've got a script that performs an action against a resource that is out of your control. As such, you can't just expect an SMTP connection to succeed and wonder what you should do to prevent it from failing. Deep breath. The answer is to consider edge cases and actually cater for them in your code. Granted, you've got a sleep in there to try to not fall foul of rate limiting, but that's not a robust solution. In this instance, a simple exception handler around the Send-MailMessage call would suffice. You could include a number of retries and small sleep delays.

A "maximum number of acceptable failures" threshold could be used to break out of the retry loop and cause some kind of inward alerting, Etc.

Long story short, don't just throw spaghetti at the wall with your eyes closed.

</rant>

An example, but not necessarily the tidiest solution:

[Int32]   $maxAttempts       = 5;
[Int32]   $failureDelay      = 2;
[Int32]   $interMessageDelay = 10;
[Int32]   $numAttempts       = 0;  
[Boolean] $messageSent       = $false;

:CONTACT foreach ( $contact in $contacts ) {
    $numAttempts = 1;
    $messageSent = $false;
    while ( ($numAttempts -le $maxAttempts) -and (! $messageSent) ) {
        try {
            Write-Host -Object ( 'Sending message, attempt #{0} of #{1}...' -f $numAttempts, $maxAttempts );
            Send-MailMessage <blah>;
            Write-Host -Object "Ok.`n";
            $messageSent = $true;
            } #try
        catch [System.Exception] {
            # ^^^^^ This exception type needs changing, but I don't know the full
            # type of your failure.
            Write-Host -Object 'Failed.';
            if ( $numAttempts -ge $maxAttempts ) {
                Write-Host -Object "ERROR : Maximum attempts reached - aborting.`n";
                continue CONTACT;
            } else {
                Write-Host -Object ( 'Sleeping for {0} second(s)...' -f $failureDelay );
                Start-Sleep -Seconds $failureDelay;
                $numAttempts++;
            } #else-if
            } #catch
        } #while
    Write-Host -Object ( 'Sleeping for {0} second(s)...' -f $interMessageDelay );
    Start-Sleep -Seconds $interMessageDelay;
    } #foreach