For the emails your Gmail search picks out, logs whether each passed the sender checks and which servers it really came through.
Uses
Claude
Gmail
Google Sheetsip-api.com
Record where incoming emails really came from
mailSearch to blank.Set mailSearch to blank.spreadsheetUrl to blank.Set spreadsheetUrl to blank.tabName to blank.Set tabName to blank.checkedLabel to Headers logged.Set checkedLabel to Headers logged.maxMessages to 10.Set maxMessages to 10.maxRelayAddresses to 10.Set maxRelayAddresses to 10.checkedOn.Get today's date and save it as checkedOn.searchQuery to mailSearch -label:@checkedLabel.Set searchQuery to mailSearch -label:@checkedLabel.searchQuery. Save it as inbox.List emails matching searchQuery. Save it as inbox.messages to an empty list.Set messages to an empty list.inbox.emails isn't emptyIf inbox.emails isn't emptyYes
messages to inbox.emails.Change messages to inbox.emails.No
No steps
maxMessages multiplied by 1 items of messages and save it as checkedMessages.Take the first maxMessages multiplied by 1 items of messages and save it as checkedMessages.checkedMessages is emptyIf checkedMessages is emptyYes
No mail matched that search that has not already been logged..Show in the output: No mail matched that search that has not already been logged..No
No steps
message in checkedMessages:For each message in checkedMessages:messageId to blank.Set messageId to blank.message.id isn't empty and message.id isn't blankIf message.id isn't empty and message.id isn't blankYes
message.id and save it as messageId.Trim message.id and save it as messageId.No
No steps
messageId is emptyIf messageId is emptyYes
Skipped a message that arrived without an id..Show in the output: Skipped a message that arrived without an id..No
No steps
headerErrorIf a step below fails, save the error as headerErrorTry
messageId. Save it as headers.Get the sender, subject and date of email messageId. Save it as headers.fromLine to no From header.Set fromLine to no From header.headers.From isn't empty and headers.From isn't blankIf headers.From isn't empty and headers.From isn't blankYes
headers.From and save it as fromLine.Trim headers.From and save it as fromLine.No
No steps
subjectLine to no Subject header.Set subjectLine to no Subject header.headers.Subject isn't empty and headers.Subject isn't blankIf headers.Subject isn't empty and headers.Subject isn't blankYes
headers.Subject and save it as subjectLine.Trim headers.Subject and save it as subjectLine.No
No steps
dateLine to no Date header.Set dateLine to no Date header.headers.Date isn't empty and headers.Date isn't blankIf headers.Date isn't empty and headers.Date isn't blankYes
headers.Date and save it as dateLine.Trim headers.Date and save it as dateLine.No
No steps
returnPathLine to no Return-Path header.Set returnPathLine to no Return-Path header.headers["Return-Path"] isn't empty and headers["Return-Path"] isn't blankIf headers["Return-Path"] isn't empty and headers["Return-Path"] isn't blankYes
headers["Return-Path"] and save it as returnPathLine.Trim headers["Return-Path"] and save it as returnPathLine.No
No steps
authLine to blank.Set authLine to blank.headers["Authentication-Results"] isn't empty and…If headers["Authentication-Results"] isn't empty and…Yes
headers["Authentication-Results"] and save it as authLine.Trim headers["Authentication-Results"] and save it as authLine.No
No steps
receivedLine to blank.Set receivedLine to blank.headers.Received isn't empty and headers.Received isn't blankIf headers.Received isn't empty and headers.Received isn't blankYes
headers.Received and save it as receivedLine.Trim headers.Received and save it as receivedLine.No
No steps
spfHeaderLine to blank.Set spfHeaderLine to blank.headers["Received-SPF"] isn't empty and headers["Received-SPF"] isn't blankIf headers["Received-SPF"] isn't empty and headers["Received-SPF"] isn't blankYes
headers["Received-SPF"] and save it as spfHeaderLine.Trim headers["Received-SPF"] and save it as spfHeaderLine.No
No steps
originIpLine to blank.Set originIpLine to blank.headers["X-Originating-IP"] isn't empty and headers["X-Originating-IP"] isn't…If headers["X-Originating-IP"] isn't empty and headers["X-Originating-IP"] isn't…Yes
headers["X-Originating-IP"] and save it as originIpLine.Trim headers["X-Originating-IP"] and save it as originIpLine.No
No steps
authText to authLine spfHeaderLine.Set authText to authLine spfHeaderLine.spfResult to no SPF result recorded in these headers.Set spfResult to no SPF result recorded in these headers.authText in lowercase and save it as spfResultMatch.Extract the first match from authText in lowercase and save it as spfResultMatch.spfResultMatch isn't emptyIf spfResultMatch isn't emptyYes
spfResult to spfResultMatch.Change spfResult to spfResultMatch.No
No steps
dkimResult to no DKIM result recorded in these headers.Set dkimResult to no DKIM result recorded in these headers.authText in lowercase and save it as dkimResultMatch.Extract the first match from authText in lowercase and save it as dkimResultMatch.dkimResultMatch isn't emptyIf dkimResultMatch isn't emptyYes
dkimResult to dkimResultMatch.Change dkimResult to dkimResultMatch.No
No steps
dmarcResult to no DMARC result recorded in these headers.Set dmarcResult to no DMARC result recorded in these headers.authText in lowercase and save it as dmarcResultMatch.Extract the first match from authText in lowercase and save it as dmarcResultMatch.dmarcResultMatch isn't emptyIf dmarcResultMatch isn't emptyYes
dmarcResult to dmarcResultMatch.Change dmarcResult to dmarcResultMatch.No
No steps
scanText to receivedLineSet scanText to receivedLine
authText
originIpLine.scanText and save it as foundAddresses.Extract all matches from scanText and save it as foundAddresses.maxRelayAddresses multiplied by 1 items of foundAddresses with duplicates removed and save it as relayAddresses.Take the first maxRelayAddresses multiplied by 1 items of foundAddresses with duplicates removed and save it as relayAddresses.relayList to no relay address appears in these headers.Set relayList to no relay address appears in these headers.relayPlaces to no relay addresses to look up.Set relayPlaces to no relay addresses to look up.locatedRows to an empty list.Set locatedRows to an empty list.relayAddresses isn't emptyIf relayAddresses isn't emptyYes
relayAddresses with , and save it as relayList.Join relayAddresses with , and save it as relayList.lookupErrorIf a step below fails, save the error as lookupErrorTry
located.Look up several IP addresses. Save it as located.located.results isn't emptyIf located.results isn't emptyYes
locatedRows to located.results.Change locatedRows to located.results.No
No steps
On error
relayPlaces to the address lookup step did not answer, so these addresses were not looked up.Change relayPlaces to the address lookup step did not answer, so these addresses were not looked up.No
No steps
placeLines to an empty list.Set placeLines to an empty list.relay in locatedRows:For each relay in locatedRows:relayQuery to an address. Set relayQuery to an address.relay.query isn't empty If relay.query isn't emptyYes
relay.query and save it as relayQuery.Trim relay.query and save it as relayQuery.No
No steps
relayPlace to not located. Set relayPlace to not located.relay.located is yes If relay.located is yesYes
placeLabel to no place on record.Set placeLabel to no place on record.relay.location_label isn't empty and relay.location_label isn't blankIf relay.location_label isn't empty and relay.location_label isn't blankYes
relay.location_label and save it as placeLabel.Trim relay.location_label and save it as placeLabel.No
No steps
ispName to no provider on record.Set ispName to no provider on record.relay.isp isn't empty and relay.isp isn't blankIf relay.isp isn't empty and relay.isp isn't blankYes
relay.isp and save it as ispName.Trim relay.isp and save it as ispName.No
No steps
relayPlace to placeLabel, run by ispName.Change relayPlace to placeLabel, run by ispName.No
notLocatedReason to no reason given.Set notLocatedReason to no reason given.relay.reason isn't empty and relay.reason isn't blankIf relay.reason isn't empty and relay.reason isn't blankYes
relay.reason and save it as notLocatedReason.Trim relay.reason and save it as notLocatedReason.No
No steps
relayPlace to not located (notLocatedReason).Change relayPlace to not located (notLocatedReason).relay.hosting is yes If relay.hosting is yesYes
relayPlace to relayPlace - a data centre address, which is what mail sent through a cloud or bulk provider looks like.Change relayPlace to relayPlace - a data centre address, which is what mail sent through a cloud or bulk provider looks like.No
No steps
relayQuery: relayPlace to the end of placeLines and save it as placeLines. Add relayQuery: relayPlace to the end of placeLines and save it as placeLines.placeLines isn't emptyIf placeLines isn't emptyYes
placeLines with a new line and save it as relayPlaces.Join placeLines with a new line and save it as relayPlaces.No
No steps
fromLine in lowercase and save it as fromDomain.Extract the first match from fromLine in lowercase and save it as fromDomain.returnPathLine in lowercase and save it as pathDomain.Extract the first match from returnPathLine in lowercase and save it as pathDomain.envelopeNote to A domain could not be read from both the From header and the Return-Path header..Set envelopeNote to A domain could not be read from both the From header and the Return-Path header..fromDomain isn't empty and pathDomain isn't emptyIf fromDomain isn't empty and pathDomain isn't emptyYes
fromDomain is pathDomainIf fromDomain is pathDomainYes
envelopeNote to The From address and the Return-Path are on the same domain (fromDomain).Change envelopeNote to The From address and the Return-Path are on the same domain (fromDomain).No
envelopeNote to The From address is on fromDomain and the Return-Path is on pathDomain. These differ on most mailing lists, newsletters, ticketing systems and forwarded mail.Change envelopeNote to The From address is on fromDomain and the Return-Path is on pathDomain. These differ on most mailing lists, newsletters, ticketing systems and forwarded mail.No
No steps
factsSender to From: fromLineSet factsSender to From: fromLine
Return-Path: returnPathLine
envelopeNote.factsChecks to SPF: spfResultSet factsChecks to SPF: spfResult
DKIM: dkimResult
DMARC: dmarcResult.factsRelays to Relay addresses named in the headers: relayListSet factsRelays to Relay addresses named in the headers: relayList
Where those addresses resolve:
relayPlaces.headerFacts to factsSenderSet headerFacts to factsSender
factsChecks
factsRelays.notePrompt to You are writing one short note for a log of email header fields.Set notePrompt to You are writing one short note for a log of email header fields.
Describe only what the fields below record, in at most three sentences of plain English. Say when a field records nothing. Do not decide anything about the message, do not say what it is, and do not say what should be done about it. Do not mention anything that is not listed below.
Relay addresses in other countries are ordinary: mailing lists, forwarding services and cloud providers all route mail through them. Say where an address resolves without implying anything about it.
Header fields:
headerFacts.replyText to blank.Set replyText to blank.noteErrorIf a step below fails, save the error as noteErrorTry
notePrompt. Save it as modelReply.Ask Claude: notePrompt. Save it as modelReply.
Maximum response length: 300modelReply.response_text isn't emptyIf modelReply.response_text isn't emptyYes
modelReply.response_text and save it as replyText.Trim modelReply.response_text and save it as replyText.No
No steps
On error
messageId.Show in the output: The written note step did not answer for message messageId.note to The written note step returned nothing, so this row carries the header fields on their own..Set note to The written note step returned nothing, so this row carries the header fields on their own..replyText isn't emptyIf replyText isn't emptyYes
note to replyText.Change note to replyText.No
No steps
replyText in lowercase and save it as noteWentBeyondTheFields.Match keywords in replyText in lowercase and save it as noteWentBeyondTheFields.noteWentBeyondTheFields.is_match is yesIf noteWentBeyondTheFields.is_match is yesYes
note to The written note was set aside because it went beyond describing the fields. Read the header fields in this row instead..Change note to The written note was set aside because it went beyond describing the fields. Read the header fields in this row instead..No
No steps
tabName in spreadsheetUrl.Add a row to tabName in spreadsheetUrl.checkedLabel to email messageId.Add label checkedLabel to email messageId.On error
messageId: headerError as text.Show in the output: Could not read the headers for message messageId: headerError as text.