send email from swift osx application
Asked Answered
D

3

14

I am facing problem on sending mail from my osx swift application. To send mail i used the below code

import Foundation
import Cocoa


class sendemail : NSObject, NSSharingServiceDelegate{

func sendEmail()  throws
{
    print("enter email sending")
    let body = "This is an email for auto testing throug code."
    let shareItems = [body] as NSArray

    let service = NSSharingService(named: NSSharingServiceNameComposeEmail)

    service?.delegate = self
    service?.recipients = ["[email protected]"]

    let subject = "Vea Software"
    service?.subject = subject

    service?.performWithItems(shareItems as [AnyObject])
}

}

i have found the source form this link:https://www.veasoftware.com/posts/send-email-in-swift-xcode-62-os-x-1010-tutorial

But it is not working.

I also tried to send mail from terminal following this instructions:

http://www.developerfiles.com/how-to-send-emails-from-localhost-mac-os-x-el-capitan/

it says :

postfix/postfix-script: fatal: the Postfix mail system is not running

please help me.

I can send mail from my mac mail app manually which is configured.

I am using

xcode 7.3, osx el captain and swift 2.2
Diakinesis answered 16/6, 2016 at 13:44 Comment(0)
E
25

Modern Swift:

func sendEmail(to recipients: [String], subject: String, body: String) {
    let service = NSSharingService(named: .composeEmail)!
    service.recipients = recipients
    service.subject = subject
    service.perform(withItems: [body])
}

// Usage
sendEmail(
    to: ["[email protected]"],
    subject: "Vea software",
    body: "This is an email for auto testing through code."
)

The local user must have an account setup with Mail.app. You also need an instance of NSApplication to run this. Can't do it in a CLI app. If you see the following error in your console, that means you have no active NSApplication instance.

[default] 0 is not a valid connection ID

Original answer

This works for me:

import Cocoa

class SendEmail: NSObject {
    static func send() {
        let service = NSSharingService(named: NSSharingServiceNameComposeEmail)!
        service.recipients = ["[email protected]"]
        service.subject = "Vea software"
        
        service.performWithItems(["This is an email for auto testing through code."])
    }
}

Usage:

SendEmail.send()
Eiger answered 17/6, 2016 at 2:13 Comment(3)
thanks a lot for your help. But it does not work here ... :( .... is this necessary to setup anything like postmail to run this successfully?Diakinesis
Works for me with no extra setup required. ThanksSubstrate
I had the same issue. What needs to be in place is that Apple's Mail is the email client of your choice.Pestilence
W
18

Swift 4.2:

class SendEmail: NSObject {
    static func send() {
        let service = NSSharingService(named: NSSharingService.Name.composeEmail)!
        service.recipients = ["[email protected]"]
        service.subject = "Email Subject"

        service.perform(withItems: ["Email Content"])
    }
}

Usage: SendEmail.send()

Wish answered 21/9, 2017 at 11:2 Comment(0)
S
2

We solved this "the hard way" by not using the shared services, but the postfix utility. Note that postfix must be running and correctly configured before the following code can take effect.

Why do this the hard way? well, it is a slightly more 'universal' solution that should work (or can be modified easily) on other platforms than macOS. Also, it does not depend on 'mail'.

Note that this also uses our Log facility, but you should be able to easily extract the code you need.

/// This queue/thread will be used to transfer the mails.

fileprivate let mailQueue = DispatchQueue.global(qos: .background)


/// Sends an email using the postfix unix utility.
///
/// - Note: Ths function only works if postfix is running (and set up correctly)
///
/// - Parameters:
///   - mail: The email to be transmitted. It should -as a minimum- contain the To, From and Subject fields.
///   - domainName: The domain this email is sent from, used for logging purposes only.

public func sendEmail(_ mail: String, domainName: String) {


    // Do this on a seperate queue in the background so this operation is non-blocking.

    mailQueue.async { [mail, domainName] in

        // Ensure the mail is ok

        guard let utf8mail = mail.data(using: .utf8), utf8mail.count > 0 else {
            Log.atDebug?.log("No mail present")
            return
        }

        let options: Array<String> = ["-t"] // This option tells sendmail to read the from/to/subject from the email string itself.

        let errpipe = Pipe() // should remain empty
        let outpipe = Pipe() // should remain empty (but if you use other options you may get some text)
        let inpipe = Pipe()  // will be used to transfer the mail to sendmail


        // Setup the process that will send the mail

        let process = Process()
        if #available(OSX 10.13, *) {
            process.executableURL = URL(fileURLWithPath: "/usr/sbin/sendmail")
        } else {
            process.launchPath = "/usr/sbin/sendmail"
        }
        process.arguments = options
        process.standardError = errpipe
        process.standardOutput = outpipe
        process.standardInput = inpipe


        // Start the sendmail process

        let data: Data
        do {

            Log.atDebug?.log("\n\(mail)")


            // Setup the data to be sent

            inpipe.fileHandleForWriting.write(utf8mail)


            // Start the sendmail process

            if #available(OSX 10.13, *) {
                try process.run()
            } else {
                process.launch()
            }


            // Data transfer complete

            inpipe.fileHandleForWriting.closeFile()


            // Set a timeout. 10 seconds should be more than enough.

            let timeoutAfter = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + UInt64(10000) * 1000000)


            // Setup the process timeout on another queue

            DispatchQueue.global().asyncAfter(deadline: timeoutAfter) {
                [weak process] in
                Log.atDebug?.log("Sendmail Timeout expired, process \(process != nil ? "is still running" : "has exited already")")
                process?.terminate()
            }


            // Wait for sendmail to complete

            process.waitUntilExit()

            Log.atDebug?.log("Sendmail terminated")


            // Check termination reason & status

            if (process.terminationReason == .exit) && (process.terminationStatus == 0) {

                // Exited OK, return data

                data = outpipe.fileHandleForReading.readDataToEndOfFile()

                if data.count > 0 {
                    Log.atDebug?.log("Unexpectedly read \(data.count) bytes from sendmail, content: \(String(data: data, encoding: .utf8) ?? "")")
                } else {
                    Log.atDebug?.log("Sendmail completed without error")
                }

            } else {

                // An error of some kind happened

                Log.atError?.log("Sendmail process terminations status = \(process.terminationStatus), reason = \(process.terminationReason.rawValue )")

                let now = dateFormatter.string(from: Date())

                Log.atError?.log("Sendmail process failure, check domain (\(domainName)) logging directory for an error entry with timestamp \(now)")


                // Error, grab all possible output and create a file with all error info

                let e = errpipe.fileHandleForReading.readDataToEndOfFile()
                let d = outpipe.fileHandleForReading.readDataToEndOfFile()

                let dump =
                """
                Process Termination Reason: \(process.terminationReason.rawValue)
                Sendmail exit status: \(process.terminationStatus)
                Details:
                - Sendmail Executable  : /usr/sbin/sendmail
                - Sendmail Options     : -t
                - Sendmail Timeout     : 10,000 mSec
                - Sendmail Error output: \(e.count) bytes
                - Sendmail Output      : \(d.count) bytes
                Below the output of sendmail is given in the following block format:
                (----- Email input -----)
                ...
                (----- Standard Error -----)
                ...
                (----- Standard Out -----)
                ...
                (----- End of output -----)

                (----- Email input -----)
                \(mail)
                (----- Standard Error -----)
                \(String(bytes: e, encoding: .utf8) ?? "")
                (----- Standard Out -----)
                \(String(bytes: d, encoding: .utf8) ?? "")
                (----- End of output -----)
                """

                let errorFileName = "sendmail-error-log-" + now
                if
                    let errorFileUrl = Urls.domainLoggingDir(for: domainName)?.appendingPathComponent(errorFileName).appendingPathExtension("txt"),
                    let dumpData = dump.data(using: .utf8),
                    ((try? dumpData.write(to: errorFileUrl)) != nil) {
                } else {
                    Log.atError?.log("Cannot create sendmail error file, content is: \n\(dump)\n")
                }
            }

        } catch let error {

            Log.atError?.log("Exception occured during sendmail execution, message = \(error.localizedDescription)")
        }
    }
}

This routine should be called with a string formatted like:

    let email: String =
    """
    To: \(emailAddress)
    From: \(fromAddress)
    Content-Type: text/html;
    Subject: Confirm Account Creation at \(domain.name)\n
    \(message)
    """
Stotinka answered 30/9, 2019 at 8:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.