blog
Send email in Swift: iOS composer and MailSlurp API
Choose the right Swift email path: open Apple's iOS mail composer for a person to send, or use MailSlurp to send and test real email from a trusted backend or test target.

"Send an email from Swift" can mean two quite different things. A customer may tap a support button and finish a message in Apple's familiar Mail sheet. Or your application may need to send a receipt, password reset, or one-time code without asking someone to press Send.
The distinction matters. One route is a user interface. The other is an application workflow. Pick the wrong one and you can end up with a compose window in an automated test, or an API key tucked inside an app bundle where it does not belong.
This guide covers both routes and shows how to test the result with a real MailSlurp inbox.
Choose the Swift email path that matches the job
Use Apple's MFMailComposeViewController when a person should review and send the message from an iPhone or iPad. It is a good fit for "contact support", "share feedback", or "email this receipt to myself" actions.
Use an email API from a trusted backend, command-line tool, or test target when the application owns the send. Password resets, sign-in codes, receipts, alerts, and CI checks should not depend on a person approving a compose sheet.
MailSlurp gives that second path real inboxes as well as sending and receiving APIs. A test can create a fresh address, hand it to the application, and inspect the message that actually arrives. No personal inbox has to become the office test cupboard.
Open the iOS mail composer
Apple's MessageUI framework provides the standard mail composer. Check canSendMail() before presenting it, set a delegate so the sheet can close, and populate the fields before presentation:
import MessageUI
import UIKit
final class SupportMailPresenter: NSObject, MFMailComposeViewControllerDelegate {
func present(from viewController: UIViewController) {
guard MFMailComposeViewController.canSendMail() else {
// Show a helpful fallback, such as copying the support address.
return
}
let composer = MFMailComposeViewController()
composer.mailComposeDelegate = self
composer.setToRecipients(["support@example.com"])
composer.setSubject("A question from the iOS app")
composer.setMessageBody(
"Hello, I could use a hand with...",
isHTML: false
)
viewController.present(composer, animated: true)
}
func mailComposeController(
_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?
) {
controller.dismiss(animated: true)
}
}
The sheet lets the person edit, cancel, or queue the message in Mail. It does not prove that a message reached the destination. Apple makes that boundary explicit in the MFMailComposeViewController documentation.
The iOS Simulator commonly has no Mail account configured, so canSendMail() can return false there. Exercise the success path on a configured device and keep a useful fallback for everyone else.
Send and receive with the MailSlurp Swift client
For a backend, test target, or command-line utility, add the official MailSlurp Swift client. The current package uses Swift tools 6.0. In Xcode, choose File > Add Package Dependencies and enter the repository URL:
https://github.com/mailslurp/mailslurp-client-swift
For a Swift package, add version 17.2.0 or a newer compatible release to Package.swift and attach the mailslurp product to the target that needs it. The Swift SDK guide has a complete manifest.
Create a free MailSlurp account and keep the API key in an environment variable for local or CI use:
export MAILSLURP_API_KEY="your-api-key"
Do not place the key in a shipping iOS or macOS application. Let a trusted service own it and expose only the action an authenticated customer is allowed to perform.
Here is a complete smoke test. It creates a private inbox, sends a message to itself, waits for delivery, checks the content, and removes the inbox afterward:
import Foundation
import PromiseKit
import mailslurp
let apiKey = ProcessInfo.processInfo.environment["MAILSLURP_API_KEY"] ?? ""
precondition(!apiKey.isEmpty, "Set MAILSLURP_API_KEY before running this example")
let configuration = mailslurpAPIConfiguration(
customHeaders: ["x-api-key": apiKey]
)
do {
let inbox = try hang(
InboxControllerAPI.createInboxWithDefaults(
apiConfiguration: configuration
)
)
defer {
try? hang(
InboxControllerAPI.deleteInbox(
inboxId: inbox._id,
apiConfiguration: configuration
)
)
}
let options = SendEmailOptions(
to: [inbox.emailAddress],
subject: "Swift email smoke test",
body: "The whole email path works. Nice."
)
_ = try hang(
InboxControllerAPI.sendEmailAndConfirm(
inboxId: inbox._id,
sendEmailOptions: options,
apiConfiguration: configuration
)
)
let email = try hang(
WaitForControllerAPI.waitForLatestEmail(
inboxId: inbox._id,
timeout: 120_000,
unreadOnly: true,
apiConfiguration: configuration
)
)
precondition(email.subject == "Swift email smoke test")
precondition(email.body?.contains("whole email path works") == true)
print("Received: \(email.subject ?? "(no subject)")")
} catch {
fputs("MailSlurp request failed: \(error)\n", stderr)
exit(EXIT_FAILURE)
}
Run the package with swift run. PromiseKit's hang helper is intended for command-line and test utilities; inside application code, keep the returned promises asynchronous with then, done, and catch so the main thread stays free.
The send confirmation proves that MailSlurp accepted the outgoing message. The wait and content assertions prove that the message arrived in a form a customer could use. That second check is where broken templates, wrong environments, and awkward routing mistakes tend to reveal themselves.
Test a password reset or one-time code
A useful email test starts before the email exists:
- Create a fresh MailSlurp inbox for the test.
- Enter
inbox.emailAddressin the real signup or reset screen. - Wait for the message with
WaitForControllerAPI.waitForLatestEmail. - Check the intended recipient, subject, and the expected link or code.
- Open the link or submit the code through the application.
- Delete the inbox in teardown.
Stopping at "an email arrived" leaves the most interesting failure unchecked. A reset link can point to the wrong host. An OTP can be present but expired. A beautiful receipt can belong to yesterday's test. Following the customer journey to the end turns an inbox check into a release check.
Use one inbox per parallel test when possible. It removes message-order races and makes a failure easier to understand. unreadOnly: true is helpful when a test deliberately keeps an inbox between steps.
Should Swift connect to SMTP directly?
Server-side Swift can send over SMTP with a maintained library, and MailSlurp can provide SMTP credentials for an inbox. For most application code, the HTTP client is the tidier starting point: request and response models are typed, authentication lives in one configuration, and the same controllers can wait for received email.
If an existing service already speaks SMTP, use the credentials returned for that inbox and match the client to the secure host and port. The SMTP and IMAP guide covers the available access details. Avoid inventing hostnames or reusing credentials from another inbox.
Troubleshoot the common Swift email snags
- The API returns 401: make sure the running process receives
MAILSLURP_API_KEYand the configuration adds thex-api-keyheader. Check without printing the secret. inbox.idlooks like the wrong type: generated MailSlurp models inherit fromNSObject; the inbox UUID isinbox._id.- Waiting times out: confirm the application used the new inbox address, allow enough time for the real journey, and use an unread-only wait when earlier messages exist.
- The mail composer does not open: call
canSendMail()first and test on a device with Mail configured. - A command-line process hangs: use PromiseKit's
hanghelper only on the main thread in a test or command-line tool. Keep promises asynchronous in app UI code. - Tests leave inboxes behind: put deletion in
defer, XCTest teardown, or the failure branch of the promise chain.
Keep the email check close to the customer
The best Swift email test is not the cleverest one. It is the one that follows the same path as the person waiting for a receipt or code, says clearly what broke, and cleans up its own crumbs.
Continue with the MailSlurp Swift SDK guide, browse the email API reference, or add the inbox flow to an email integration test. For templates that need to survive the small-screen gauntlet, device previews show the delivered message across real clients before it reaches customers.