Saturday, April 08, 2006

About NSMailDelivery and Message.framework

I've been asked a few times about the NSMailDelivery class by a few people, probably either through a mailing list post I made, or iSendMail. Well, let's look through iSendMail to see what happens.

Firstly, the iSendMail target includes Message.framework, which is where NSMailDelivery is defined. Now, some clips from the iSendMail.m file:

if([NSMailDelivery hasDeliveryClassBeenConfigured]==NO)
{
//can't send mail...
NSLog(@"Cannot send mail!\n");
[pool release];
exit(EXIT_FAILURE);
}


Before I even try to send mails out, I check whether NSMailDelivery is going to work. The clincher is straightforward: if Mail.app can send an email, NSMailDelivery can send an email. There's no way to configure e.g. a SMTP server programatically (unless you want to delve into Mail.app's configuration, that is); if you want to do this, consider using PantoMIME which is a more complete message framework. NSMailDelivery is just a convenience class for accessing Mail.app's message sending capabilities, nothing more. Launch Mail, open a new message, put a To address in and hit Cmd-Shift-D. If it goes, then you can use NSMailDelivery.

NSMutableDictionary *headers=[NSMutableDictionary dictionary];

while((argch=getopt(argc,argv,"f:t:s:"))!=-1)
{
switch(argch)
{
case 'f':
[headers setObject:[NSString stringWithCString:optarg] forKey:@"From"];
break;
case 't':
[headers setObject:[NSString stringWithCString:optarg] forKey:@"To"];
break;
case 's':
[headers setObject:[NSString stringWithCString:optarg] forKey:@"Subject"];
break;
case '?':
default:
usage();
}
}


Headers are defined in a dictionary. Obviously a To header is a requirement, you could probably get away without a Subject. If I wanted to use Mail's default email address I wouldn't supply a From header (but I'd use a different delivery method, see below); and if I wanted custom X-Mailer: or whatever, I'd do it here too. Any header which appears here will make it into the resultant mail, and any header not listed here will not be inserted for you.

NSAttributedString *attrMsg;
NSMutableString *msg=[[NSMutableString alloc] init];

//...

attrMsg=[[NSAttributedString alloc] initWithString:msg];


The message must be an attributed string, whether or not you actually use any attributes. But unless you choose a send a MIME message, any attribute you do use will be lost, read on...

mailSent=[NSMailDelivery deliverMessage:attrMsg headers:headers format:NSASCIIMailFormat protocol:nil];

if(mailSent==YES)
{
NSLog(@"Mail sent successfully.\n");
}
else
{
NSLog(@"Mail delivery failed!\n");
}


a few things to note about the delivery line. Firstly, observe that all of these methods we're using are class methods, not instance methods; I never instantiate anything from Message.framework. If I hadn't set a From address I could have used +deliverMessage:subject:to: but then could only send an ASCII message (which is all iSendMail does, anyway) and would have had to accept whatever From address was default in Mail.app. If your message atributed string contains formatting information (or MIME attachments :-)) then use NSMIMEMailFormat. protocol may as well always be nil, as your choices are that (which uses the preferred transport protocol, which is SMTP at the moment on Mac OS X) or NSSMTPDeliveryProtocol, which is also SMTP.

2 comments:

Sergio Santana said...

Hi Leeg, I was having problems with the implementation of email,
but thanks to your post I resolved the doubts. It is obvious that
need to expand and customize and it only works for Xcode 2.3 and
that leopard is discontinued.

Thanks

Jiří Hrabák said...

Problem is that NSMailDelivery is deprecated since 10.5 and there is no other system way....