As noted, writing Apple Mail plugins is not straightforward, since it only has a private plugin API, which is entirely undocumented and can change with any new version of Mail.app. The best code example is GPGMail, which is open source & still active (already working on Yosemite support). Here is what I successfully did to get started (will put it up on github once finished):
How to build a minimal Apple Mail plugin (as of Mavericks & Xcode 6.0.1)
- you need to create an OSX "Bundle" project in XCode
- wrapper extension is
mailbundle
(under Packaging in the project Build settings)
- a bundle needs to be stored under
~/Library/Mail/Bundles
(as Build Phase add a Copy Files action with that as absolute path destination and the *.mailbundle from your build/ folder as item to copy)
- for development, I have set up
/Applications/Mail.app
as executable in my run scheme, so that Run in XCode will build it, copy the bundle and start mail; note that at this point you'll get an error from Mail that your plugin cannot be started and was disabled
- you need to provide a list of
SupportedPluginCompatibilityUUIDs
in the Info.plist, I stole it from GPGMail, these change with new Mail/OSX versions
- use class-dump to generate the header files from Mail.app's private API
- starting point is
MVMailBundle
, which you have to inherit from and which has a registerBundle
method to hook you in
- I extracted that from the huge generated header file in a small MVMailBundle.h header to include where needed (as done by GPGMail)
- create a new class
MyMailBundle
, inheriting from NSObject
- it needs an
initialize
method
- and set it as "Principle class" in the Info.plist so that it gets run when the bundle is loaded by Mail.app
#import <Cocoa/Cocoa.h>
@interface MyMailBundle : NSObject
+ (void)initialize;
@end
initialize
implementation: previously, you could use the simple way and directly inherit as done in Letterbox, however, since 64-bit runtimes of Objective-C you have to use the dynamic way as done by GPGMail:
- using
NSClassFromString
to dynamically get the MVMailBundle
class
- and
class_setSuperclass
from <objc/runtime.h>
to have your own class inherit from it
- and then call
registerBundle
on it casted as MVMailBundle
(requires include of MVMailBundle.h
)
#import <objc/runtime.h>
#import "MVMailBundle.h"
#import "MyMailBundle.h"
@implementation MyMailBundle
+ (void)initialize
{
NSLog(@"Loading MyMail plugin...");
// since 64-bit objective-c runtimes, you apparently can't load
// symbols directly (i.e. through class inheritance) and have to
// resort to NSClassFromString
Class mvMailBundleClass = NSClassFromString(@"MVMailBundle");
// If this class is not available that means Mail.app
// doesn't allow plugins anymore or has changed the API
if (!mvMailBundleClass)
return;
// dynamically change super class hierarchy
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
class_setSuperclass([self class], mvMailBundleClass);
#pragma GCC diagnostic pop
// register our plugin bundle in mail
[[((MyMailBundle *)self) class] registerBundle];
NSLog(@"Done registering MyMail plugin.");
}
@end
- add some
NSLog
logging calls to verify the right thing is happening, they'll be visible in XCode's console when running/debugging Mail.app from within XCode or alternatively in the system logs of Console.app
- This should successfully run the plugin in Mail with no error!
- The next steps involve crazy things like MethodSwizzling and ClassPosing to modify Mail's behavior, where GPGMail can be a helpful example. (Haven't been there myself yet)
For reference, here are some of the resources that helped me: