T••LBX: Blog

Applescript to show unread emails in the terminal

If you are on OSX and use Mail.app, but work a lot in the command line and would like a simple command to print a summary of unread emails, then this post is for you.

For this we are going to use AppleScript. We'll see at the end why applescript is not necessarily a great scripting language, but it let's us control graphical applications. This means we can do things that are hardly possible otherwise.

Just print the amount of unread emails

Let's start with a first version:

#!/usr/bin/osascript
on run
  tell application "Mail"
    return the unread count of inbox
  end tell
end run

This script just prints the total number of unread emails in all accounts. Save it in a file named "unread-emails" and make it executable with chmod +x unread-emails. Then you can call it from anywhere if the script is in your $PATH.

We've already learned a few things. We now know the shebang line for applescripts. This osascript command is what runs an applescript from the terminal, or a shell script. The "OSA" means Open Scripting Architecture and the command is called like this because potentially, applescript is not the only language which can script OSX applications. I never used it but there is a javascript version called "JXA".

Back to our script, there is a main on run block which is the main function that is called when starting a script. I am not sure it is needed but I always put it in case I add arguments to the command. Then you have what is called a tell block. It puts you in the context of the Mail application so that you can control it. Once in this block, you can send instructions like check new mail or access specific objects like mailboxes and messages.

Then comes the line that actually gets the total number of unread emails. It should be self explanatory since applescripts are so verbose. It makes then easy to read, but not easy to write. And you can see the first most annoying thing: there is no easy way to print to stdout from an applescript as far as I know. One way is to use do shell script to use a shell command like echo or printf. The other way is to return something from on run because it will be printed.

I used the return method because otherwise each do shell script starts a new subprocess. However you'll see it is not great either because it means if you print a lot of things, you need to set an output string and add to it until you're done and can return it.

Anyway, this little script should work, it prints the amount of unread emails in all accounts.

Separate each mail account

If you are like me, you have more than one account, and some of them are disabled. Here is a new version which prints the amount of unread emails for each account.

#!/usr/bin/osascript

on run
  tell application "Mail"
    set myOutput to ""
    repeat with a in every account whose enabled is true
      set accountName to name of a
      set myMailbox to mailbox "INBOX" of account accountName
      set myOutput to myOutput & accountName & ": "
      set myOutput to myOutput & unread count of myMailbox
      set myOutput to myOutput & "\n"
    end repeat
    set myOutput to myOutput & "\nTotal: " & unread count of inbox
    return myOutput
  end tell
end run

Definitely more meaty. The first thing to note is that now we are using set ... to ... a lot. This is how you set a variable in applescript. The first variable is the string we will build up to return it as output.

We then iterate through accounts with a repeat loop. We use a whose statement to filter accounts and only get the ones that are enabled. For each account we print the account name and the amount of unread emails. The & is how you concatanate strings in applescript. And the way you get a property of a record is with name of a. You can also use a's name which is supposed to read better, but as a developer I would rather have a dot notation to be honest.

One thing to note is that the way you get the count for one account is slightly different. The inbox shortcut seems to only work when it is for all accounts. When counting on a single account, you need to name the inbox explicitly with mailbox "INBOX" of account accountName.

At the very end, we print the total amount of unread emails like before and then return the string to be printed. When running this command, you should see an output like this:

Perso: 2
Work: 1
Enq: 1

Total: 4

Add a summary for each unread email

Alright, so far so good. Now let's add a little summary for each unread email with the sender and the subject line.

#!/usr/bin/osascript

on run
  tell application "Mail"
    set myOutput to ""
    repeat with a in every account whose enabled is true
      set accountName to name of a
      set myMailbox to mailbox "INBOX" of account accountName
      set myOutput to myOutput & accountName & ": "
      set myOutput to myOutput & unread count of myMailbox
      set myOutput to myOutput & "\n"
      repeat with msg in every message of myMailbox whose read status is false
        set myOutput to myOutput & "- " & sender of msg & "\n"
        set myOutput to myOutput & "  " & subject of msg & "\n"
      end repeat
    end repeat
    set myOutput to myOutput & "\nTotal: " & unread count of inbox
    return myOutput
  end tell
end run

The output should now look like this:

Perso: 2
- Mom <[email protected]>
  Did you receive my funny cat images ?
- Dad <[email protected]>
  You need to help me set the wifi printer
Work: 1
- Boss <[email protected]>
  Urgent !!!
Enq: 1
- <[email protected]>
  This is not a spam. I've lost 20 kg in 1h.

Total: 4

The only difference in the script are these lines which iterate through unread messages:

repeat with msg in every message of myMailbox whose read status is false
  set myOutput to myOutput & "- " & sender of msg & "\n"
  set myOutput to myOutput & "  " & subject of msg & "\n"
end repeat

So it iterates through the messages of the current mailbox that are unread (i.e. read status is false). And then for each one we display the sender and the subject line with a bit of indentation for clarity.

There are obviously a lot of things that can be improved. We could check for new mails and wait before printing all this info. Also we could use a bit of color. That would be trivial in a shell script, but applescript is not primarily made to interract with command line. We probably would have to process the output through another command which adds colors.

Conclusion on Applescript

I don't personally know anybody who is really confortable with applescript. In my opinion, the main reason is because it is too verbose for somebody who's a developer, yet too technical for an average user. This is probably why Apple created the Automator layer on top of it.

One other reason is that error messages are not really helpful. The intention is that it reads like english, but then you miss a trivial word and get a cryptic message that is hard to interpret, even for a trained developer.

Speed can also be an issue because we essentially control graphical applications. This obviously defeats the point of a command line interface. For example printing unread messages like we just did could be more efficient if we used a command line mail client like Mutt. But writting an applescript is a good option when you want to come up with a quick and dirty solution. Or simply when there is no other way.

My advice would be to use it only for the needed part and do the rest with shell scripts. I mean a clear short task. Although if you're writing a script that is meant to be used by somebody who is not technical, then that is where it shines. You can write a script that you start with a double click, then use system dialog boxes instead of the typical stdin/stdout flow. You can even use the system panel for choosing files. Or you can create droplets with the on open block.


Tags:     applescript shell osx