178

Im working on a simple swift app where the user inputs an email address and presses a button which opens the mail app, with the entered address in the address bar. I know how to do this in Objective-C, but I'm having trouble getting it to work in Swift.

Jesse.H
  • 1,813
  • 2
  • 11
  • 3

18 Answers18

316

You can use simple mailto: links in iOS to open the mail app.

let email = "foo@bar.com"
if let url = URL(string: "mailto:\(email)") {
  if #available(iOS 10.0, *) {
    UIApplication.shared.open(url)
  } else {
    UIApplication.shared.openURL(url)
  }    
}
Gabriel Lidenor
  • 2,905
  • 2
  • 25
  • 26
Stephen Groom
  • 3,887
  • 1
  • 15
  • 23
  • 112
    Maybe worthwile to add that this does not work in the simulator, only on the device... See http://stackoverflow.com/questions/26052815/how-can-i-launch-an-email-client-on-ios-using-swfit – Pieter Apr 24 '15 at 09:44
  • 5
    now You need to add "!" in the second line, for the NSURL NSURL(string: "mailto:\(email)")! – anthonyqz Nov 07 '15 at 19:58
  • 4
    why does it say this is only available on ios 10 or newer when the answer is clearly 3 yrs old – pete Jul 28 '17 at 05:19
  • 2
    Swift 4/iOS 10+ example: UIApplication.shared.open(url, options: [:], completionHandler: nil) Passing an empty dictionary for options produces the same result as calling openURL did. – luxo Dec 30 '17 at 02:36
  • 1
    You can also provide subject, body etc. See https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MailLinks/MailLinks.html – Andreas Kraft Aug 20 '18 at 17:36
185

While other answers are all correct, you can never know if the iPhone/iPad that is running your application has the Apple's Mail app installed or not as it can be deleted by the user.

It is better to support multiple email clients. Following code handles the email sending in a more graceful way. The flow of the code is:

  • If Mail app is installed, open Mail's composer pre-filled with provided data
  • Otherwise, try opening the Gmail app, then Outlook, then Yahoo mail, then Spark, in this order
  • If none of those clients are installed, fallback to default mailto:.. that prompts the user to install Apple's Mail app.

Code is written in Swift 5:

    import MessageUI
    import UIKit

    class SendEmailViewController: UIViewController, MFMailComposeViewControllerDelegate {
        
        @IBAction func sendEmail(_ sender: UIButton) {
            // Modify following variables with your text / recipient
            let recipientEmail = "test@email.com"
            let subject = "Multi client email support"
            let body = "This code supports sending email via multiple different email apps on iOS! :)"
            
            // Show default mail composer
            if MFMailComposeViewController.canSendMail() {
                let mail = MFMailComposeViewController()
                mail.mailComposeDelegate = self
                mail.setToRecipients([recipientEmail])
                mail.setSubject(subject)
                mail.setMessageBody(body, isHTML: false)
                
                present(mail, animated: true)
            
            // Show third party email composer if default Mail app is not present
            } else if let emailUrl = createEmailUrl(to: recipientEmail, subject: subject, body: body) {
                UIApplication.shared.open(emailUrl)
            }
        }
        
        private func createEmailUrl(to: String, subject: String, body: String) -> URL? {
            let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
            let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
            
            let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
            let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
            let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
            let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
            let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
            
            if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
                return gmailUrl
            } else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
                return outlookUrl
            } else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
                return yahooMail
            } else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
                return sparkUrl
            }
            
            return defaultUrl
        }
        
        func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
            controller.dismiss(animated: true)
        }
    }

You also have to add following code to Info.plist file that whitelists the URl query schemes that are used.

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>googlegmail</string>
    <string>ms-outlook</string>
    <string>readdle-spark</string>
    <string>ymail</string>
</array>
Derek Lee
  • 3,452
  • 3
  • 30
  • 39
Nace
  • 2,854
  • 1
  • 15
  • 21
  • 8
    Well done. This is the most complete answer and is easily extensible for other email client apps. IMHO, I don't think it's acceptable in late 2019 to just tell the person "sorry, you're out of luck" if they don't use the default Apple Mail app, as most other solutions suggest. This fixes that deficiency. – wildcat12 Dec 28 '19 at 17:07
  • Does this method work with HTML? I cannot get it display properly. – Matthew Bradshaw Jan 22 '20 at 17:11
  • @MatthewBradshaw you can support HTML for the default mail composer by setting `isHTML` in the above code to true. For other clients, it doesn't appear to be possible, for further reading see https://stackoverflow.com/questions/5620324/mailto-link-with-html-body – Nace Jan 22 '20 at 20:22
  • 1
    Thanks, this woks great. I modified it slightly to let user choose client of their preference (I am filtering them in advance with canOpenUrl). Btw body for Microsoft Outlook is working fine :-) – Filip Feb 29 '20 at 21:35
  • This is brilliant! Has anyone done this for SwiftUI? – Averett Apr 18 '20 at 07:19
  • 1
    I think, it should be .urlQueryAllowed not .urlHostAllowed – thetrutz Oct 29 '20 at 09:51
  • Thanks @Nace!, Your answer is very helpful for me – Jignesh Mayani Nov 19 '20 at 12:20
  • So in iOS 14, the user can change the default app. But what would I do if I don't want the user to compose a new email, but to just make them check their emails to click on a confirmation link and also use the default app choice. If I go the mailto: route, then the proper default mail application is used, but it shows the compose view which would only be confusing in my case. If I use message: it works nicely for Mail.app but does not invoke the default mail app, unless it is coincidentally Apple Mail. – Raphael Feb 12 '21 at 18:33
  • openURL() is deprecated btw, use this now: https://developer.apple.com/documentation/uikit/uiapplication/1648685-open – justColbs Jun 15 '21 at 20:01
  • ymail does not seem to be working. Has the url scheme changed? – Kevin Le - Khnle Jun 29 '21 at 03:36
  • How to add an attachment with openURL? – riven Oct 17 '22 at 13:53
  • Can you give a usage example ? – Chris Neve Nov 18 '22 at 16:15
  • I have created Swift package which alleviates apps enumeration and url building (to open an email app) - https://github.com/alex1704/EmailApps – alex1704 Jan 12 '23 at 14:40
63

I'm not sure if you want to switch to the mail app itself or just open and send an email. For the latter option linked to a button IBAction:

    import UIKit
    import MessageUI

    class ViewController: UIViewController, MFMailComposeViewControllerDelegate {

    @IBAction func launchEmail(sender: AnyObject) {

    var emailTitle = "Feedback"
    var messageBody = "Feature request or bug report?"
    var toRecipents = ["friend@stackoverflow.com"]
    var mc: MFMailComposeViewController = MFMailComposeViewController()
    mc.mailComposeDelegate = self
    mc.setSubject(emailTitle)
    mc.setMessageBody(messageBody, isHTML: false)
    mc.setToRecipients(toRecipents)

    self.presentViewController(mc, animated: true, completion: nil)
    }

    func mailComposeController(controller:MFMailComposeViewController, didFinishWithResult result:MFMailComposeResult, error:NSError) {
        switch result {
        case MFMailComposeResultCancelled:
            print("Mail cancelled")
        case MFMailComposeResultSaved:
            print("Mail saved")
        case MFMailComposeResultSent:
            print("Mail sent")
        case MFMailComposeResultFailed:
            print("Mail sent failure: \(error?.localizedDescription)")
        default:
            break
        }
        self.dismissViewControllerAnimated(true, completion: nil)
    }

    }
user3378170
  • 2,371
  • 22
  • 11
Steve Rosenberg
  • 19,348
  • 7
  • 46
  • 53
  • 1
    I am having issues where the mailComposeController delegate function is not being called. – AustinT Jan 22 '15 at 05:31
  • 3
    Add "import MessageUI" to your imports and be sure to add the "MFMailComposeViewControllerDelegate" option to your class declaration like: `class myClass: UIViewController, MFMailComposeViewControllerDelegate {` – Jalakoo Feb 21 '15 at 01:27
  • MFMailComposeViewController() return nil for me – ilan Apr 20 '15 at 07:21
  • 2
    Also having issues: `'NSInvalidArgumentException', reason: 'Application tried to present a nil modal view controller on target`. App crashes in some devices (iPhone 5, iPhone 6 and iPad Mini) – Spacemonkey Nov 23 '15 at 10:29
34

For Swift 4.2+ and iOS 9+

let appURL = URL(string: "mailto:test@example.com")!

if #available(iOS 10.0, *) {
    UIApplication.shared.open(appURL, options: [:], completionHandler: nil)
} else {
    UIApplication.shared.openURL(appURL)
}

Replace test@example.com with your desired email address.

You can also include a subject field, a message, and multiple recipients in the To, Cc, and Bcc fields:

mailto:foo@example.com?cc=bar@example.com&subject=Greetings%20from%20Cupertino!&body=Wish%20you%20were%20here!
Sabrina
  • 2,531
  • 1
  • 32
  • 30
26

In Swift 3 you make sure to add import MessageUI and needs conform to the MFMailComposeViewControllerDelegate protocol.

func sendEmail() {
  if MFMailComposeViewController.canSendMail() {
    let mail = MFMailComposeViewController()
    mail.mailComposeDelegate = self
    mail.setToRecipients(["ved.ios@yopmail.com"])
    mail.setMessageBody("<p>You're so awesome!</p>", isHTML: true)

    present(mail, animated: true)
  } else {
    // show failure alert
  }
}

Protocol:

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
  controller.dismiss(animated: true)
}
creeperspeak
  • 5,403
  • 1
  • 17
  • 38
Ved Rauniyar
  • 1,539
  • 14
  • 21
18

Swift 2, with availability check:

import MessageUI

if MFMailComposeViewController.canSendMail() {
    let mail = MFMailComposeViewController()
    mail.mailComposeDelegate = self
    mail.setToRecipients(["test@test.test"])
    mail.setSubject("Bla")
    mail.setMessageBody("<b>Blabla</b>", isHTML: true)
    presentViewController(mail, animated: true, completion: nil)
} else {
    print("Cannot send mail")
    // give feedback to the user
}


// MARK: - MFMailComposeViewControllerDelegate

func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
    switch result.rawValue {
    case MFMailComposeResultCancelled.rawValue:
        print("Cancelled")
    case MFMailComposeResultSaved.rawValue:
        print("Saved")
    case MFMailComposeResultSent.rawValue:
        print("Sent")
    case MFMailComposeResultFailed.rawValue:
        print("Error: \(error?.localizedDescription)")
    default:
        break
    }
    controller.dismissViewControllerAnimated(true, completion: nil)
}
OhadM
  • 4,687
  • 1
  • 47
  • 57
User
  • 31,811
  • 40
  • 131
  • 232
17

Here how it looks for Swift 4:

import MessageUI

if MFMailComposeViewController.canSendMail() {
    let mail = MFMailComposeViewController()
    mail.mailComposeDelegate = self
    mail.setToRecipients(["test@test.test"])
    mail.setSubject("Bla")
    mail.setMessageBody("<b>Blabla</b>", isHTML: true)
    present(mail, animated: true, completion: nil)
} else {
    print("Cannot send mail")
    // give feedback to the user
}

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        switch result.rawValue {
        case MFMailComposeResult.cancelled.rawValue:
            print("Cancelled")
        case MFMailComposeResult.saved.rawValue:
            print("Saved")
        case MFMailComposeResult.sent.rawValue:
            print("Sent")
        case MFMailComposeResult.failed.rawValue:
            print("Error: \(String(describing: error?.localizedDescription))")
        default:
            break
        }
        controller.dismiss(animated: true, completion: nil)
    }
Danylo Zatorsky
  • 5,856
  • 2
  • 25
  • 49
Yuval
  • 171
  • 1
  • 6
14

Updated answer from Stephen Groom for Swift 3

let email = "email@email.com"
let url = URL(string: "mailto:\(email)")
UIApplication.shared.openURL(url!)
Ben W
  • 311
  • 2
  • 5
14

Here's an update for Swift 4 if you're simply looking to open up the mail client via a URL:

let email = "foo@bar.com"
if let url = URL(string: "mailto:\(email)") {
   UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

This worked perfectly fine for me :)

Mirek
  • 470
  • 4
  • 18
Nii Mantse
  • 2,554
  • 2
  • 13
  • 10
10

This is a straight forward solution of 3 steps in Swift.

import MessageUI

Add to conform the Delegate

MFMailComposeViewControllerDelegate

And just create your method:

    func sendEmail() {
    if MFMailComposeViewController.canSendMail() {
        let mail = MFMailComposeViewController()
        mail.mailComposeDelegate = self
        mail.setToRecipients(["support@mail.com"])
        mail.setSubject("Support App")
        mail.setMessageBody("<p>Send us your issue!</p>", isHTML: true)
        presentViewController(mail, animated: true, completion: nil)
    } else {
        // show failure alert
    }
}

func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
    controller.dismissViewControllerAnimated(true, completion: nil)
}
Maria Ortega
  • 371
  • 4
  • 8
9

You should try sending with built-in mail composer, and if that fails, try with share:

func contactUs() {

    let email = "info@example.com" // insert your email here
    let subject = "your subject goes here"
    let bodyText = "your body text goes here"

    // https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontroller
    if MFMailComposeViewController.canSendMail() {

        let mailComposerVC = MFMailComposeViewController()
        mailComposerVC.mailComposeDelegate = self as? MFMailComposeViewControllerDelegate

        mailComposerVC.setToRecipients([email])
        mailComposerVC.setSubject(subject)
        mailComposerVC.setMessageBody(bodyText, isHTML: false)

        self.present(mailComposerVC, animated: true, completion: nil)

    } else {
        print("Device not configured to send emails, trying with share ...")

        let coded = "mailto:\(email)?subject=\(subject)&body=\(bodyText)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        if let emailURL = URL(string: coded!) {
            if #available(iOS 10.0, *) {
                if UIApplication.shared.canOpenURL(emailURL) {
                    UIApplication.shared.open(emailURL, options: [:], completionHandler: { (result) in
                        if !result {
                            print("Unable to send email.")
                        }
                    })
                }
            }
            else {
                UIApplication.shared.openURL(emailURL as URL)
            }
        }
    }
}
lenooh
  • 10,364
  • 5
  • 58
  • 49
7

For Swift 4.2 and above

let supportEmail = "abc@xyz.com"
if let emailURL = URL(string: "mailto:\(supportEmail)"), UIApplication.shared.canOpenURL(emailURL)
{
    UIApplication.shared.open(emailURL, options: [:], completionHandler: nil)
}

Give the user to choose many mail options(like iCloud, google, yahoo, Outlook.com - if no mail is pre-configured in his phone) to send email.

iHarshil
  • 739
  • 10
  • 22
  • 2
    In my case, with iOS 13, when calling UIApplication.shared.open, the OS would always show a dialog offering to install Mail.app (oh, and canOpenURL for "mailto" is always true, too), even if there are other mail apps. So this is definitely not working out. – NeverwinterMoon Nov 01 '19 at 06:31
  • 1
    still works for iOS 16, swift 5+ – Mishka Feb 03 '23 at 17:13
6

In the view controller from where you want your mail-app to open on the tap.

  • At the top of the file do, import MessageUI.
  • Put this function inside your Controller.

    func showMailComposer(){
    
      guard MFMailComposeViewController.canSendMail() else {
           return
      }
      let composer = MFMailComposeViewController()
      composer.mailComposeDelegate = self
      composer.setToRecipients(["abc@gmail.com"]) // email id of the recipient
      composer.setSubject("testing!!!")
      composer.setMessageBody("this is a test mail.", isHTML: false)
      present(composer, animated: true, completion: nil)
     }
    
  • Extend your View Controller and conform to the MFMailComposeViewControllerDelegate.

  • Put this method and handle the failure, sending of your mails.

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
      if let _ = error {
          controller.dismiss(animated: true, completion: nil)
          return
      }
      controller.dismiss(animated: true, completion: nil)
    }
    
Shiv Prakash
  • 172
  • 1
  • 11
5
@IBAction func launchEmail(sender: AnyObject) {
 if if MFMailComposeViewController.canSendMail() {
   var emailTitle = "Feedback"
   var messageBody = "Feature request or bug report?"
   var toRecipents = ["friend@stackoverflow.com"]
   var mc: MFMailComposeViewController = MFMailComposeViewController()
   mc.mailComposeDelegate = self
   mc.setSubject(emailTitle)
   mc.setMessageBody(messageBody, isHTML: false)
   mc.setToRecipients(toRecipents)

   self.present(mc, animated: true, completion: nil)
 } else {
   // show failure alert
 }
}

func mailComposeController(controller:MFMailComposeViewController, didFinishWithResult result:MFMailComposeResult, error:NSError) {
    switch result {
    case .cancelled:
        print("Mail cancelled")
    case .saved:
        print("Mail saved")
    case .sent:
        print("Mail sent")
    case .failed:
        print("Mail sent failure: \(error?.localizedDescription)")
    default:
        break
    }
    self.dismiss(animated: true, completion: nil)
}

Note that not all users have their device configure to send emails, which is why we need to check the result of canSendMail() before trying to send. Note also that you need to catch the didFinishWith callback in order to dismiss the mail window.

2

In my case I was just trying to open the mail app, without creating an email draft.

In case someone is stumbling upon this question and is actually trying to do the same thing here is the code I used:

private var openMailAppButton: some View { 
        Button {
            if let emailUrl = mailAppUrl() {
                UIApplication.shared.open(emailUrl)
            }
        } label: {
            Text("OPEN MAIL APP")
        }
    }

private func mailAppUrl() -> URL? {
        let gmailUrl = URL(string: "googlegmail://")
        let outlookUrl = URL(string: "ms-outlook://")
        let yahooMail = URL(string: "ymail://")
        let sparkUrl = URL(string: "readdle-spark://")
        let defaultUrl = URL(string: "message://")
        
        if let defaultUrl = defaultUrl, UIApplication.shared.canOpenURL(defaultUrl) {
            return defaultUrl
        } else if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
            return gmailUrl
        } else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
            return outlookUrl
        } else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
            return yahooMail
        } else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
            return sparkUrl
        }
        
        return defaultUrl
    }
razvan
  • 355
  • 4
  • 19
1

For those of us still lagging behind on Swift 2.3 here is Gordon's answer in our syntax:

let email = "foo@bar.com"
if let url = NSURL(string: "mailto:\(email)") {
   UIApplication.sharedApplication().openURL(url)
}
Paul Lehn
  • 3,202
  • 1
  • 24
  • 29
-1

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult,error: Swift.Error?) {

    controller.dismiss(animated: true, completion: nil)
}
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Vikram Parimi Sep 18 '22 at 16:56
-2

all answer is great but for me i like this more

extension ContentUsVC : MFMailComposeViewControllerDelegate {
    func sendEmail(emile:String) {
        if MFMailComposeViewController.canSendMail() {
            let mail = MFMailComposeViewController()
            mail.mailComposeDelegate = self
            mail.setToRecipients([emile])
            present(mail, animated: true)
        } else {
            UIPasteboard.general.string = emile
            self.view.makeToast(StaticString.copiedToClipboard[languageString],position: .top)
        }
        
    }
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        controller.dismiss(animated: true)
    }
}
  • self.view.makeToast(StaticString.copiedToClipboard[languageString],position: .top) this from library Toast_Swift but you can build with yourself or show for user you copied the text – asem elkhouli Mar 12 '23 at 19:00
  • Whenever possible, if the original asker does not ask for a library to do so and it is easy to perform an action without a library, I would not reccomend including an extra library. Also, if you must do so, please include the fact that you are using that library in your post, not in a comment below. –  Mar 18 '23 at 23:21