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.
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.
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()}| Mail controller | Type | Description |
|---|---|---|
mailComposeDelegateMFMailComposeViewControllerDelegate<MFMailComposeController> | nullReceives result and optional NSError fields. | MFMailComposeViewControllerDelegate<MFMailComposeController> | null | Receives result and optional NSError fields. |
setSubject(subject: string) => voidSets the mail subject. | (subject: string) => void | Sets the mail subject. |
setToRecipients(recipients: string[]) => voidSets primary recipients. | (recipients: string[]) => void | Sets primary recipients. |
setCcRecipients(recipients: string[]) => voidSets copied recipients. | (recipients: string[]) => void | Sets copied recipients. |
setBccRecipients(recipients: string[]) => voidSets blind copied recipients. | (recipients: string[]) => void | Sets blind copied recipients. |
setMessageBody(body: string, isHTML: boolean) => voidSets plain text or HTML body. | (body: string, isHTML: boolean) => void | Sets plain text or HTML body. |
setPreferredSendingEmailAddress(emailAddress: string) => voidSelects a preferred sending account. | (emailAddress: string) => void | Selects 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) => void | Adds 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 controller | Type | Description |
|---|---|---|
messageComposeDelegateMFMessageComposeViewControllerDelegate<MFMessageComposeController> | nullReceives completion or cancellation. | MFMessageComposeViewControllerDelegate<MFMessageComposeController> | null | Receives completion or cancellation. |
recipientsstring[] | undefinedRecipient addresses; undefined clears them. | string[] | undefined | Recipient addresses; undefined clears them. |
bodystring | undefinedMessage body; undefined clears it. | string | undefined | Message body; undefined clears it. |
subjectstring | undefinedSubject when supported; undefined clears it. | string | undefined | Subject 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. | () => void | Disables 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 ) => boolean | Adds 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) => boolean | Adds 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. |
| iOS | Android | Web | |
|---|---|---|---|
| System composer | Throws | Throws | |
| Message | System composer | Throws | Throws |
| Checks | SDK capability | False | False |
ArrayBuffer,
files as file URL strings. Remote URLs need downloading first.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.
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.