Send email from Cocoa

14,847

Solution 1

UPDATE: As others suggested, from 10.9 you can use NSSharingService that supports attachments as well!

Swift example:

    let emailImage          = NSImage.init(named: "ImageToShare")!
    let emailBody           = "Email Body"
    let emailService        =  NSSharingService.init(named: NSSharingServiceNameComposeEmail)!
    emailService.recipients = ["[email protected]"]
    emailService.subject    = "App Support"

    if emailService.canPerform(withItems: [emailBody,emailImage]) {
        // email can be sent
        emailService.perform(withItems: [emailBody,emailImage])
    } else {
        // email cannot be sent, perhaps no email client is set up
        // Show alert with email address and instructions

    }

OLD UPDATE: My old answers worked fine until I had to sandbox my apps for the App Store.~~ Since then the only solution I found was using simply a mailto: link.

- (void)sendEmailWithMail:(NSString *) senderAddress Address:(NSString *) toAddress Subject:(NSString *) subject Body:(NSString *) bodyText {
    NSString *mailtoAddress = [[NSString stringWithFormat:@"mailto:%@?Subject=%@&body=%@",toAddress,subject,bodyText] stringByReplacingOccurrencesOfString:@" " withString:@"%20"];
    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:mailtoAddress]];
    NSLog(@"Mailto:%@",mailtoAddress);
}

Disadvantage: No attachment! If you know how to make it work on Mac let me know!

OLD ANSWER: You can Apple Script, Apple's scripting bridge framework (Solution 2) or a Python script (Solution 3)

Solution 1 (Apple script):

attachments is an array of stings containing file paths

- (void)sendEmailWithMail:(NSString *) toAddress withSubject:(NSString *) subject Attachments:(NSArray *) attachments { 
NSString *bodyText = @"Your body text \n\r";    
NSString *emailString = [NSString stringWithFormat:@"\
                         tell application \"Mail\"\n\
                         set newMessage to make new outgoing message with properties {subject:\"%@\", content:\"%@\" & return} \n\
                         tell newMessage\n\
                         set visible to false\n\
                         set sender to \"%@\"\n\
                         make new to recipient at end of to recipients with properties {name:\"%@\", address:\"%@\"}\n\
                         tell content\n\
                         ",subject, bodyText, @"McAlarm alert", @"McAlarm User", toAddress ];

//add attachments to script
for (NSString *alarmPhoto in attachments) {
    emailString = [emailString stringByAppendingFormat:@"make new attachment with properties {file name:\"%@\"} at after the last paragraph\n\
                   ",alarmPhoto];

}
//finish script
emailString = [emailString stringByAppendingFormat:@"\
               end tell\n\
               send\n\
               end tell\n\
               end tell"];



//NSLog(@"%@",emailString);
NSAppleScript *emailScript = [[NSAppleScript alloc] initWithSource:emailString];
[emailScript executeAndReturnError:nil];
[emailScript release];

/* send the message */
NSLog(@"Message passed to Mail");

}

Solution 2 (Apple scriptingbridge framework): You can use Apple's scriptingbridge framework to use Mail to send your message
Apple's exmaple link it's pretty straightforward, you only need to fiddle with adding a rule and Mail.app to your project. Read Readme.txt carefully.

Change "emailMessage.visible = YES;" to "emailMessage.visible = NO;" so it sends it in the background.

Disadvantage: users need to have valid accounts under Mail.

Solution 3 (Python Script (no user account): You can also use a python script to send a message. Disadvantage: users have to enter SMTP details unless you grab them from Mail (but then you can use Solution 1 above directly), or you have to have a reliable SMTP relay hardcoded in your app (you can set up a gmail account and use it for that purpose, however if your apps send too many emails google can delete your account (SPAM))
I use this python script:

import sys
import smtplib
import os
import optparse

from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders

username = sys.argv[1]
hostname = sys.argv[2]
port = sys.argv[3]
from_addr = sys.argv[4]
to_addr = sys.argv[5]
subject = sys.argv[6]
text = sys.argv[7]

password = getpass.getpass() if sys.stdin.isatty() else sys.stdin.readline().rstrip('\n')

message = MIMEMultipart()
message['From'] = from_addr
message['To'] = to_addr
message['Date'] = formatdate(localtime=True)
message['Subject'] = subject
#message['Cc'] = COMMASPACE.join(cc)
message.attach(MIMEText(text))

i = 0
for file in sys.argv:
    if i > 7:
        part = MIMEBase('application', 'octet-stream')
        part.set_payload(open(file, 'rb').read())
        Encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))
        message.attach(part)
    i = i + 1

smtp = smtplib.SMTP(hostname,port)
smtp.starttls()
smtp.login(username, password)
del password

smtp.sendmail(from_addr, to_addr, message.as_string())
smtp.close()

And I call it form this method to send an email using a gmail account

- (bool) sendEmail:(NSTask *) task toAddress:(NSString *) toAddress withSubject:(NSString *) subject Attachments:(NSArray *) attachments {

        NSLog(@"Trying to send email message");
        //set arguments including attachments
        NSString *username = @"[email protected]";
        NSString *hostname = @"smtp.gmail.com";
        NSString *port = @"587";
        NSString *fromAddress = @"[email protected]";  
        NSString *bodyText = @"Body text \n\r"; 
        NSMutableArray *arguments = [NSMutableArray arrayWithObjects:
                                    programPath,
                                    username,
                                    hostname,
                                    port, 
                                    fromAddress, 
                                    toAddress,
                                    subject,
                                    bodyText, 
                                    nil];  
        for (int i = 0; i < [attachments count]; i++) {
            [arguments addObject:[attachments objectAtIndex:i]];
        }

        NSData *passwordData = [@"myGmailPassword" dataUsingEncoding:NSUTF8StringEncoding];


        NSDictionary *environment = [NSDictionary dictionaryWithObjectsAndKeys:
                                     @"", @"PYTHONPATH",
                                     @"/bin:/usr/bin:/usr/local/bin", @"PATH",
                                     nil];
        [task setEnvironment:environment];
        [task setLaunchPath:@"/usr/bin/python"];

        [task setArguments:arguments];

        NSPipe *stdinPipe = [NSPipe pipe];
        [task setStandardInput:stdinPipe];

        [task launch];

        [[stdinPipe fileHandleForReading] closeFile];
        NSFileHandle *stdinFH = [stdinPipe fileHandleForWriting];
        [stdinFH writeData:passwordData];
        [stdinFH writeData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
        [stdinFH writeData:[@"Description" dataUsingEncoding:NSUTF8StringEncoding]];
        [stdinFH closeFile];

        [task waitUntilExit];

        if ([task terminationStatus] == 0) { 
            NSLog(@"Message successfully sent");
            return YES;
        } else {
            NSLog(@"Message not sent");
            return NO;
        }
    }

I hope it helps

Solution 2

Those response are outdated Mac OS X 10.8 and more you should use NSSharingService

NSArray *shareItems=@[body,imageA,imageB];
NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail];
service.delegate = self;
service.recipients=@[@"[email protected]"];
service.subject= [ NSString stringWithFormat:@"%@ %@",NSLocalizedString(@"SLYRunner console",nil),currentDate];
[service performWithItems:shareItems];

The sharing service documentation page

Solution 3

This post should help - it cites example code too.

You also need to change line 114 in Controller.m to send the message in the background:

emailMessage.visible = NO;
Share:
14,847
Lenny Magico
Author by

Lenny Magico

Not much :)

Updated on June 17, 2022

Comments

  • Lenny Magico
    Lenny Magico about 2 years

    How can I send an email from a Cocoa app without using any email clients ? I have NSURL but it opens up an email client. I would like to send the email without this happening.

  • Lenny Magico
    Lenny Magico about 13 years
    I have had a look at that post before but it opens up the mail application when yous end the email which I don't really want, just want to send the email, basically I want to make my own mail application :), but thanks for the answer :D
  • Dave DeLong
    Dave DeLong about 13 years
    Scripting Bridge... What if I use Thunderbird? Or Outlook? Or something else?
  • Tibidabo
    Tibidabo about 13 years
    Then you can use the perl script. Mail is the most popular email client, if it is not present you can use ask the user to enter his/her SMTP details or you can hardcode yours.
  • Martin Hering
    Martin Hering over 11 years
    You can try using [[NSWorkspace sharedWorkspace] openFile:filePath withApplication:@"Mail"] to create a new email with an attachment. You can't prefill it with recipient however and you need to have access to a shared folder to save the file to (Downloads folder e.g.).
  • Mark Bridges
    Mark Bridges over 10 years
    You could try using the new NSSharingService in Mavericks 'NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail]; [service setRecipients:[NSArray arrayWithObject:@"Your email"]]; [service setSubject:@"Your subject"]; [service performWithItems:yourAttachments, nil];'
  • fzwo
    fzwo almost 10 years
    This should be the accepted answer. Be aware though that if the user is using Outlook, the attachments won't get attached.
  • Z S
    Z S almost 10 years
    Another problem with NSSharingService is that you cannot set the email body of the composer beforehand (if that is something you need to do).
  • insys
    insys over 9 years
    @ZS this is not correct. See performWithItems: to set the message body.
  • Vincent Tourraine
    Vincent Tourraine over 9 years
    The NSSharingService is 10.8+, but the recipients and subject properties are 10.9+ only, so you should add a respondToSelector: test if you target 10.8.
  • Hedin
    Hedin almost 9 years
    This doesn't work on El Capitan. It throws NSSoftLinking - The ShareKit framework's library couldn't be loaded from /System/Library/PrivateFrameworks/ShareKit.framework/Version‌​s/A/ShareKit. to the console and nothing happens. Seems it has some restrictions on using private frameworks. FYI the app is not sandboxed.
  • Hedin
    Hedin almost 9 years
    UPD: the same with Yosemite. I think, I need to link some framework to the app, not sure what.
  • Hedin
    Hedin almost 9 years
    UPD: for anyone who gets the same error: ShareKit is 64 bit-only.
  • jimwan
    jimwan about 7 years
    i cannot attach file using this NSArray *shareItems=@[body,fileURL];
  • lifjoy
    lifjoy over 4 years
    To clarify: specify attachments as file URLs, like so: NSArray *theShareItems = @[aMessageStr, [NSURL fileURLWithPath:anAttachmentsPath]];