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:
- An Exchange Online mailbox to receive the notifications. Let's call it <unsubscribe@contoso.com.>
- A distribution group called do-not-send@contoso.com.
- A service account. This can be the unsubscribe mailbox. It will need certain permissions in order to function correctly.
- An Exchange Online Transport Rule (ETR).
- 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.
- From Exchange Management Shell:
New-RemoteMailbox -Name 'Unsubscribe' -UserPrincipalName 'unsubscribe@contoso.com'
Distribution Group
- 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:
- 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)
}
}
Comments
- Anonymous
January 19, 2017
I am trying to get this to work. I have it where the script will change groups but eveyone in the group still gets the messages except for me.- Anonymous
January 23, 2017
Are the users you are attempting to add to the group internal / tenant Exchange users or people external to your organization? - Anonymous
August 21, 2018
Hi Clandis, will this work with external users?
- Anonymous
- Anonymous
July 19, 2017
I am looking at doing something similar at our organization, but wit on-premise Exchange 2016. All the steps would be the same, save for the script. Obviously, it would need some modification and I'm assuming the paths would just need to be changed pointing to the relevant Exchange resources. Am I thinking correctly on this?Thanks!- Anonymous
August 01, 2017
Yes. Just specify the correct on-prem endpoint. You'll probably need to change the auth type to Kerb instead of Basic.
- Anonymous