Posts to Slack when new mail links to a site outside your expected list, with how recently that site was registered and who runs it.
Uses
Gmail
IPWHOIS.io
RDAP
Slack
Flag emails that link to unfamiliar websites
Whenever new mail arrives, the messages your Gmail search matches are read and one Slack note per message lists the hosts their links point to. Hosts on your list of expected domains, your own and your suppliers', are left out, so what reaches Slack is mail linking somewhere you did not expect.
For each host the note gives what the public registry holds: when the name was registered, how many days ago, and the registrar, or for a bare numeric address, who runs it and in which country. It also flags a link over plain http, a name in punycode, and a link on the sender's own domain. It reports and does not conclude: nothing says whether a link is worth opening, and no address is ever visited. Only the host part of a link is used, and a report that would repeat a full web address is withheld. Each message is labelled once read, so nothing is reported twice.
You need a Slack channel, a Gmail search naming the mail to read, and a list of the domains you already expect.
mailSearch to blank.Set mailSearch to blank.slackChannelId to blank.Set slackChannelId to blank.expectedDomains to blank.Set expectedDomains to blank.checkedLabel to Links checked.Set checkedLabel to Links checked.maxMessagesPerRun to 10.Set maxMessagesPerRun to 10.maxHostsPerMessage to 10.Set maxHostsPerMessage to 10.mailSearch, -label:, @checkedLabel, ] with nothing and save it as linkQuery.Join a list of [mailSearch, -label:, @checkedLabel, ] with nothing and save it as linkQuery.messageLimit to maxMessagesPerRun multiplied by 1.Set messageLimit to maxMessagesPerRun multiplied by 1.hostLimit to maxHostsPerMessage multiplied by 1.Set hostLimit to maxHostsPerMessage multiplied by 1.expectedDomains with "Split expectedDomains with
replaced by , with replaced by in lowercase by , and save it as expectedList.linkQuery. Save it as arrived.List emails matching linkQuery. Save it as arrived.readCount to 0.Set readCount to 0.postCount to 0.Set postCount to 0.message in arrived.emails:For each message in arrived.emails:readCount is greater than or equal to messageLimitIf readCount is greater than or equal to messageLimitreadErrorIf a step below fails, save the error as readErrormessage.id and save it as messageId.Trim message.id and save it as messageId.messageId is emptyIf messageId is emptymessage.from and save it as fromHeader.Trim message.from and save it as fromHeader.addressPart to fromHeader.Set addressPart to fromHeader.fromHeader contains <If fromHeader contains <fromHeader split by < split by > and save it as addressPart.Get the first item of the last item of fromHeader split by < split by > and save it as addressPart.addressPart split by @ lowercase and save it as senderDomain.Make the last item of addressPart split by @ lowercase and save it as senderDomain.subject to (no subject).Set subject to (no subject).subjectMissingIf a step below fails, save the error as subjectMissingmessage.subject and save it as subjectText.Trim message.subject and save it as subjectText.subjectText isn't empty and subjectText is not nullIf subjectText isn't empty and subjectText is not nullsubject to subjectText.Change subject to subjectText.A message arrived with no subject..Show in the output: A message arrived with no subject..bodyText to blank.Set bodyText to blank.bodyMissingIf a step below fails, save the error as bodyMissingmessage.body and save it as bodyValue.Trim message.body and save it as bodyValue.bodyValue is not nullIf bodyValue is not nullbodyText to bodyValue.Change bodyText to bodyValue.A message arrived with no readable text..Show in the output: A message arrived with no readable text..seenHosts.Start an empty list and save it as seenHosts.hostLines.Start an empty list and save it as hostLines.hostCount to 0.Set hostCount to 0.linkUrl in all web addresses in bodyText:For each linkUrl in all web addresses in bodyText:hostCount is greater than or equal to hostLimit If hostCount is greater than or equal to hostLimitlinkUrl and save it as urlText. Trim linkUrl and save it as urlText.urlText starts with http:// (ignoring case) and save it as plainHttp. Check whether urlText starts with http:// (ignoring case) and save it as plainHttp.urlText split by :// with :// and save it as afterScheme. Join the items from index 1 onward of urlText split by :// with :// and save it as afterScheme.afterScheme split by / and save it as hostPart. Get the first item of afterScheme split by / and save it as hostPart.hostPart split by ? and save it as hostPart. Get the first item of hostPart split by ? and save it as hostPart.hostPart split by # and save it as hostPart. Get the first item of hostPart split by # and save it as hostPart.hostPart split by @ and save it as hostPart. Get the last item of hostPart split by @ and save it as hostPart.hostPart split by : and save it as hostPart. Get the first item of hostPart split by : and save it as hostPart.hostPart lowercase and save it as linkHost. Make hostPart lowercase and save it as linkHost.linkHost is empty or linkHost doesn't contain . or linkHost contains : or… If linkHost is empty or linkHost doesn't contain . or linkHost contains : or…seenHosts contains linkHost If seenHosts contains linkHostlinkHost to the end of seenHosts and save it as seenHosts. Add linkHost to the end of seenHosts and save it as seenHosts.expected to false. Set expected to false.allowed in expectedList: For each allowed in expectedList:allowed and save it as allowedName. Trim allowed and save it as allowedName.allowedName is empty or allowedName is null If allowedName is empty or allowedName is nulllinkHost is allowedName or linkHost ends with .@allowedName If linkHost is allowedName or linkHost ends with .@allowedNameexpected to true.Change expected to true.expected is yes If expected is yeshostCount. Add 1 to hostCount.notes to blank. Set notes to blank.plainHttp is yes If plainHttp is yesnotes to notes [linked over plain http].Change notes to notes [linked over plain http].linkHost starts with xn-- or linkHost contains .xn-- If linkHost starts with xn-- or linkHost contains .xn--notes to notes [the name is written in punycode, xn--].Change notes to notes [the name is written in punycode, xn--].linkHost is senderDomain or linkHost ends with .@senderDomain If linkHost is senderDomain or linkHost ends with .@senderDomainnotes to notes [on the sender's own domain].Change notes to notes [on the sender's own domain].factText to nothing was looked up for this host.. Set factText to nothing was looked up for this host..linkHost isn't empty If the first match in linkHost isn't emptyipErrorIf a step below fails, save the error as ipErrorlinkHost. Save it as net.Get the network operator of linkHost. Save it as net.net.isp and save it as isp.Trim net.isp and save it as isp.net.autonomous_system and save it as asn.Trim net.autonomous_system and save it as asn.net.country and save it as country.Trim net.country and save it as country.the link names an address rather than a name; the registry for that address records , isp, , , asn, , , country, .] with nothing and save it as factText.Join a list of [the link names an address rather than a name; the registry for that address records , isp, , , asn, , , country, .] with nothing and save it as factText.factText to the link names an address rather than a name, and the lookup for that address did not complete..Change factText to the link names an address rather than a name, and the lookup for that address did not complete..registryErrorIf a step below fails, save the error as registryErrorlinkHost. Save it as registry.Look up domain linkHost. Save it as registry.record to registry.result.Set record to registry.result.record.registration_date and save it as registeredOn.Trim record.registration_date and save it as registeredOn.record.registrar and save it as registrar.Trim record.registrar and save it as registrar.factText to the registry returned no registration date for this name..Change factText to the registry returned no registration date for this name..registeredOn isn't empty and registeredOn is not nullIf registeredOn isn't empty and registeredOn is not nullageText to blank.Set ageText to blank.ageErrorIf a step below fails, save the error as ageError, , the difference between registeredOn and today's date in days as text, days ago] with nothing and save it as ageText.Join a list of [, , the difference between registeredOn and today's date in days as text, days ago] with nothing and save it as ageText.ageText to blank.Change ageText to blank.the registry records this name as registered on , the first item of registeredOn split by T, ageText, .] with nothing and save it as factText.Join a list of [the registry records this name as registered on , the first item of registeredOn split by T, ageText, .] with nothing and save it as factText.registrar isn't empty and registrar is not nullIf registrar isn't empty and registrar is not nullfactText, Registrar of record: , registrar, .] with nothing and save it as factText.Join a list of [factText, Registrar of record: , registrar, .] with nothing and save it as factText.factText to the registry lookup for this name did not complete..Change factText to the registry lookup for this name did not complete..- , linkHost, notes, - , factText] joined by nothing to the end of hostLines and save it as hostLines. Add a list of [- , linkHost, notes, - , factText] joined by nothing to the end of hostLines and save it as hostLines.hostLines isn't emptyIf hostLines isn't emptyLinks in mail that just arrived
From: , fromHeader truncated to 120 characters using ... as the ellipsis,
Subject: , subject truncated to 120 characters using ... as the ellipsis] joined by nothing, hostLines joined by a new line, Only the host part of each link is shown, and only hosts that are not on your 'expectedDomains' list. None of these lookups says what a link is - they report what the public registry holds about a name, and nothing more. A name with nothing on record is a name with nothing on record. Nothing here has visited any of these addresses.] with a blank line and save it as report.reportLink in all web addresses in report:For each reportLink in all web addresses in report:reportLink with [link removed] in report and save it as report. Replace reportLink with [link removed] in report and save it as report.report contains ://If report contains ://report to A message arrived carrying links to hosts outside your expected domains, but the report could not be written without repeating a web address from the message. Open the message in the mailbox instead..Change report to A message arrived carrying links to hosts outside your expected domains, but the report could not be written without repeating a web address from the message. Open the message in the mailbox instead..slackChannelId.Send a message to slackChannelId.reportpostCount.Add 1 to postCount.Message , messageId, linked to nothing outside your expected domains, or its text held no web addresses.] joined by nothing.Show in the output: a list of [Message , messageId, linked to nothing outside your expected domains, or its text held no web addresses.] joined by nothing.checkedLabel to email messageId.Add label checkedLabel to email messageId.readCount.Add 1 to readCount.readError as text.Show in the output: Could not read the links out of one message: readError as text.Messages read: , readCount as text, , reports posted: , postCount as text] joined by nothing.Show in the output: a list of [Messages read: , readCount as text, , reports posted: , postCount as text] joined by nothing.