Skip to content
pullipalli edited this page Nov 9, 2021 · 13 revisions

This is incomplete and work in progress

Table of Contents

Background

MailMate has a plugin system known as bundles. A bundle is a collection of files which can be used to extend the functionality of MailMate, in particular, to provide new commands in the “Command” menu. Most of the existing bundles can be found in the Bundles preferences pane.

Originally, MailMate was supposed to include a graphical user interface (GUI) for creating and editing bundles and therefore it was not documented how to create bundles manually. This was a mistake because the GUI still does not exist and without documentation it has been very hard for users to create new bundles. Hopefully, the following documentation can help some MailMate users in the future.

If you prefer to learn by example then skip ahead to the “Example” section. Also note that all of the bundles available in the Bundles preferences pane are also available on GitHub. In any case, you should make sure that you read about the script, input, output, and environment keywords.

Bundle Locations

Currently, MailMate looks for bundles in the following 3 locations:

~/Library/Application Support/MailMate/Bundles/
~/Library/Application Support/MailMate/Managed/Bundles/
MailMate.app/Contents/SharedSupport/Bundles/

Only the first one is relevant for users wanting to create their own bundles. The second one is used by MailMate to store the bundles enabled in the Bundles preferences pane and the last one contains some bundles distributed with MailMate.

Anatomy of a Bundle

A bundle is a folder containing an info.plist file and one or more folders:

Example.mmBundle/
	Commands/
		Example.mmCommand
	Filters/
		Example.mmFilter
	Themes/
		Example.mmTheme
	Support/
		bin/
			helper_script
	info.plist

The Support folder can be used to contain anything needed for the commands or filters declared in the .mmCommand and .mmFilter files. This could be scripts like the helper_script above or it could be data files needed in the bundle. Anything but the files in the Support foldes has to be so-called property lists. Most of the current bundles use the old-style ASCII format.

ToDo: Support JSON format for property lists.

The info.plist file

This file identifies the bundle with a few key/value pairs.

name

A descriptive name for the bundle, e.g., the name of the application if the bundle provides integration with another application. Currently displayed in the “Command” menu and in the Bundles preferences pane.

description

A short description of the bundle. Currently displayed in the Bundles preferences pane.

contactEmailRot13

An email address for the creator of the bundle after applying the ROT13 algorithm. The following is an example of how to do that in a Terminal window: printf "user@example.com" | tr '[A-Za-z]' '[N-ZA-Mn-za-m]'. This is currently not used anywhere.

uuid

A UUID which uniquely identifies the bundle.

Note that all property list files in a bundle has its own UUID. It is very important that these are never reused. For convenience, a UUID can be put on the pasteboard using the following commands in a Terminal window:

uuidgen | tr -d "\n" | pbcopy

General Keywords

Commands, filters and themes have a few keywords in common.

name

The name is used in the user interface whenever it is needed. Examples include the Command menu for commands and the theme popup menus in the Preferences.

uuid

It's the UUID which uniquely identifies any command, filter or theme. This should always be unique and it should never be changed.

disabled

Some times it is useful to be able to disable something temporarily. This can be done with the disabled keyword like this:

disabled = 1;

Commands and Filters

Commands and filters are very different things, but they do share some keywords. We describe these first and then go into the keywords only relevant for commands or filters.

script

This is the most important key and it should be used in all commands/filters. It provides the script to be executed. This script can be written in any scripting language you like as long as it uses a so-called “shebang” to specify the interpreter. The type of input (on stdin) is defined using the input keyword and the type of output (on stdout) is defined using the output keyword. In addition to this, it's possible to define some environment variables using the environment keyword.

input

Parsing emails is really hard. You need to handle MIME parts, encrypted parts, various encodings like quoted-printable, base64, format=flowed, header encoded words, and various non- or semi-standard formats like tnef (winmail.dat). But much worse, you also have to work around numerous bugs and legacy types of behavior. Ideally, you need to be able to parse any email created by any version of any email client since the early 1970s.

If MailMate only allowed you to get the raw data of an email then it would be very hard to do anything useful and it would be close to impossible to make it robust. Fortunately, MailMate allows you to get the data you need after it has been decoded and after MailMate has worked around all the bugs and legacy stuff of the past.

On a more philosophical note, the main goal of MailMate is to provide input which takes care of as many of the existing email intricacies as possible before handing over data to commands or other parts of the interface. The many problems concerning the conversion of headers and bodies to any kind of canonical data should be handled by MailMate to keep everything else as simple as possible. In other words, canonicalization is to be MailMate's side of the fence.

The main input needed by a command is defined using the input keyword. This input is then provided on stdin. Unless something else is explicitly specified then the input uses the UTF-8 character encoding. The following options are currently available:

Yes, the current input types are:

  • none (the default)

    No input

  • raw (example)

    The message in its original raw format. This is, for example, suitable for moving messages into other email-capable applications. The character encoding can be anything and there might even be a combination of different encodings in different MIME parts.

  • decoded

    The MIME part is decoded if it is, for example, encoded using quoted-printable or base64. This is primarily useful if sending specific MIME parts to a command. For example, an image could be given to the command in its raw (original) binary format. This is not currently used by any of the included commands.

    ToDo: Complete implementation.

  • canonical (example)

    This is equivalent to the text displayed in MailMate when using the “Prefer Plain Text” option. It is, most often, based on the text/plain MIME part. The text is decoded (quoted-printable/base64), deflowed (format=flowed), and converted to UTF-8.

  • html (example)

    This is equivalent to what happens before MailMate displays a message in its message view which means that even a plain text message is converted to HTML. This is probably mainly useful for display purposes.

  • selection

    The currently selected text in the message view.

  • formatted (example)

    This requires an additional formatString key/value pair for the command which is described further below.

environment (example)

A bundle command or filter can also be configured to receive more information via the use of environment variables. Reading environment variables is supported by most scripting languages. Here's a very simple example:

environment = "DEBUG_ENABLED=1";

The script can then use the DEBUG_ENABLED environment variable to change its behavior.

You can specify multiple environment variables using newlines and you can query the headers of the message involved using format strings and specifiers. Here's an example getting the type and subtype of the Content-Type header while defaulting to text/plain:

environment = 'MM_CONTENT_TYPE=${content-type.type:text}\nMM_CONTENT_SUBTYPE=${content-type.subtype:plain}';

A few environment variables are always available:

Variable Description
MM_BUNDLE_ITEM_NAME The name of the command/filter.
MM_BUNDLE_ITEM_UUID The UUID of the command/filter.
MM_BUNDLE_SUPPORT The path to the “Support” folder within the bundle.
MM_SHARED_SUPPORT The path to the “SharedSupport” folder within the MailMate application bundle.
MM_SELECTED_RANGE If using the canonical input and text is selected in the message view then this is the start/end offset of the selection.

Commands

Commands are shown in the “Command” menu. The files use the extension .mmCommand and they are all placed in the Commands folder within a bundle. There is a number of keys which only applies to commands.

executionMode (example)

The executionMode is relevant when multiple messages are selected in MailMate. It supports 3 different values:

noMessages
singleMessage
multipleMessages

This value determines whether the script should be executed once for each message or once for all selected messages. The noMessages option is only used if the command does not need the selected message(s) at all.

The multipleMessages mode is especially important when used in combination with the formatted input type. More about this in the section about the formatString key.

formatString (example)

The formatString key should be used in combination with the formatted input type. This tells MailMate to use the format string to generate a string on stdout for each message. The formatString value format is identical to the enviroment value format.

This make it easy (and fast) to, e.g., get one or more specific headers from a large set of messages. For example, one could get all the “From” addresses like this:

formatString = "${from.address:unavailable}";

This is well suited for further handling, e.g., by standard UNIX commands. For example, a top 10 of senders could be done like this:

sort -f | uniq -ic | sort --reverse --numeric-sort | head -10

It's also fine to use the formatString to provide multiple values:

formatString = "${from.name:No name available}\n${subject.body:No subject available}\n";

separatorString and suffixString

ToDo: Document or refactor how these work.

output

The output key is used to tell MailMate what it should do with the output of a command. Currently only 2 options exist. The default output option is discard and this leaves us with the only interesting output for now: actions.

The actions output type expects a property list to be returned from the command. Here is a simple example:

{
        actions = (
                {
                        type = "moveMessage";
                        mailbox = "archive";
                }
        );
}

Examples for the actions output returned from a command in

Ruby:

require 'json'

actions = {
  actions:
    [
      {
        type: 'moveMessage',
        mailbox: 'archive'
      }
    ]
}

print JSON.dump(actions)

and Python:

import json

actions = {
    "actions":
        [
            {
                "type": "moveMessage",
                "mailbox": "archive"
            }
        ]
}

print(json.dumps(actions))

Each action must have a type. Additional keys may be allowed/required depending on the type. The currently available types are:

  • playSound

    • path: Full path or a sound name if the sound can be found in a standard sound path.
  • notify

    • formatString: A format string (default is "“${subject}” from “${from.name:${from.address}}”").
    • mailbox: Mailbox identifier (click on a mailbox and do ⌘C to get this value).
  • moveMessage

    • mailbox: Mailbox identifier (must be an IMAP mailbox). Use ⌘C in MailMate to get the identifier for the selected mailbox.
    • mailboxType: One of the standard mailbox types, inbox, archive, sent, drafts, junk, or trash.
  • expungeMessage

    Permanently delete the message(s).

  • copyMessage

    • mailbox: See moveMessage.
    • mailboxType: See moveMessage.
    • variables: More about this further below.
  • changeFlags

    • enable: Array of IMAP flags/keywords, e.g., ( "\\Flagged", "\\Send").
    • disable: Array of IMAP flags/keywords.
  • changeHeaders

    • headers: Dictionary with headers and their new headers.
    • formatStrings: Dictionary with headers and format strings to generate their new headers.
  • exportMessage

    • folderPath: Simple disk path (it can also be a file: URL).
  • forwardMessage

    • recipient: Forward message to the recipient (this includes sending the message).
  • redirectMessage

    • recipient: Redirect message to the recipient (this includes sending the message).
  • createMessage

    • headers: Dictionary with headers for the message.
    • body: Entire message body.
  • replyMessage (currently always “Reply All”)

    • headers: Dictionary with headers for the message.
    • body: Reply part of message body.
  • sendMessage

    Send the message which must be a draft. This is most often used as part of resultActions.

  • openMessage

    Open the message in its own window. This is most often used as part of resultActions, e.g., to open a draft in a Composer window.

  • runCommand

    • scriptUUID: The UUID of a bundle command. Note that this command can return actions itself.

All actions allow an ids key which is an array of internal message ids (integers). If needed, these can be provided to a command using the virtual header named #body-part-id.

All actions allow a resultActions key. This takes another set of actions to be applied to the result of the first action. For example, it can be used to open or send a generated draft.

The copyMessage action is special since it has two different behaviors. If variables are not defined then it's a simple copy action equivalent to ⌥-dragging a message. If variables are defined then all headers and the body of the copied message are interpreted as being format strings for which the variables should be used. This can be used to create a draft message in MailMate with the purpose of using it as a template for an external script. The external script could, for example, handle a list of recipients for the draft message.

Two more things to keep in mind:

  • Note that actions are allowed to be returned in the JSON format. This is supported by many scripting languages and therefore makes it easier to work with than the old-school ASCII property list format.

  • In singleMessage mode MailMate tries to handle any resulting actions efficiently by merging them if they are identical for subsets of messages. This is important for large message selections.

keyEquivalent

Frequently used commands need shortcuts. This is configured using the keyEquivalent keyword:

keyEquivalent = "^t";

A description of the possible values can be found here.

If multiple commands share the same shortcut then MailMate shows a list with shortcut-numbers to quickly select the desired item.

conditions (example)

You can limit a commmand to only apply to certain types of messages. It's the same syntax as mailboxes use for their conditions. The following limits a command to apply to emails with the “List-Unsubscribe” header:

conditions = "list-unsubscribe exists";

inputFilesPattern

ToDo: Add example link.

Some times a command needs easy access to one or more MIME parts of a message. This is most easily done using the inputFilesPattern key. This is a regular expression telling MailMate which MIME parts to pick out and save as separate (decoded) files. For each MIME part a string is generated of a space-separated list of the hierarchy of the MIME content types. Here are some examples:

multipart/mixed multipart/alternative text/html
multipart/mixed image/png

ToDo: Consider if disposition should be part of the string by suffixing inline/attachment.

The first one is the HTML version of the displayed message and the second one is a PNG image attachment.

Here are some examples of some regular expressions and what they match:

Pattern Description
^text/plain$ Simple messages with a single root text/plain part.
image/ Any kind of image part.
multipart/mixed image/ Explicitly attached images (ignoring images in multipart/related).

MailMate saves the matched MIME parts in a temporary location and the script receives information about them in the MM_FILES environment variable. For example:

[ { "MIME":"image/jpeg",
    "filePath":"/some/temporary/path/example.jpeg",
	 "partID":390,
	 "rootID":386
  },
  { "MIME":"image/png",
    "filePath":"/some/temporary/path/example.png",
	 "partID":391,
	 "rootID":386
  }
]

saveForEditing (example)

This one exists explicitly for the purpose of allowing external editors access to the currently edited message in the Composer:

saveForEditing = 1;

See one of the many text editor bundles for examples on how to use it.

Filters

ToDo: Make it easier to customize which filters are used by MailMate. This is currently hardcoded in the eventFilters.plist file.

The idea of filters is to make it customizable what should be done with the content of emails in various parts of the interface. For example, one might want to strip parts of an email before displaying or replying to it.

Internally, this feature is used in many parts of MailMate.

input

Currently two formats are supported: canonical and html. These are already described further above.

output

The output formats are the same as the input formats. Note that in some cases the input and output types are not expected to be the same for a filter.

Themes

A theme is a section of CSS used when generating HTML for an outgoing email or when displaying a message. Themes appear for configuration in some popup menus in the Viewer and Composer preferences panes.

They only have a few possible keywords:

name

The name of the theme is used whenever the it is available for selection in the user interface.

author

The name of the author of the given stylesheet. It is not currently used for anything.

comment

A short description of the theme. It is not currently used for anything.

css

The stylesheet used when generating HTML using the theme. Note that it can be very tricky to create CSS which works well in a majority of email clients. In fact, it's almost impossible without also using a test service like Emails on Acid or Litmus. They are unfortunately subscription-based services which are not very well suited for infrequent use.

Debugging Bundles

Consider the following suggestions when developing and debugging bundles.

Test without MailMate

If possible, test your scripts without using MailMate. You can do this by keeping most of the code in the support folder in the bundle:

Support/bin/helper

Emulate the input using stdin and environment variables, for example, like this:

cat test_input.txt | MM_FROM_EMAIL=user@example.com Support/bin/helper

Use a Terminal Window

Launch MailMate from a Terminal window in order to more easily spot when a script fails. It can be done as follows if MailMate is installed in /Applications/

/Applications/MailMate.app/Contents/MacOS/MailMate

You can get additional output if you enable the following debug variable:

defaults write com.freron.MailMate MmDebugCommands -bool YES

Print to stderr or to a file if adding debug output to your scripts.

Checklist

  • Make sure all UUIDs are unique including the one in info.plist. Always generate them using uuidgen.

Customizing a Default Bundle

All of the bundles shown in the Bundles preference pane are available on GitHub.

If you want to, e.g., customize the OmniFocus bundle then you first need to create this folder:

mkdir -p ~/Library/Application\ Support/MailMate/Bundles
cd ~/Library/Application\ Support/MailMate/Bundles

Then you can clone the repository like this:

git clone https://github.com/mailmate/omnifocus.mmbundle.git

If you want to contribute your changes then you should look into forking (and maybe using SSH for cloning).

It's also fine to just use “Help ▸ Send Feedback” to tell me about improvements you would like to share.

Creating a New Bundle

MailMate includes a helper script to get you started. You can execute it like this:

/Applications/MailMate.app/Contents/SharedSupport/bin/create_bundle TestBundle "Your Name" your_address@example.com

Shortly after executing this, you should see an “Example Command” in the “Command ▸ TestBundle” menu within MailMate.

The bundle itself can be found and edited here:

~/Library/Application\ Support/MailMate/Bundles