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.
ShareLink or UIActivityViewController — For general sharing
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.
mailto links — The best way to send emails
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 addmailto
in an array under theLSApplicationQueriesSchemes
key in our app’sInfo.plist
. However, on iOS 18.2, I found this wasn’t needed. Perhaps queryingmailto
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 compatibility —
MFMailComposeViewController
doesn’t work in Mac Catalyst apps or iOS apps running on Apple silicon Macs. On a Mac,canSendMail
always returnsfalse
. -
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.