Reading time: 2 min

This recipe shows how to send mail from SwiftUI. You'll build a MailView by wrapping MessageUI's MFMailComposeViewController in a UIViewControllerRepresentable. You'll be able to:

  • Determine if you can send mail or not.
  • Pass subject, message and recipients to the view via a binding.
  • Attach files to the email.
  • Receive success or failure result after sending the email.

The end result will look like this.


This component is available as a Swift Package in this repo.

OK, first define the models for the data you'll pass to the MailView. These correspond to parameters that you can set on a MFMailComposeViewController:

struct ComposeMailData {
  let subject: String
  let recipients: [String]?
  let message: String
  let attachments: [AttachmentData]?

struct AttachmentData {
  let data: Data
  let mimeType: String
  let fileName: String

Then, add the wrapper code:

import SwiftUI
import UIKit
import MessageUI

typealias MailViewCallback = ((Result<MFMailComposeResult, Error>) -> Void)?

struct MailView: UIViewControllerRepresentable {
  @Environment(\.presentationMode) var presentation
  @Binding var data: ComposeMailData
  let callback: MailViewCallback

  class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
    @Binding var presentation: PresentationMode
    @Binding var data: ComposeMailData
    let callback: MailViewCallback

    init(presentation: Binding<PresentationMode>,
         data: Binding<ComposeMailData>,
         callback: MailViewCallback) {
      _presentation = presentation
      _data = data
      self.callback = callback

    func mailComposeController(_ controller: MFMailComposeViewController,
                               didFinishWith result: MFMailComposeResult,
                               error: Error?) {
      if let error = error {
      } else {

  func makeCoordinator() -> Coordinator {
    Coordinator(presentation: presentation, data: $data, callback: callback)

  func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
    let vc = MFMailComposeViewController()
    vc.mailComposeDelegate = context.coordinator
    vc.setMessageBody(data.message, isHTML: false)
    data.attachments?.forEach {
      vc.addAttachmentData($, mimeType: $0.mimeType, fileName: $0.fileName)
    return vc

  func updateUIViewController(_ uiViewController: MFMailComposeViewController,
                              context: UIViewControllerRepresentableContext<MailView>) {

  static var canSendMail: Bool {

That's it! You can now use your new MailView component like this:

struct MailViewTest: View {
  @State private var mailData = ComposeMailData(subject: "A subject",
                                                recipients: [""],
                                                message: "Here's a message",
                                                attachments: [AttachmentData(data: "Some text".data(using: .utf8)!,
                                                                             mimeType: "text/plain",
                                                                             fileName: "text.txt")
 @State private var showMailView = false

  var body: some View {
    Button(action: {
    }) {
      Text("Send mail")
    .sheet(isPresented: $showMailView) {
      MailView(data: $mailData) { result in

Next Post Previous Post