Blog post

Choosing the best way to send emails in an iOS app

Illustration: Choosing the best way to send emails in an iOS app

When building apps for Apple platforms, you may want to enable users to send emails. Whether this is for feedback, integrating with a business workflow, or sharing content, there are several approaches to integrating email functionality into your app. Modern apps for Apple platforms built using SwiftUI and/or UIKit might run on an iPad, iPhone, Mac, and perhaps even an Apple Vision Pro.

In this post, we’ll explore the options and their tradeoffs so you can choose the best method for your app.

Probably the most common entry point to sending an email from an app is the system share sheet. Use this to enable users to share content when you, as the developer, don’t have a particular destination in mind. Apple’s Mail app is available as a destination in the share sheet, and other email apps can provide their own extensions to appear here. Users might choose to send by email, but they could also share using a messaging app, post to their website, or pass the content in Shortcuts as part of a custom workflow. Any type of data can be shared, including text, links, and documents.

In SwiftUI, use ShareLink to share any items conforming to the Transferable protocol:

ShareLink(
    items: [itemsToShare],
    subject: Text("Prefilled subject for Apple Mail"),
    message: Text("Additional text to be shared")
)

As far as I can tell, the subject is only used by Apple Mail and is unavailable to the receiving app in the public API of NSExtensionItem. Does anyone feel like submitting an interoperability request to make this available to all of us (at least in the European Union)? 😀

The message is useful because the elements of itemsToShare must all have the same type, and it’s often helpful to add a description alongside a link or document. If the elements of itemsToShare are strings, the message in the receiving app is equivalent to having an extra string in itemsToShare.

In UIKit, use UIActivityViewController. The activityItems used in this API are vaguely typed as [Any], but this means we can easily share items of different types:

let activityVC = UIActivityViewController(activityItems: [itemsToShare], applicationActivities: nil)

if let popoverPC = activityVC.popoverPresentationController {
    popoverPC.sourceView = sendingView
    popoverPC.sourceRect = sendingView.bounds
}

present(activityVC, animated: true)

It’s possible to prefill the email subject for Apple Mail only by providing an object conforming to UIActivityItemSource as an activity item and implementing the activityViewController(_:subjectForActivityType:) method.

There’s a newer API to create a UIActivityViewController with an object conforming to UIActivityItemsConfigurationReading, but the last time I tried this API, I wasn’t successful, because most apps expect to receive data in the (somewhat messy) structure produced by the activityItems API.

The share sheet isn’t appropriate for situations like customer support and other business workflows that may require an email sent to a specific address. Users would have to manually select Mail from the various sharing destinations and then enter the destination address. Below we’ll look at two other ways to send emails that are more suitable for those situations.

The mailto URL scheme was first defined in RFC 1738 in 1994. Links using this scheme open the default email app on the user’s device and start a new draft message. You can prefill the recipient, subject, and body, if needed.

To open a mailto link, first create the URL. You can do this succinctly by creating a URL from a string, but I think the more verbose URLComponents API is nicer because it avoids the need to deal with percent encoding:

let mailtoURL: URL = {
    var components = URLComponents()
    components.scheme = "mailto"
    // The path determines the prefilled destination address. This can be omitted to leave the "To" field blank initially.
    components.path = "[email protected]"
    components.queryItems = [
        URLQueryItem(name: "subject", value: "Prefilled subject"),
        URLQueryItem(name: "body", value: "Prefilled body"),
    ]
    return components.url!
}()

Check if the URL can be opened. For example, this will be false on iOS Simulator:

private static let canOpenMailtoURLs = UIApplication.shared.canOpenURL(mailtoURL)

Note: According to the documentation, to use canOpenURL like this, we should add mailto in an array under the LSApplicationQueriesSchemes key in our app’s Info.plist. However, on iOS 18.2, I found this wasn’t needed. Perhaps querying mailto is implicitly allowed.

We can then let the user trigger this from a Link in SwiftUI:

if Self.canOpenMailtoURLs {
    Link("Send Email", destination: mailtoURL)
}

If you’re using UIKit, call this API in response to a button action or similar:

UIApplication.shared.open(mailtoURL)

mailto links are beautifully interoperable. On Apple platforms, they work across iOS, macOS, and visionOS. Additionally, these links aren’t just for Apple Mail: mailto links will open whichever default email app the user has set in the Settings app under Apps > Default Apps > Email. Thanks to John Brayton for pointing this out to me.

Since mailto links open a different app, users can refer back to your app while composing their message, which works especially well when using multiple windows on iPad. I wrote about similar benefits regarding opening web pages in my 2019 post: Open links in Safari, not Safari View Controller.

The key downside of mailto links is that they’re limited to sending text, so they can’t be used to send attachments like documents.

MFMailComposeViewController — To send emails with attachments

If you need to send attachments to a specific email address, MFMailComposeViewController is the only option for iOS apps.

To use this API, first add import MessageUI at the top of your file:

if MFMailComposeViewController.canSendMail() {
    let mailComposer = MFMailComposeViewController()
    mailComposer.mailComposeDelegate = self
    mailComposer.setToRecipients(["[email protected]"])
    mailComposer.setSubject("Prefilled subject")
    mailComposer.setMessageBody("Prefilled body", isHTML: false)
    mailComposer.addAttachmentData(fileData, mimeType: "application/pdf", fileName: "example.pdf")

    present(mailComposer, animated: true)
}

In addition to supporting attachments, a feature of MFMailComposeViewController that might be advantageous is that it keeps users in your app after they finish sending an email. You can also use this API to prefill an email with HTML content if you really want to.

Of course, you can use MFMailComposeViewController without adding an attachment, but I recommend mailto links instead due to a couple of significant drawbacks:

  • No Mac compatibilityMFMailComposeViewController doesn’t work in Mac Catalyst apps or iOS apps running on Apple silicon Macs. On a Mac, canSendMail always returns false.

  • Exclusive to Apple Mail — This API requires Apple Mail to be configured on the device, so it won’t work if a user has only configured another email app. This would be fine if this weren’t the only way to send attachments to a prefilled address. How about another interoperability request?

If you use MFMailComposeViewController without needing attachments, I recommend falling back to opening a mailto link if MFMailComposeViewController.canSendMail() is false to handle these situations. This takes a little more work but, in some ways, gives the best of both worlds. This is the approach we use for support emails in our PDF Viewer app.

Choosing the right approach

Unfortunately, there’s no single correct answer for the best API to use to send email from an iOS app. Here’s a summary of the features supported by each API:

API Prefilled recipient Attachments Mac Any email app
ShareLink or UIActivityViewController
mailto link
MFMailComposeViewController

In our iOS PDF SDK, we need to send PDF documents, so mailto links aren’t appropriate. We support sending documents with both the system share sheet and MFMailComposeViewController through our PDFDocumentSharingViewController with the .activity and .email destinations, respectively.

We can hope for a bright future where a single API can meet all these requirements, but sadly it seems unlikely that Apple will add options that don’t favour its own apps unless there’s regulatory pressure.

Author
Douglas Hill
Douglas Hill iOS Team Lead

Douglas makes the most of our fully remote setup by frequently traveling around Europe by train. He’s a proponent of iPad as a productivity platform and is obsessive about the details of great user experiences. He’s also an organizer of the NSLondon meetup.

Free trial Ready to get started?
Free trial