cn198 Posted May 9, 2022 Posted May 9, 2022 Hi all, Not sure if this is the right place to post about this, but we have a custom built email program that send emails using SMTP, with an Outlook account. This all works great, but with sending lots of emails, the sent items of the Outlook account can fill up the storage quota (a lot of the emails we send have large attachments). So what I'd like to do is have some sort of policy set on the account that any large emails (say >200KB) in the sent items get deleted after 6 months. We have an A1 license for Office (free for education) so I think our options may be limited. But I've looked into having retention policies on the sent items of the Outlook account, and auto applying retention labels using specific keyword searches looks like the way to go to do this. Does anyone know if this is possible with an A1 license? Our network manager isn't sure if this is possible with the A1 license, and I'm just looking for some guidance. Thanks! Chris
chaplic Posted May 9, 2022 Posted May 9, 2022 Overall, don't do this through O365. Using O365 as a SMTP relay is fine if you are sending the odd scan-to-email but if it is the volume you say its going to be painful. Sendgrid costs peanuts and is designed for ht ejob. Health warning over.. Fairly sure enhanced stuff like that needs a A/E 3 I've got a PS script that is designed to call graph api to delete email, you could adapt that, or even a simple powershell job to search-mailbox and delete content 1
HPlum78 Posted May 9, 2022 Posted May 9, 2022 Or if you really don't want to pay set up an SMTP box on prem.... oh and don't use the Windows SMTP service as this is being disbanded. https://www.hmailserver.com/
cn198 Posted May 9, 2022 Author Posted May 9, 2022 Thanks for the reply. I'd certainly echo that O365 is not suited for high volume emails. But being an educational environment every penny counts, and it's free. But we're not really sending high volume emails, our custom based email system downloads pupil/parental information directly from SIMS, and then we can pick and choose who to email based the email requirements. It means we can send highly targeted emails so that parents only receive essential information that's relevant to them. So whilst it's certainly not a speedy system to send email, it's definitely effective.As for the license, it's pretty much what I was fearing. But the idea of doing it via code or powershell is interesting, and could definitely work. Do you have a good resource to point me in the direction of for doing it via powershell? Really appreciate you help!
chaplic Posted May 10, 2022 Posted May 10, 2022 (edited) Here's my code - note it's set to delete ALL email (not calendar, tasks etc) so would need a bit of work. Its a fair bit more involved than I recall and if you want to scope it to sent items only it would need modifying You would need to create an app registration with mail.readwrite.all (then I would scope it down to the mailbox in question) Limiting application permissions to specific Exchange Online mailboxes - Microsoft Graph | Microsoft Docs if you are going to try this please do it cautiously in a test tenant... $ClientID = "blah" $AppSecret = "sshhh" $loginURL = 'https://login.microsoftonline.com' $TenantName = "yourtenant.onmicrosoft.com" $resource = 'https://graph.microsoft.com' $Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" $Scope = 'https://graph.microsoft.com/.default' $user='[email protected]' $start=(get-date).AddDays(-7).ToString('yyyy-MM-ddT00:00:01.00Z') $finish=(get-date).AddDays(-1).ToString('yyyy-MM-ddT23:59:59.00Z') $num=0 $datestamp=(get-date).AddMinutes(-10).ToString('yyyyMMddHHmmss') $LogPath="." $logname= $($LogPath + "" + $datestamp + "-ECC.txt") # Get an Oauth 2 access token based on client id, secret and tenant domain $body = @{grant_type="client_credentials";resource=$resource;client_id=$ClientID;client_secret=$AppSecret} #write-host "$loginURL/$tenantname/oauth2/token" $oauth = Invoke-RestMethod -Method Post -Uri "$loginURL/$tenantname/oauth2/token" -Body $body $2daysago = "{0:s}" -f (get-date).AddDays(-2) + "Z" # or, AddMinutes(-5) #Write-Output $2daysago function LogIt { param ($Message) $tim=get-date -Format HH:mm:ss Add-Content $logname "$tim`t$Message" } # Add System.Web for urlencode Add-Type -AssemblyName System.Web Add-Type -AssemblyName System.Drawing # Create body $Body = @{ client_id = $clientID client_secret = $AppSecret scope = $Scope grant_type = 'client_credentials' } $body $PostSplat = @{ ContentType = 'application/x-www-form-urlencoded' Method = 'POST' Body = $Body Uri = $Url } function deleteFolders { param ($uri) logit "INFO`tCalling delete $uri with $uri" $SomeInfo = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Delete -ContentType "application/json" -SkipHttpErrorCheck -StatusCodeVariable scv $scv } function deleteEmail { param ($uri) logit "INFO`tCalling delete $uri" $SomeInfo = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Delete -ContentType "application/json" $scv } function get_All_Values_From_GraphAPI { #Where a response will run the risk of paging, follow all pages and return an array param ($uri,$method) $header logit "INFO`tCalling $uri with method $method " $Allinfo=@() # Get the first X amount of detail try { $SomeInfo = Invoke-RestMethod -Uri $Uri -Headers $Header -Method $method -ContentType "application/json" $someinfo } catch { logit("WARNING`tCall Failed with $($error[0]). Retrying in 5 seconds...") start-sleep -seconds 5 if ($error[0] -like "*Access token has expired*") { logit("INFO`tRequesting New Acces Token") # Request the token $global:Request = Invoke-RestMethod @PostSplat $global:Header = @{ Authorization = "$($Request.token_type) $($Request.access_token)" } } try { $SomeInfo = Invoke-RestMethod -Uri $Uri -Headers $Header -Method $method -ContentType "application/json" } catch { logit("WARNING `tCall Failed with $($error[0]). Retrying in 30 seconds...") if ($error[0] -like "*Access token has expired*") { logit("INFO`tRequesting New Acces Token") # Request the token $global:Request = Invoke-RestMethod @PostSplat $global:Header = @{ Authorization = "$($Request.token_type) $($Request.access_token)" } } start-sleep -seconds 30 try { $SomeInfo = Invoke-RestMethod -Uri $Uri -Headers $Header -Method $method -ContentType "application/json" } catch { logit("ERROR `tCall Failed with $($error[0]). Giving Up..") $WeHaveAnError=$True } } } $AllInfo+=$SomeInfo.value # Keep following paging links until all content is returned while($someInfo.'@odata.nextLink' -ne $null) { try { $SomeInfo=Invoke-RestMethod -Uri $SomeInfo.'@odata.nextLink' -Headers $Header -Method $method -ContentType "application/json" } catch { logit("WARNING`tCall Failed with $($error[0]). Retrying in 5 seconds...") start-sleep -seconds 5 if ($error[0] -like "*Access token has expired*") { logit("INFO`tRequesting New Acces Token") # Request the token $global:Request = Invoke-RestMethod @PostSplat $global:Header = @{ Authorization = "$($Request.token_type) $($Request.access_token)"} } try { $SomeInfo = Invoke-RestMethod -Uri $Uri -Headers $Header -Method $method -ContentType "application/json" } catch { logit("WARNING `tCall Failed with $($error[0]). Retrying in 30 seconds...") if ($error[0] -like "*Access token has expired*") { logit("INFO`tRequesting New Acces Token") # Request the token $global:Request = Invoke-RestMethod @PostSplat $global:Header = @{ Authorization = "$($Request.token_type) $($Request.access_token)" } } start-sleep -seconds 30 try { $SomeInfo = Invoke-RestMethod -Uri $Uri -Headers $Header -Method $method -ContentType "application/json" } catch { logit("ERROR `tCall Failed with $($error[0]). Giving Up..") $WeHaveAnError=$True } } } $AllInfo+=$SomeInfo.value } return ($AllInfo) } # Request the token $global:Request = Invoke-RestMethod @PostSplat $global:Header = @{ Authorization = "$($Request.token_type) $($Request.access_token)" } $queryurl=$('https://graph.microsoft.com/beta/users/'+ $user + '/mailFolders') $myReport=get_All_Values_From_GraphAPI $queryurl Get logit "Deleting other folders other than well known" for ($num=2; $num -lt $myreport.length; $num++) { if ($myreport[$num].WellKnownName -eq $null -and $myreport[$num].displayName -ne 'RSS Feeds' ) { $id=$myreport[$num].id $displayName=$myreport[$num].displayName write-host "Deleting $Displayname" $queryurl=$('https://graph.microsoft.com/beta/users/' + $user + '/mailFolders/' + $id) write-host "query is $queryurl" $myDelete=deleteFolders $queryurl if ($myDelete -eq '204') { write-host -ForegroundColor Green "`tOK (http $mydelete)" } elseif ($mydelete -eq '404') { write-host -ForegroundColor Magenta "`tNot found probably deleted (http $mydelete) " } else { write-host -ForegroundColor red "`tSomething gone pete tong (http $mydelete)" } } } # Now get all remaining messages $queryurl=$('https://graph.microsoft.com/beta/users/'+ $user + '/messages?$top=999&$select=subject') $remainingemails=get_All_Values_From_GraphAPI $queryurl Get logit "Deleting $($remainingemails.length) Emails" for ($num=2; $num -lt $remainingemails.length; $num++) { write-host "Deleting email $($remainingemails[$num].subject) $($remainingemails[$num].id)" $queryurl=$('https://graph.microsoft.com/beta/users/'+ $user + '/messages/' + $remainingemails[$num].id) DeleteEmail $queryurl } logit "Deletion Complete" Edited May 10, 2022 by chaplic 1
cn198 Posted May 10, 2022 Author Posted May 10, 2022 Thanks so much for that, I shall digest and see if I can cobble something together. I have a test account I can play on so no harm can be done. Thanks for your help!
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now