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 seems like a good choice. Sending simple emails is quite straight forward.
CWMessage *message = [[CWMessage alloc] init];
CWInternetAddress *address;
address = [[CWInternetAddress alloc] initWithString:@"from@gmail.com"];
[message setFrom:address];
[address release];
address = [[CWInternetAddress alloc] initWithString:@"to@somewhere.com"];
[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.
- EdMessages’ proper name is EDMessage
- EDMessage as it exists on the referenced site will not build. I have sustantially reworked the project and added authentication, but I have no way to deliver this new code at this point - stay tuned
EDMessage is somewhat easier to use than Pantomime, but does not send asynchronously (guess I should add that :-)
Thanks for the feedback, David. Looking forward for the new code to arrive. Cheers.
Any ideas on sending emails in Cocoa in the iPhone SDK?
I’m writing my own that is compatible with the SDK limitations, and am open to open-sourcing it. Can’t talk about what the limitations are (for the moment), but as the code works on Mac OS X too, I don’t see why I can’t release it.