Let’s assume I am selecting several messages in Apple Mail and that each message consists of a thread of messages between 3 different people (the initial sender, watson and me). How can I get in each iteration the “initial sender”?
set myReply to "Override"
set theSender to "Me <me@gmail.com>"
tell application "Mail"
set theSelection to selection
if theSelection is {} then return
activate
set myList to {}
repeat with thisMessage in theSelection
if (extract address from sender of thisMessage) is "watson@gmail.com" then
set theOutgoingMessage to reply thisMessage with properties {visible:true, sender:theSender}
delay 1
tell application "System Events"
keystroke myReply
delay 1
end tell
send theOutgoingMessage
delay 1
set myList to myList
delay 1
end if
end repeat
end tell
So basically this watson always replies within each conversation. I want to tell to Watson “override”, but I want to send a confirmation email, like “tokens sent”, to the actual first sender (initiator of the conversation) and not to watson.
I draw mail flow graph with Apple Mail.app. from selected messages.
So, It may be possible for us to detect original message sender in a thread.
This script is a part of my ebook “Mail.app Scripting Book with AppleScript”.
Would you be able to provide a sample code?
My book reader can read or run or change it.
That’s not possible, at least not in the sense of SMTP messages. A message is only a single entity. It can contain headers referring to other messages, but itself is only a single item. Here is (basically) what I’d do in JavaScript. The same is certainly possible in AS, but I don’t want to think about that.
Since JavaScript is not really popular here, I heavily commented the code, hoping to make it clearer. It does nothing but to figure out the first sender in a thread, and it logs that when the script is run in Script Editor or with osascript
.
Code to send a new message to the first sender must still be added to this script.
(() => {
const app = Application("Mail")
/* NOTE: This is just a POC, handling the _first_ selected message.
To make it handle the complete selection, enclose the relevant code in a
loop running over selection */
const mail = app.selection()[0];
/* Get the account, assuming that all messages are in the same one */
const account = mail.mailbox.account();
/* Init the sender to the sender of the last message */
let sender = mail.sender();
/* Get the previous message. If there's none, pm will be null */
let pm = getPriorMessage(account, mail);
/* As long as there's a previous message, update the sender to
the sender of the current message and find the previous one */
while (pm) {
sender = pm.sender();
pm = getPriorMessage(account, pm);
}
/* Now pm is null, i.e. there's no previous message,
and the sender of the first message in this thread is stored in sender */
console.log(`first sender: "${sender}"`);
})()
/* Take an account and a message and find the previous message if there is one.
The function uses the "In-Reply-To" header for that */
function getPriorMessage(account, msg) {
/* Get all headers */
const headers = msg.headers();
/* Filter out the "In-Reply-To" header" */
const replyingTo = headers.filter(h => h.name() === 'in-reply-to');
/* If there is none, return null to indicate that this is the first message */
if (!replyingTo.length) return null;
/* Sanitize the message ID of the "In-Reply-To" header
by removing the angle brackets around it */
const priorMsgId = replyingTo[0].content().replaceAll(/[<>]/g,"");
/* Find the message(s) for this message ID with "whose".
"flatMap" removes all empty elements from the resulting list */
const priorMail = account.mailboxes.messages.whose({"messageId": priorMsgId})().flatMap(x => x);
if (priorMail.length === 0) {
/* The message mentioned in the In-Reply-To header couldn't be found in this account.
Return null to terminate processing */
console.log(`No message found for ID ${priorMsgId} in account ${account.name()}`);
return null;
} else {
/* Return the first message matching the previous message ID */
return priorMail[0];
}
}
Thanks a lot for your detailed answer. But I’m not sure how would I “insert it” or “call it” from my script. Could you please provide an example? I would truly appreciate it!
I don’t really know, since I do not know nor use AppleScript. Personally, I’d simply write whatever I want to write in JavaScript. Having said that, you might do something like this:
- modify the line
console.log(`first sender: "${sender}"`);
to read return sender
;
- save the modified version to a file, eg
script.js
;
- run this script with
do shell script "osascript -l JavaScript script.js"
from your AS code and capture the output in a variable, like so
set sender to do shell script ...
And please use the modified version of the script – the first one I posted contained a stupid error due to heavy commenting. Also: This stuff is not fast, because of the whose
construct.