Send email with attachments in iOS using MFMailComposeViewController from the MessageUI framework. The MessageUI framework provides a built-in mail composer identical to Apple's native Mail app. You pre-populate recipients, subject, body, and file attachments. The user reviews and sends from the familiar system interface. Your app receives a callback with the send result.
What Is the MessageUI Framework
MessageUI provides two view controllers for communication features. MFMailComposeViewController handles email composition and delivery. MFMessageComposeViewController handles SMS and MMS messages. Both present standard system interfaces so users get a familiar experience without leaving your app. No web views or custom UI are required.
How to Set Up the Mail Composer Delegate
Import MessageUI and adopt the delegate protocol:
import MessageUI
Implement the delegate method in an extension:
extension AttachmentTableViewController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?) {
switch result {
case .cancelled:
print("Mail cancelled")
case .saved:
print("Mail saved as draft")
case .sent:
print("Mail sent successfully")
case .failed:
print("Failed to send: (error?.localizedDescription ?? "")")
@unknown default:
print("Unknown error")
}
dismiss(animated: true, completion: nil)
}
}
The delegate method fires when the user finishes with the mail composer. The result parameter indicates what happened. Always call dismiss in the delegate method to prevent a stuck interface.
How to Define MIME Types for Attachments
MIME types tell email systems what kind of data you are sending. Define an enum:
enum MIMEType: String {
case jpg = "image/jpeg"
case png = "image/png"
case doc = "application/msword"
case ppt = "application/vnd.ms-powerpoint"
case html = "text/html"
case pdf = "application/pdf"
init?(type: String) {
switch type.lowercased() {
case "jpg": self = .jpg
case "png": self = .png
case "doc": self = .doc
case "ppt": self = .ppt
case "html": self = .html
case "pdf": self = .pdf
default: return nil
}
}
}
The failing initializer returns nil for unsupported file types. Use optional binding to handle these cases.
How to Create and Present the Mail Composer
Build the core method that creates and presents the mail composer:
private func showEmail(attachment: String) {
guard MFMailComposeViewController.canSendMail() else {
print("This device cannot send mail.")
return
}
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setSubject("Great Photo and Doc")
mailComposer.setMessageBody("Hey, check this out!", isHTML: false)
mailComposer.setToRecipients(["[email protected]"])
let fileparts = attachment.components(separatedBy: ".")
let filename = fileparts[0]
let fileExtension = fileparts[1]
guard let filePath = Bundle.main.path(forResource: filename, ofType: fileExtension) else {
return
}
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)),
let mimeType = MIMEType(type: fileExtension) {
mailComposer.addAttachmentData(fileData,
mimeType: mimeType.rawValue,
fileName: filename)
present(mailComposer, animated: true, completion: nil)
}
}
MFMailComposeViewController.canSendMail checks if the device has an email account configured. Without this check, presenting the composer on an unconfigured device throws an error. Bundle.main.path(forResource:ofType:) locates files embedded in the app bundle. addAttachmentData accepts the raw data, MIME type string, and preferred filename.
How to Handle Table Row Selection for Email
Connect the mail composer to table view row selection:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedFile = filenames[indexPath.row]
showEmail(attachment: selectedFile)
}
Testing the Mail Composer on a Real Device
The mail composer requires a configured email account. Test on a physical iPhone or iPad with Mail set up. The simulator lacks the Mail app and cannot send email. When you tap a file, the mail composer slides up with the recipient pre-filled, subject line set, file attached, and message body pre-populated. Users can modify anything before sending.
How to Handle Different File Types
The MIMEType enum handles six common formats. Extend it for additional file types as needed. JSON files use application/json. XML files use application/xml. MP3 audio uses audio/mpeg. MP4 video uses video/mp4. ZIP archives use application/zip. CSV spreadsheets use text/csv. The IANA maintains the complete MIME type registry for reference.
Best Practices for In-App Email
Always check canSendMail before presenting the composer. iPod touches without iMessage and iPads without Mail configuration return false. Pre-populate subject and body to save users time but do not prevent editing. Handle the failed result with a user-friendly alert. Keep attachments under 25 megabytes as email systems typically reject larger files. Set isHTML to true only when your message body contains formatted HTML. Use false for plain text. Always call dismiss in the delegate method to prevent stuck interfaces.
Summary
This tutorial covered the complete implementation process from setup to working code. You now have a functional implementation that you can extend and customize for your own projects. The same patterns apply to related features in the iOS ecosystem.
Frequently Asked Questions
Can I test this on the iOS Simulator? Some features require a physical device with a compatible processor. The article notes specific sections where the Simulator is sufficient and where a real device is required.
What are the prerequisites for this tutorial? You need Xcode installed on a Mac and basic familiarity with Swift programming. No prior experience with the specific framework is required.