MailMate bundles for fun and profit

I usually think of MailMate like the A-10 Warthog of OS X email programs: it’s not as sleek and sexy as the average new email app you see coming out of indie devs’ doors, but it makes up for that in sheer power and efficiency when it comes to dealing with your email.1

One of these is a blunt, efficient instrument of concentrated fury, and the other is a plane.

One part of MailMate that seems pretty opaque to newbies is MailMate’s ability to run Commands on messages. If you’ve used MailMate in the past, you’ll probably have noticed the “Commands” menu item nestling on the top of your screen, but you may not have investigated it. Or maybe you use one or two of the default command bundles available for download, but have never thought about the fact that you can make your own custom commands.

It’s always nice to be able to get at the guts of your emails - especially if you want to act on them outside your mail program, or archive them for later referral. In fact, this is what got me to start looking at custom commands: I wanted to copy an email as Markdown so I could store a copy of it for a project I was working on. As far as I can tell, there’s no way to do this with MailMate as it stands, but something like this should be readily doable through commands, right?

The main issue I had when it came to commands for MailMate, was how to get started. While MailMate’s manual isn’t too bad, its documentation for advanced features is a little lacking. Looking through the table of contents, you can find this page on scripts, which mentions that “bundles” will replace scripts “in a future revision of MailMate”. Bundles are also mentioned on the preferences page in the manual, which states:

Creating new bundles is undocumented for now, but a large set of existing bundles allow you to integrate MailMate with various third party applications.

Searching the internet doesn’t reveal anything. So I guess it’s time to make my own guide.

Bundle structure

For this tutorial, I’m going to make a simple Bundle that allows us to copy an email for pasting into a MediaWiki page, complete with Mediawiki-style formatting.

A bundle is nothing more than a grouped set of commands, with a little metadata wrapped around it. Bundles exist as folders (named with the .mmBundle extension), inside which you store your metadata, commands, and any supporting documentation. Here’s how your average bundle will look:

1
2
3
4
5
6
7
8
9
10
myBundle.mmBundle/
|--Commands/
|  |--Command one.mmCommand
|  +--Command two.mmCommand
|--Support/
|  +--bin/
|     |--bin_one
|     +--bin_two
+--info.plist

To get MailMate to read the bundle, it needs to be situated in ~/Library/Application Support/MailMate/Bundles/ - but we’re a while away from that yet.

Populating plists

Every essential file for your bundle is a property list file, or plist. Apple allows a couple of different formats for plist files, but I’ve had the best luck with old NeXTSTEP-style dictionaries of values. I’ll be using these for this guide, since JSON plists (my favoured style) apparently break MailMate like nobody’s business.

Info

First, let’s edit the file info.plist. This just tells MailMate a little bit about the bundle: chiefly, we give it a UUID to refer to it by, and a title to show in the menu bar:

1
2
3
4
{
    name = "Copy";
    uuid = "DD86F48D-7276-448C-AC50-9BD156EFBEF9";
}

That’s all you need! I’ve used my own UUID here, if you want to generate one by yourself, open up terminal and type in:

1
uuidgen | pbcopy

Then ⌘+V wherever you want that UUID to appear. Note that info.plist supports other properties as well, including:

These are all optional fields, presumably for use with a bundle repository (like the one you can access from Preferences). It’s worth noting that your bundle will still work if you leave out these properties, but it will fail if you add a property it doesn’t recognise.

Commands

Let’s get into the nitty-gritty of this: the commands. Each bundle is represented by an entry in the Commands menu, and each command file inside the Bundle’s Commands/ folder is represented by a sub-entry under this entry:

A typical bundle entry in the Commands menu.

Our bundle will start off with just one command, but you can imagine building several commands into the bundle, all related to one program or task.

We’ll create our command in the Commands/ folder, calling it Copy to wiki.mmCommand. This will be a text file - in fact, it’s going to be a NeXTSTEP-stype plist file just like info.plist.

This is what a .mmCommand file looks like:

1
2
3
4
5
6
7
8
{
    name          = "Copy as wiki";
    uuid          = "550219B8-6E4E-4C2B-A1F2-1043951C0947";
    keyEquivalent = "^c";
    input         = "canonical";
    environment   = "MM_FROM=${from}\nMM_TO=${to}\nMM_SUBJECT=${subject}\nMM_DATE=${date}";
    command       = '#!/bin/bash\n"${MM_BUNDLE_SUPPORT}/bin/copy_wiki"';
}

We have a bunch of properties here. This is what they do:

There’s a few other properties you can put in this file as well:

Most of these properties I’ve discovered by looking through the built-in MailMate bundle (located in MailMate.app/SharedSupport/Bundles), and by looking through the MailMate list archives.

Input and Output

Exactly how can you input your data? Let me list the ways:

What about output? It looks like there’s currently only one form of output that isn’t just discarding the message entirely, and that’s action. This will make MailMate perform an action (a list of possible actions is available in the previous link). This output requires that you return a plist-style string specifying the action to perform. For example, MailMate’s command “New message to Recipients” returns:

1
2
3
4
5
6
7
8
{ actions = ( {
    type = createMessage;
        headers = {
            "to" = '${TO}';
        };
        resultActions = ( { type = openMessage; } );
    },);
}

(where ${TO} is a variable set to the recipients of the selected email).

Making it do things

OK, now we’ve talked forever about what MailMate’s commands can do. Let’s make it actually do something.

Our command above is looking for a binary called copy_wiki in the Support/bin/ directory. Let’s give it something to run. This is the contents of copy_wiki:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/ruby
# encoding: UTF-8

require "shellwords"

header = "* '''Date:''' #{ENV["MM_DATE"]}\n* '''From:''' #{ENV["MM_FROM"]}\n* '''To:''' #{ENV["MM_TO"]}\n* '''Subject:''' #{ENV["MM_SUBJECT"]}\n\n"
body = $stdin.read

# Reformat
body = body.gsub(/((?:^> .*)+)/m) do |m|
    "<blockquote>" + m.gsub(/^>\s*/,"") + "</blockquote>"
end

`echo #{(header + body).shellescape} | pbcopy`

What does this do?

As it stands, MailMate won’t actually be able to execute this file, so you’ll want to give it the right permissions:

1
chmod +x copy_wiki

Putting it in place

OK! We should now have the following files:

1
2
3
4
5
6
7
Copy/
|--Commands/
|  +--Copy as wiki.mmCommand
|--Support/
|  +--bin
|     +--copy_wiki
+--info.plist

Now all you need to do is:

  1. Rename the main folder (Copy/ in this case) to have the mmBundle extension.
  2. Place the bundle in ~/Library/Application Support/MailMate/Bundles.
  3. See if your bundle appears in the Commands menu.

My bundle doesn’t appear in the Commands menu!

This probably means that something’s up with your info.plist file. I highly recommend futzing around with copies of the working bundles (usually located in Managed/Bundles/ within the MailMate App Support folder) to work out how they differ from yours - or drop a comment and we can work it out together.

My bundle appears, but the Command won’t work!

Well, the good news is that your info.plist file is fine. The bad news is that there’s an error either in your mmCommand file, or the binary itself - and there’s no way I know of to find out!

I suggest replacing your binary file with something really simple. For example, I’ll often replace the binary script with:

1
2
#!/usr/bin/env ruby
File.open(File.join(ENV["HOME"], "Desktop/mailmate_test.txt"),"w"){ |io| io.puts "This is working!"}

That’ll drop an annoying file on your desktop. If this works, you know your mmCommand is fine, and it’s just the binary that’s causing havoc. If this still doesn’t work, chances are something horrid is going on in your mmCommand. See solutions for the above problem.

Can I look at your source?

Funny you should ask.


  1. Also, it has a business plan which isn’t reliant on being bought out and shuttered by a large software company in a few years - not that I’m bitter or anything.