Sending emails from Cocoa
With 10.5 Apple deprecated the use of NSMailDelivery – without a replacement. So the questions comes up regulary – how does one send emails from Cocoa? Turns out there are couple of frameworks available that can also be used to do the job.
EdMessage has been around for ages. Checkout the example on how to
use the API. MailCore is also really easy to use. But both lack support for asynchronous mail delivery. With MailCore I
couldn’t even work out how to send attachments. Also no idea if it’s getting developed anymore. So if you are OK to use a LGPL library Pantomime seemsseemed like a good choice. Sending simple emails is quite straight
forward.
CWMessage *message = [[CWMessage alloc] init];
CWInternetAddress *address;
address = [[CWInternetAddress alloc] initWithString:@"[email protected]"];
[message setFrom:address];
[address release];
address = [[CWInternetAddress alloc] initWithString:@"[email protected]"];
[address setType:PantomimeToRecipient];
[message addRecipient:address];
[address release];
[message setSubject:@"test"];
[message setContentType: @"text/plain"];
[message setContentTransferEncoding: PantomimeEncodingNone];
[message setCharset: @"us-ascii"];
[message setContent: [@"This is a simple content." dataUsingEncoding: NSASCIIStringEncoding]];
smtp = [[CWSMTP alloc] initWithName:@"smtp.gmail.com" port:465];
[smtp setDelegate: self];
[smtp setMessage: message];
[message release];
ssl = YES;
mechanism = @"PLAIN";
[smtp connectInBackgroundAndNotify];
Pantomime features an asynchronous connection handling and sends messages to the delegate. The following methods should be implemented:
- (void) authenticationCompleted: (NSNotification *) theNotification
{
NSLog(@"Authentication completed! Sending the message.");
[smtp sendMessage];
}
- (void) authenticationFailed: (NSNotification *) theNotification
{
NSLog(@"Authentication failed! Closing the connection.");
[smtp close];
}
- (void) connectionEstablished: (NSNotification *) theNotification
{
NSLog(@"Connected!");
if (ssl) {
NSLog(@"Now starting SSL...");
[(CWTCPConnection *)[smtp connection] startSSL];
}
}
- (void) connectionTerminated: (NSNotification *) theNotification
{
NSLog(@"Connection closed.");
}
- (void) messageSent: (NSNotification *) theNotification
{
NSLog(@"Sent! Closing the connection.");
[smtp close];
}
- (void) serviceInitialized: (NSNotification *) theNotification
{
if (ssl) {
NSLog(@"SSL handshaking completed.");
}
if ([mechanism isEqualToString: @"NONE"]) {
NSLog(@"Sending the message.");
[smtp sendMessage];
} else {
NSLog(@"Available authentication mechanisms: %@", [smtp supportedMechanisms]);
[smtp authenticate:@"username" password:@"secret" mechanism: mechanism];
}
}
- (void) transactionResetCompleted: (NSNotification *) theNotification
{
NSLog(@"Sending the message over the same connection.");
[smtp sendMessage];
}
That’s a little more verbose than the simple synchronous delivery but it gives you a lot more flexibility to deal with authentication and connection problems.
As always sending mails with attachments requires a bit more work during the message setup. You basically need to wrap all the content in a multi part message like this:
...
[message setSubject:@"subject"];
CWMIMEMultipart *multipart = [[CWMIMEMultipart alloc] init];
CWPart *part = [[CWPart alloc] init];
[part setContentType: @"text/plain"];
[part setContentTransferEncoding: PantomimeEncodingQuotedPrintable];
[part setFormat: PantomimeFormatUnknown];
[part setCharset: @"UTF-8"];
[part setContent: [@"text body" dataUsingEncoding:NSUTF8StringEncoding]];
[multipart addPart:part];
[part release];
part = [[CWPart alloc] init];
NSFileWrapper *file = [[NSFileWrapper alloc] initWithPath:@"/path/to/file"];
[part setFilename: [[file filename] lastPathComponent]];
[part setContentType: @"application/octet-stream"];
[part setContentTransferEncoding: PantomimeEncodingBase64];
[part setContentDisposition: PantomimeAttachmentDisposition];
[part setContent: [file regularFileContents]];
[multipart addPart:part];
[part release];
[file release];
[message setContentTransferEncoding: PantomimeEncodingNone];
[message setContentType: @"multipart/mixed"];
[message setContent: multipart];
[multipart release];
[message setBoundary: [CWMIMEUtility globallyUniqueBoundary]];
[smtp setMessage: message];
NSLog(@"Sending message");
[smtp connectInBackgroundAndNotify];
In order to get to the Mail.app SMTP settings you can “steal” them directly from the Mail.app preferences
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryPath = [searchPaths objectAtIndex:0];
NSString *mailPrefsPath = [[libraryPath stringByAppendingPathComponent:@"Preferences"] stringByAppendingPathComponent:@"com.apple.mail.plist"];
NSDictionary *mailPreferences = [NSDictionary dictionaryWithContentsOfFile:mailPrefsPath];
NSArray *accounts = [mailPreferences valueForKey:@"DeliveryAccounts"];
Quite a bit of code just to get around the deprecation. Another alternative is to go through the new scripting bridge. But I personally consider that as a terrible hack. So I will stick with the above. Hope this little write-up is helpful!
Update: Turns out EdMessage is currently been worked on. Unfortunately no ETA and the current sources are not properly building.
Update: A new version of EdMessage has been released with lots of fixes and enhancements. I’ve also run into some nasty problems with Pantomime. So in the end I’ve switched to EdMessage and haven’t looked back. The official release is 10.5 only, but you can find my version that also compiles on 10.4 over on github.