A modern, pure-Swift IMAP client library with async/await support. SwiftIMAP provides a clean, type-safe API for interacting with IMAP email servers without any C dependencies.
- Pure Swift: No C or Objective-C dependencies
- Modern Async/Await: Built with Swift Concurrency from the ground up
- Type-Safe: Strongly typed commands and responses
- Secure by Default: TLS 1.2+ with certificate validation
- Memory Efficient: Streaming support for large messages
- Custom Labels: IMAP keywords via raw flag support
- Well-Tested: Comprehensive unit test coverage
- Swift 5.10+
- macOS 13+, iOS 16+, tvOS 16+, watchOS 9+
Add SwiftIMAP to your Package.swift:
dependencies: [
.package(url: "https://github.com/HokuNZ/SwiftIMAP.git", from: "0.1.0")
]import SwiftIMAP
// Configure the client
let config = IMAPConfiguration(
hostname: "imap.example.com",
port: 993,
authMethod: .login(username: "user@example.com", password: "password")
)
// Create client
let client = IMAPClient(configuration: config)
// Connect and authenticate
try await client.connect()
// List mailboxes
let mailboxes = try await client.listMailboxes()
for mailbox in mailboxes {
print("Mailbox: \(mailbox.name)")
}
// Select a mailbox
let status = try await client.selectMailbox("INBOX")
print("Messages in INBOX: \(status.messages)")
// Search for messages
let messageUIDs = try await client.listMessages(in: "INBOX")
// Fetch a message
if let firstUID = messageUIDs.first {
if let message = try await client.fetchMessage(uid: firstUID, in: "INBOX") {
print("Subject: \(message.envelope?.subject ?? "No subject")")
}
}
// Disconnect
await client.disconnect()SwiftIMAP includes a command-line tool for testing IMAP connections.
swift build --product swift-imap-tester# Connect and list mailboxes
.build/debug/swift-imap-tester connect \
--host imap.gmail.com \
--username your@email.com \
--password yourpassword \
--command list
# Fetch a specific message
.build/debug/swift-imap-tester connect \
--host imap.gmail.com \
--username your@email.com \
--password yourpassword \
--command fetch \
--mailbox INBOX \
--uid 12345.build/debug/swift-imap-tester interactive \
--host imap.gmail.com \
--username your@email.com \
--password yourpasswordInteractive commands:
list [pattern]- List mailboxesselect <mailbox>- Select a mailboxstatus [mailbox]- Show mailbox statussearch- Search messages in selected mailboxfetch <uid>- Fetch a message by UIDcapability- Show server capabilitieshelp- Show available commandsquit- Disconnect and exit
let config = IMAPConfiguration(
hostname: "imap.example.com",
port: 993, // Default IMAP SSL/TLS port
tlsMode: .requireTLS, // .requireTLS, .startTLS, or .disabled
authMethod: .login(username: "user", password: "pass"),
connectionTimeout: 30, // seconds
commandTimeout: 60, // seconds
logLevel: .info // .none, .error, .warning, .info, .debug, .trace
)// Username/Password
.login(username: "user", password: "password")
// PLAIN mechanism
.plain(username: "user", password: "password")
// OAuth 2.0
.oauth2(username: "user", accessToken: "token")
// External (client certificate)
.external
// Custom SASL flow
.sasl(
mechanism: "PLAIN",
initialResponse: "base64-encoded",
responseHandler: { challenge in
// Return Base64-encoded response or "" for an empty response
return "next-base64-response"
}
)// List all mailboxes
let mailboxes = try await client.listMailboxes()
// List with pattern
let inboxSubfolders = try await client.listMailboxes(pattern: "INBOX.*")
// Get mailbox status without selecting
let status = try await client.mailboxStatus("Sent")// Search messages
let allMessages = try await client.listMessages(
in: "INBOX",
searchCriteria: .all
)
let unreadMessages = try await client.listMessages(
in: "INBOX",
searchCriteria: .unseen
)
let fromAlice = try await client.listMessages(
in: "INBOX",
searchCriteria: .from("alice@example.com")
)
// Fetch message summary
let summary = try await client.fetchMessage(
uid: 12345,
in: "INBOX",
items: [.uid, .flags, .envelope, .bodyStructure]
)
// Fetch full message body
let bodyData = try await client.fetchMessageBody(
uid: 12345,
in: "INBOX",
peek: true // Don't mark as read
)
// Add/remove custom label (IMAP keyword)
try await client.storeFlags(uid: 12345, in: "INBOX", flags: ["ProjectA"], action: .add)
let labeled = try await client.searchMessages(in: "INBOX", criteria: .keyword("ProjectA"))
// Labels map to IMAP keywords (Gmail's X-GM-LABELS extension is not implemented)Run the test suite:
swift testRun tests with verbose output:
swift test --enable-code-coverage --verboseSwiftIMAP is built with a layered architecture:
- Network Layer (SwiftNIO + NIOSSL): Handles TCP connections and TLS
- Protocol Layer (Parser/Encoder): Implements IMAP protocol parsing and encoding
- API Layer (IMAPClient): Provides high-level async/await APIs
- TLS 1.2+ is required by default
- Certificate validation enabled
- Sensitive data (passwords) never logged
- Support for certificate pinning via custom TLSConfiguration
Contributions are welcome! Please feel free to submit a Pull Request.
This repository is LLM-generated code, and we have done our best to be accurate, but 🤷♂️ it works for us.
SwiftIMAP is released under the MIT license. See LICENSE for details.