Find First Unread Mail Message in My Inbox

As part of a larger macro to block all emails from a sender, I wrote this AppleScript, which works, but I would rather skip over to my unread emails, not go to the first one.

My second attempt does not work, and I don't know why. Any help would be appreciated.

tell application "System Events"
   tell application process "Mail"
      select row 1 of table 1 of scroll area 1 of splitter group 1 of splitter group 1 of window 1
   end tell
end tell
set downArrowKeyCode to 125
set theSender to "None"

tell application "System Events"
   tell application process "Mail"
      repeat with currentRow from 1 to 50
         display dialog "current row " & currentRow
         select row currentRow of table 1 of scroll area 1 of splitter group 1 of splitter group 1 of window 1
         delay 1.0
         set theSelectedMessage to selection
         set msgCount to (count of theSelectedMessage)
         if (msgCount = 1) then
            set theMsg to item (currentRow) of theSelectedMessage
            set theSender to (sender of theMsg)
            display dialog "Sender is: " & theSender
            if read status of theMsg is false then key code downArrowKeyCode
            set currentRow to currentRow + 1
            exit repeat
         else
            display dialog "giving up for now"
         end if
         
      end repeat
      
   end tell
end tell

Hey Ike,

Try something like this:

tell application "System Events"
   tell application process "Mail"
      tell window 1
         tell splitter group 1
            tell splitter group 1
               tell scroll area 1
                  tell table 1
                     set readRowList to rows where its UI element 2's accessibility description is not "unread"
                  end tell
               end tell
            end tell
         end tell
      end tell
   end tell
end tell

-Chris

1 Like

Hmmm….

This gets me to a row, but “accessibility description” does not seem to be a reliable way to decide whether or not the corresponding message is read or unread. That field is often “missing value”. If you replace "is not unread" with "is read" you'll see what I mean.

I’m having trouble getting from the row to the message’s state (read or unread). Any other suggestions?

Here is what I tried:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application "System Events"
	tell application process "Mail"
		tell window 1
			tell splitter group 1
				tell splitter group 1
					tell scroll area 1
						tell table 1
							set readRowList to rows where its UI element 2's accessibility description is not "unread"
							-- set readRowList to rows where its UI element 2's selected is true
							-- set readRowList to rows where its UI element 2's selected is false
						end tell
						
						repeat with aRow in readRowList
							-- display dialog "Name of " & (name of UI element of aRow)
							
						end repeat
					end tell
				end tell
			end tell
		end tell
	end tell
end tell

I've already done that.

Missing value means read. This is silly, but we're dealing with Apple here...

Set various messages to read and unread then run the script.

# Collect read-status of messages.
tell application "System Events"
   tell application process "Mail"
      tell window 1
         tell splitter group 1
            tell splitter group 1
               tell scroll area 1
                  tell table 1
                     
                     tell rows
                        set readList to accessibility description of UI element 2
                     end tell
                     
                  end tell
               end tell
            end tell
         end tell
      end tell
   end tell
end tell

It reliably shows unread where applicable, and when the email is read the result is an empty-string not missing value.

missing value has its own meaning in AppleScript:

set m1 to missing value
set m2 to ""
m1 = m2

I'll look at your script here in a bit.

Try this:

tell application "System Events"
   tell application process "Mail"
      tell window 1
         tell splitter group 1
            tell splitter group 1
               tell scroll area 1
                  tell table 1
                     
                     set firstReadRow to first row where its UI element 2's accessibility description is not "unread"
                     set selected of firstReadRow to true
                     
                  end tell
               end tell
            end tell
         end tell
      end tell
   end tell
end tell

This works but has a built-in annoyance – the viewer will not scroll to show the selection.

So if the selection will not naturally be in view you have to play with things to force it.

1 Like

I'm not sure why you're monkeying around with UI-Scripting when Mail is pretty scriptable.

# This won't work on certain containers on on Smart-Mailboxes.
tell application "Mail"
   tell (some message viewer whose index is 1)
      set selectedMailboxList to (get selected mailboxes)
      if selectedMailboxList ≠ {} then
         set theMailbox to item 1 of selectedMailboxList
         if class of theMailbox = container then
            tell theMailbox
               set readMessageList to messages whose read status is true
            end tell
         end if
      end if
   end tell
end tell

Why don't you describe what your overall goal is, and we can see if there's a more efficient means to get you there.

I'm not certain if this is sufficiently comparable, but I'll share it and let ikenassi decide if this macro is of use.

Since I keep the Dock hidden, I had a menu bar app that displayed a badge when there was new mail. That app is no longer compatible with more recent versions of macOS. To conveniently check for new mail without switching to Mail I whipped up this macro, which is the kind of thing someone like me does since I don't have scripting skills like CCStone has.

All this macro does is check for new email. If there are new messages, then

  1. Mail comes to the front,
  2. the Inbox is selected,
  3. the topmost message is selected,
  4. and then it is opened.

Really, though, the AppleScript checks for new mail, and most everything else is just Keyboard Maestro simulating keystrokes.

If there are no new messages, I get a system alert.

One key to this macro is that the Favorites Bar is used so that the macro can go directly to a desired mailbox by simulating a keystroke. In my macro, that's Command-1 for the Inbox, which is leftmost in the Favorites Bar. The Favorites Bar supports any Command-[1 through 9] keyboard shortcut for a corresponding mailbox, with the counting going from left to right in the Favorites Bar.

Another key to this setup is the use of Option-[Up Arrow]. This is a built-in Mac shortcut for going to the top of a list (Option-[Down Arrow] being the direct way to the bottom of a list). Most native apps should support this. Since my Inbox is sorted such that most recent messages are at the top, Option-[Up Arrow] is thus selecting the most recent email in the Inbox.

The Return key is just opening the selected message.

As I said, I don't have scripting skills like CCStone. But simulating keystrokes can also be a means to navigating an app like Mail.

image

1 Like

So first, I want to thank everyone for their responses to my request. Much appreciated. Here is the complete macro "Block and Junk" to deal with the increasing amount of spam I'm getting.

The purpose of this macro is to block the sender of a selected mail message, and then select the next message in the list of messages. It mostly works, except for selecting the next message after the one whose sender was blocked. It's quite workable the way it is, but I would like to automate the last step, i.e., to select the next message in the message list. That's what I haven't figured out how to do.

Block and Junk .kmmacros (14.8 KB)

Macro-Image

Unfortunately I can't test the blocking, because Mojave doesn't have that feature...

Before you go any further...

  • Have you made sure there's not a regular menu item?
  • Have you searched the Apple Mail AppleScript Dictionary for a “Block” verb?

I know how to block a sender. That's not the problem. And yes, to your other question, mail is very scriptable. But going from the UI of the mail app to the internal Mail db state is where I have a problem. For example, this script works, but I can't figure out how to select the next unread message. On the other hand, it's not a big deal, so unless there's an obvious answer, I'll drop it. Check out this script to show the unread messages in the inbox:

tell application "Mail"
	
	set myAccount to account "Nassi"
	set myMailbox to mailbox "INBOX" of myAccount
	set myCount to unread count of myMailbox
	-- display dialog "Count of unread messages in inbox " & myCount
	repeat with i from myCount to 1 by -1
		set thisMsg to message i of myMailbox
		if read status of thisMsg is false then
			-- select thisMsg-- but how???
			-- exit repeat
			set f to extract name from sender of thisMsg
			set d to date sent of thisMsg
			set d to (month of d as integer) & "/" & day of d & "/" & year of d
			set s to subject of thisMsg
			
			display dialog ("From:	" & f & ¬
				"
Date:	" & d & ¬
				"
Subj:	" & s)
			
		end if
	end repeat
end tell

Mail in Mojave is a little flakey about how it handles first unread in some circumstances. For instance:

  • Run the script.
  • Mark the message as Read.
  • Run the script again.
    • The same bleeping message is selected.
    • Hopefully that's been fixed, but I wouldn’t bet on it.

Try this:

tell application "Mail"

   # Don't change the inbox – that's a reference to the inbox container for all accounts.
   set firstUnread to first message of inbox whose read status is false
   
   tell (some message viewer whose index is 1)
      
      try
         set selectedMailboxes to selected mailboxes
      on error errMsg number errNum
         if errNum = -10000 then
            set selectedMailboxes to {missing value}
         end if
      end try
      
      if selectedMailboxes ≠ {inbox} then
         set selected mailboxes to {inbox}
         delay 0.2
      end if
      
      set selected messages to firstUnread
      
   end tell
end tell