Create an Opt-Out Tool for Exchange Online

Earlier this week, a question popped up on a distribution list for managing an opt-out process with Exchange Online.  That wasn't the first request I've seen for such a tool (in fact, I had my own customer asking for something similar).  Of course, it's much more fun to solve someone else's problems than your own, so I dove right in.

Let's imagine the scenario: we have a mailing list that we're going to populate with email addresses, and it's going to periodically send out from an Exchange Online mailbox. From time to time, recipients may choose to stop receiving messages and you want an automated way to process those.  While we're not really fans of using Exchange Online to perform bulk mailing, I'm not going to be the one to tell you how to use your subscription.

The pieces that we're going to configure:

  1. An Exchange Online mailbox to receive the notifications.  Let's call it <unsubscribe@contoso.com.>
  2. A distribution group called do-not-send@contoso.com.
  3. A service account. This can be the unsubscribe mailbox. It will need certain permissions in order to function correctly.
  4. An Exchange Online Transport Rule (ETR).
  5. A script similar to the one I provide.

Exchange Online Mailbox

Honestly, I hope this part is pretty self-explanatory.  Create a mailbox that will be the recipient of the opt-out messages.  For purposes of the script, it can't be a shared mailbox, since we will need to log on to it directly. In a future post, I will revisit this topic for processing a mailbox using impersonation.

  1. From Exchange Management Shell:
    New-RemoteMailbox -Name 'Unsubscribe' -UserPrincipalName 'unsubscribe@contoso.com'

Distribution Group

  1. From Exchange Management Shell:
    New-DistributionGroup -Name 'Do Not Send' -PrimarySmtpAddress 'do-not-send@contoso.com'

Service Account

The script will need to run as a user account.  It can be a user account that already exists in your organization or you can use the unsubscribe user account.  It will need to have "Account Operator" permissions in Active Directory or be delegated permissions to manage the distribution group.

Exchange Online Transport Rule

Now that you have a mailbox that you can send to, a distribution group of people who have chosen to opt out, and an account to run it all, you need to rule to handle the processing.  Something like this should do the trick:

  1. From the Exchange Management Shell:
    New-TransportRule -Name “Delete messages sent to do-not-send list” -DeleteMessage:$True -SentToMemberOf do-not-send@contoso.com

Script

In order to achieve the "unsubscribe" effect, we're going to run a script to add users to a group (that gets synchronized to Exchange Online via AADConnect) that has an ETR that drops messages matching the recipients of said group.  The script will connect to the "unsubscribe" mailbox, download the messages, and then loop through them, checking for your key unsubscribe words, log the matches, and then delete the original message.   The -ArgumentList 10 parameter will just grab the firs 10 messages. You may want to adjust this based on how active your mailbox is or any other number of factors.

 <#
.SYNOPSIS
Sample Office 365 Opt-Out mailbox processing
.DESCRIPTION
Read an Office 365 mailbox and exectute a script based on an email
#>
# Locating EWS Managed API and loading
Write-Host -Fore Yellow "Locating EWS installation ..."
If (Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll')
 {
  Write-Host -ForegroundColor Green "Found Exchange Web Services DLL."
  $WebServicesDLL = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
  Import-Module $WebServicesDLL
 }
ElseIf
 ( $filename = Get-ChildItem 'C:\Program Files' -Recurse -ea silentlycontinue | where { $_.name -eq 'Microsoft.Exchange.WebServices.dll' })
 {
  Write-Host -ForegroundColor Green "Found Exchange Web Services DLL at $($filename.FullName)."
  $WebServicesDLL = $filename.FullName
  Import-Module $WebServicesDLL
 }
ElseIf
 (!(Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll'))
 {
  Write-Host -ForegroundColor Yellow "This requires the Exchange Web Services Managed API. Attempting to download and install."
  wget https://download.microsoft.com/download/8/9/9/899EEF2C-55ED-4C66-9613-EE808FCF861C/EwsManagedApi.msi -OutFile ./EwsManagedApi.msi
  msiexec /i EwsManagedApi.msi /qb
  Sleep 60
  If (Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll')
  {
   Write-Host -ForegroundColor Green "Found Exchange Web Services DLL."
   $WebServicesDLL = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
   Import-Module $WebServicesDLL
  }
  Else
   { 
    Write-Host -ForegroundColor Red "Please download the Exchange Web Services API and try again."
    Break
   }
 }
Function ConnectToExchangeOnPrem()
 {
 $ExOnPremSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://$ExchangeServer/PowerShell/ -Authentication Kerberos
 Import-PSSession $ExOnPremSession -AllowClobber -ErrorAction SilentlyContinue
 } # End ConnectToExchangeOnPrem Function
$user = "unsubscribe@contoso.com"
$userPass = "Password123"
$SuccessLogFile = "Success.txt"
$ExchangeServer = "myonpremExchangeServer"
$ErrorLogFile = "Error.txt"
$OptOutGroup = "testgroup1"
$securePassword = ConvertTo-SecureString $userPass -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $user,$securePassword
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1 
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
$creds = New-Object System.Net.NetworkCredential($user,$userPass)
$Service.Credentials = $creds
$Service.AutodiscoverUrl($user, {$true})
$results = $Service.FindItems("Inbox",( New-Object Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList 10 ))
ConnectToExchangeOnPrem
$DistributionGroupMembers = Get-DistributionGroupMembers $OptOutGroup
$results.Items | ForEach-Object {
    $Subject = $_.Subject
    $SenderAddress = ($_.From).Address
    $SenderName = ($_.From).Name
    Write-Host -NoNewline "Subject   : ";Write-Host -Fore Green $Subject
    Write-Host -NoNewline "Sender    : ";Write-Host -Fore Green $SenderAddress
    Write-Host -NoNewLine "SenderName: ";Write-host -fore Cyan $SenderName
    If ($Subject -like "*remove*")
        {
        If (Get-MailContact $SenderAdddress -ea SilentlyContinue)
            {
            If ($DistributionGroupMembers.EmailAddresses.AddressString -match $SenderAddress)
                {
                "$(Sender) already member of group."
                }
            Else
                {
                Set-MailContact -Identity $SenderAddress -HiddenFromAddressListsEnabled $true
                Add-DistributionGroupMember -Identity $OptOutGroup -Member $SenderAddress
                $Data = """" + $SenderAddress + """" + "," + """" +"Added to $($OptOutGroup)" + """"
                $Data | Out-File $SuccessLogFile -Append
                $Data = $null
                }
            }
        Else
            {
            New-MailContact -Name $SenderName -ExternalEmailAddress $SenderAddress
            Set-MailContact -Identity $SenderAddress -HiddenFromAddressListsEnabled $true
            Add-DistributionGroupMember -Identity $OptOutGroup -Member $SenderAddress
            $Data = """" + $SenderAddress + """" + "," + """" +"Added to $($OptOutGroup)" + """"
            $Data | Out-File $SuccessLogFile -Append
            $Data = $null
            }
        Write-Host -Fore Green "Deleting message $($Subject) from user $($SenderAddress)."
        $_.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
        }
    Else
        {
        $Data = """" + $SenderAddress + """" + "," + """" +"did not match criteria to be added to $($OptOutGroup)" + """"
        $Data | Out-File $ErrorLogFile -Appen
        Write-Host -Fore Green "Deleting non-matching message $($Subject) from user $($SenderAddress)."
        $_.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
        }
    }