How to get the sender of the first message in a thread

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.