One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Message UI

Compose mail and messages with the iOS system UI

One.iOS.MFMailComposeViewController and One.iOS.MFMessageComposeViewController present Apple’s mail and message composers, prefilled from your app and sent by the user. Reach for them for support emails, invite texts, and share-by-message flows where the system UI beats a custom form.

This is iOS only. Off iOS the capability checks return false and every other call throws. Simulators and devices without configured services can report false; these are capability checks, and One never substitutes another mail or SMS app.

Usage

import { One } from 'one'
if (One.iOS.MFMailComposeViewController.canSendMail()) {
const composer = new One.iOS.MFMailComposeViewController()
composer.setToRecipients(['hello@example.com'])
composer.setSubject('Hello')
composer.setMessageBody('A message from the app', false)
composer.mailComposeDelegate = {
mailComposeController: (controller, result) => {
// result.didFinishWith: cancelled, saved, sent or failed.
// result.error preserves NSError domain, code and localizedDescription.
void controller.dismiss()
},
}
await composer.present()
}

The delegate owns dismissal. present resolves after presentation; the result arrives separately on the delegate callback.

How it works

The native controller stays owned by its delegate until the app dismisses it. present adapts view controller presentation to the current app presenter and resolves after the transition; dismiss resolves after closing. Swift delegate argument labels become a JavaScript object after the controller argument.

Mail’s static canSendMail returns the SDK capability. The message side adds canSendText, canSendAttachments, and canSendSubject, plus isSupportedAttachmentUTI for attachment types. Attachments use ArrayBuffer for native data and file URL strings for native URLs. Message attachment results preserve the SDK success and failure booleans, and attachments exposes the native URL and alternate filename dictionaries as records.

Presentation without an active presenter rejects, as do duplicate presentation and overlapping transitions. Mail or message presentation on an incapable device rejects as unavailable. The message result is cancelled, sent, or failed; mail adds saved.

import { One } from 'one'
if (One.iOS.MFMessageComposeViewController.canSendText()) {
const composer = new One.iOS.MFMessageComposeViewController()
composer.recipients = ['+15555550100']
composer.body = 'Hello'
composer.messageComposeDelegate = {
messageComposeViewController: (controller, result) => {
// result.didFinishWith: cancelled, sent or failed.
void controller.dismiss()
},
}
await composer.present()
}

API reference

Mail controllerTypeDescription
mailComposeDelegateMFMailComposeViewControllerDelegate<MFMailComposeController> | nullReceives result and optional NSError fields.
MFMailComposeViewControllerDelegate<MFMailComposeController> | nullReceives result and optional NSError fields.
setSubject(subject: string) => voidSets the mail subject.
(subject: string) => voidSets the mail subject.
setToRecipients(recipients: string[]) => voidSets primary recipients.
(recipients: string[]) => voidSets primary recipients.
setCcRecipients(recipients: string[]) => voidSets copied recipients.
(recipients: string[]) => voidSets copied recipients.
setBccRecipients(recipients: string[]) => voidSets blind copied recipients.
(recipients: string[]) => voidSets blind copied recipients.
setMessageBody(body: string, isHTML: boolean) => voidSets plain text or HTML body.
(body: string, isHTML: boolean) => voidSets plain text or HTML body.
setPreferredSendingEmailAddress(emailAddress: string) => voidSelects a preferred sending account.
(emailAddress: string) => voidSelects a preferred sending account.
addAttachmentData(attachment: ArrayBuffer, mimeType: string, fileName: string) => voidAdds native bytes with MIME type and filename.
(attachment: ArrayBuffer, mimeType: string, fileName: string) => voidAdds native bytes with MIME type and filename.
present(animated?: boolean) => Promise<void>Presents the configured system composer.
(animated?: boolean) => Promise<void>Presents the configured system composer.
dismiss(animated?: boolean) => Promise<void>Dismisses the system composer.
(animated?: boolean) => Promise<void>Dismisses the system composer.
Message controllerTypeDescription
messageComposeDelegateMFMessageComposeViewControllerDelegate<MFMessageComposeController> | nullReceives completion or cancellation.
MFMessageComposeViewControllerDelegate<MFMessageComposeController> | nullReceives completion or cancellation.
recipientsstring[] | undefinedRecipient addresses; undefined clears them.
string[] | undefinedRecipient addresses; undefined clears them.
bodystring | undefinedMessage body; undefined clears it.
string | undefinedMessage body; undefined clears it.
subjectstring | undefinedSubject when supported; undefined clears it.
string | undefinedSubject when supported; undefined clears it.
attachmentsMessageAttachment[]Native attachment URLs and alternate filenames.
MessageAttachment[]Native attachment URLs and alternate filenames.
disableUserAttachments() => voidDisables attachment insertion by the user.
() => voidDisables attachment insertion by the user.
addAttachmentData( attachment: ArrayBuffer, typeIdentifier: string, filename: string ) => booleanAdds native bytes with a type identifier and filename.
( attachment: ArrayBuffer, typeIdentifier: string, filename: string ) => booleanAdds native bytes with a type identifier and filename.
addAttachmentURL(attachmentURL: string, withAlternateFilename?: string) => booleanAdds a file URL with an optional display filename.
(attachmentURL: string, withAlternateFilename?: string) => booleanAdds a file URL with an optional display filename.
present(animated?: boolean) => Promise<void>Presents the configured system composer.
(animated?: boolean) => Promise<void>Presents the configured system composer.
dismiss(animated?: boolean) => Promise<void>Dismisses the system composer.
(animated?: boolean) => Promise<void>Dismisses the system composer.

Platform differences

iOSAndroidWeb
MailSystem composerThrowsThrows
MessageSystem composerThrowsThrows
ChecksSDK capabilityFalseFalse

Limits and gotchas

Examples

Send a support email with a log

import { One } from 'one'
async function supportEmail(log: ArrayBuffer) {
if (!One.iOS.MFMailComposeViewController.canSendMail()) return false
const composer = new One.iOS.MFMailComposeViewController()
composer.setToRecipients(['support@example.com'])
composer.setSubject('Support request')
composer.setMessageBody('Describe the issue below.\n\n', false)
composer.addAttachmentData(log, 'text/plain', 'app.log')
const sent = await new Promise<boolean>((resolve) => {
composer.mailComposeDelegate = {
mailComposeController: (controller, result) => {
void controller.dismiss()
resolve(result.didFinishWith === 'sent')
},
}
void composer.present()
})
return sent
}

The promise bridges the delegate into async flow, resolving true only for a sent message. Saved, cancelled, and failed drafts all resolve false after dismissing.

Invite by text message

import { One } from 'one'
async function inviteByText(phone: string, code: string) {
if (!One.iOS.MFMessageComposeViewController.canSendText()) return
const composer = new One.iOS.MFMessageComposeViewController()
composer.recipients = [phone]
composer.body = `Join me with code ${code}`
composer.messageComposeDelegate = {
messageComposeViewController: (controller) => {
void controller.dismiss()
},
}
await composer.present()
}

Prefilling recipient and body leaves the user one tap from sending. The delegate ignores the result beyond dismissing, since the invite flow needs no follow-up either way.

Edit this page on GitHub.