Get Inumbo message tracking records with PowerShell

Inumbo is a cloud-based mail hygiene solution.  Whether using their free or paid subscription, you can search the message tracking log in the web portal to see a history of messages being processed for your subscription.  The main thing I use it for is to know if I haven’t received a message because it was marked as spam.  But rather than use the web portal, you can also use their REST API to programmatically get tracking records.  So instead of logging into the portal and searching, you just run a script and perform filtered searches and also get more information that what is exposed in the portal.

You need to get a read-only or read/write API key since that is what is used to authenticate the request.  You don’t have to use any search restrictions, and my script doesn’t require any either, but usually you’ll want to narrow it down to a time frame, sender, recipient, or action performed (delivered, rejected, etc.)  I have included these common search parameters, plus subject and sender IP.  Subject is a substring filter, and the start date and end date parameters are not mutually exclusive.

Speaking of dates, the API requires the format to be in epoch time (number of seconds since 1/1/1970, aka UNIX time).  To convert a .NET DateTime object to UNIX time, I use a method that was introduced in .NET 4.6.  If you aren’t using 4.6+, you can modify the Get-UnixTime function (at line 61) to calculate it using a time span, and there is link in the script for how to do that.  Furthermore, the script will account for time zone in the request and the response, so you can use “3/23/16 7:00 AM,” “3/23/16 7:00 AM -0700,” or “3/23/16 2:00 PM UTC” and get the same results, and the time stamp for each result will be in local time.

There are a number of properties for a record, so I only keep the ones that are relevant for normal queries.  These are time stamp, action, sender IP, sender, recipient, subject, RPD (anti-spam) score, SpamAssassin score, and description (why it was rejected or next hop information).  That’s still nine properties, too many to display in a table and see enough of the values to be meaningful, and I want the default output to be as a table.  So I specified the default properties to return five of the nine in a table.

How did I do that?  I created a custom formatting file (included in the download).  The file specifies that, for the custom type I assign to the record object, the default view is a table with five properties and specific widths for the columns, except for the subject which will fill the rest of the width.  To use the file, you need to run Update-FormatData .\TrackingInfo.format.psx1.  You will need to this once in each time you open a new shell.  You can add the command to your profile or even add the line to the script.  If you don’t use the formatting file, I still set a default property set in the script so the five properties are displayed, but the default will be in a list.  You, of course, can use standard format and export cmdlets to choose the properties displayed and how they are displayed.  So, if you want to see all properties, pipeline the results to, for example, Format-List -Property *.

The script’s code can be expanded and seen below, but you can download the script and the formatting file in the below attachment.

  Get-InumboTracking.zip (2.9 KiB)

<#
	.Synopsis
		Retrieve message tracking information from Inumbo
	.Description
		Get message tracking details for messages from Inumbo, optionally filtering on
		sender, recipient, date range, IP, subject, and action.
	.Parameter Action
		Restrict search results to valid action: quarantine, deliver, delete, bounce, reject, defer, or error.
	.Parameter Sender
		Sender email address to restrict search results.
	.Parameter Recipient
		Recipient email address to restrict search results.
	.Parameter Subject
		Substring of subject to restrict search results.
	.Parameter SenderIP
		IP address of sending server to restrict search results
	.Parameter StartDate
		Date and time to restrict the search results to messages with a timestamp
		after the date provided. Any DateTime object, or string that can be converted
		to DateTime, can be used and time zone will be accounted for. It can be used
		with or without EndDate.
	.Parameter EndDate
		Date and time to restrict the search results to messages with a timestamp
		before the date provided. Any DateTime object, or string that can be converted
		to DateTime, can be used and time zone will be accounted for. It can be used
		with or without StartDate.
	.Parameter ResultSize
		Maximum number of records to return.  There is no default other than any
		limit that Inumbo may enforce.  If a number larger than 50 is specified, the
		result size will be the number of available records if the total is within
		the next higher multiple of 50, or the next higher multiple of 50 if there
		are more records.  For example, if the ResultSize parameter is 65 and there
		are 80 records, the result size will be 80; if there are 120 records, the
		result size will be 100.
	.Example
		Get-InumboTracking.ps1 -StartDate "3/21/16 7:00 AM"
	.Example
		Get-InumboTracking.ps1 -Sender johndoe@company.com -EndDate "3/18/16 9:00 AM -0700"
	.Notes
		Version: 1.0
		Date: 3/21/16
#>

Param (
	[ValidateSet('QUARANTINE', 'DELIVER', 'DELETE', 'BOUNCE', 'REJECT', 'DEFER', 'ERROR')][string]$Action,
	[ValidateScript({$_ -as [System.Net.Mail.MailAddress]})][string]$Sender,
	[ValidateScript({$_ -as [System.Net.Mail.MailAddress]})][string]$Recipient,
	[string]$Subject,
	[ValidateScript({$_ -as [System.Net.IPAddress]})][string]$SenderIP,
	[DateTime]$StartDate,
	[DateTime]$EndDate,
	[int]$ResultSize
	)

#Put your Inumbo read-only or read/write key below
$apiKey = '123456789abcdefghijklmnopqrst'

#This function requires .NET 4.6
#If using a lower version, the function can be modified to use a timespan to make the calculation
#http://stackoverflow.com/questions/4192971/in-powershell-how-do-i-convert-datetime-to-unix-time
function Get-UnixTime ($date)
	{
	$dateOffset = New-Object -TypeName System.DateTimeOffset($date)
	$dateOffset.ToUnixTimeSeconds()
	}

function Get-RPDScoreTranslation ($score)
	{
	#RPD is CYREN's Recurrent Pattern Detection anti-spam engine
	if (-not($score))
		{return $null}
	switch ([int]$score)
		{
		0	{$return = 'Unknown'}
		10 	{$return = 'Suspect'}
		40 	{$return = 'ValidBulk'}
		50 	{$return = 'Bulk'}
		100 	{$return = 'Spam'}
		}
	$return
	}

#Convert API key to credential object to send as a password
$credential = New-Object -TypeName Management.Automation.PSCredential('api',(ConvertTo-SecureString $apiKey -AsPlainText -Force))
$url = 'https://api.inumbo.com/v1/tracking'

#Build query
if ($Action -or $Sender -or $Recipient -or $SenderIP -or $Subject -or $StartDate -or $EndDate)
	{
	$queryString = '&query='
	if ($Action)
		{
		$queryString += 'action=' + $Action.ToUpper() + ' '
		}
	if ($Sender)
		{
		$queryString += "from=$Sender "
		}
	if ($Recipient)
		{
		$queryString += "to=$Recipient "
		}
	if ($SenderIP)	
		{
		$queryString += "ip=$SenderIP "
		}
	if ($Subject)
		{
		$queryString += "subject~$Subject "
		}
	if ($StartDate)
		{
		#Convert date to Unix time format needed by API
		$start = Get-UnixTime $StartDate
		$queryString += "time>$start "
		}
	if ($EndDate)
		{
		#Convert date to Unix time format needed by API
		$end = Get-UnixTime $EndDate
		$queryString += "time<$end "
		}
	$queryString = $queryString.TrimEnd(' ')
	}
	
if ($ResultSize -le 50 -and $ResultSize -gt 0)
	{
	#Use limit parameter if result size specified is 50 or less
	$limitParam = "?limit=$ResultSize"
	$fullUrl = $url + $limitParam + $queryString
	$response = Invoke-RestMethod -Method Get -Uri $fullUrl -Credential $credential
	$result = $response.items
	}
else
	{
	$offset = 0
	do
		{
		#If no result size or larger than 50, use paging to build result object
		$offsetParam = "?offset=$offset"
		$fullUrl = $url + $offsetParam + $queryString
		$response = Invoke-RestMethod -Method Get -Uri $fullUrl -Credential $credential
		$result += $response.items
		$offset = $offset + 50
		}
	until ($response.total_count -eq 0 -or $result.Count -ge $ResultSize)
	}

#Filter results to contain desired properties
$output = $result | Sort-Object -Property msgts | 
	Select-Object -Property @{n='Timestamp';e={([DateTime]$_.msgts0).ToLocalTime()}},
		@{n='Action';e={$_.msgaction}},
		@{n='SenderIP';e={$_.msgfromserver}},
		@{n='Sender';e={$_.msgfrom}},
		@{n='Recipient';e={$_.msgto}},
		@{n='Subject';e={$_.msgsubject}},
		#Convert spam rating to corresponding word meaning
		@{n='RPDScore';e={Get-RPDScoreTranslation $_.score_rpd}},
		#SpamAssassin rating (>=5 means spam)
		@{n='SAScore';e={[math]::Round($_.score_sa,1)}},
		@{n='Description';e={$_.msgdescription}}

#Add custom type for use with formatting file
foreach ($entry in $output)
	{
	$entry.pstypenames.Insert(0,'Tracking.Information')
	}

#Create the default property display set (mostly for use without formatting file)
$defaultDisplaySet = 'Timestamp','Action','Sender','Recipient','Subject'
$defaultDisplayPropertySet = New-Object -TypeName System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultDisplaySet)
$psStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
$output | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $psStandardMembers

Write-Output $output

Leave a Reply

Your email address will not be published. Required fields are marked *

*