Swift SMTP client.
- Connect securely through SSL/TLS when needed
- Authenticate with CRAM-MD5, LOGIN, PLAIN, or XOAUTH2
- Send emails with local file, HTML, and raw data attachments
- Add custom headers
- Documentation
macOS & Linux: Swift 5.2 or above.
You can add SwiftSMTP to your project using Swift Package Manager. If your project does not have a Package.swift file, create one by running swift package init in the root directory of your project. Then open Package.swift and add SwiftSMTP as a dependency. Be sure to add it to your desired targets as well:
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "MyProject",
products: [
.library(
name: "MyProject",
targets: ["MyProject"]),
],
dependencies: [
.package(url: "https://github.com/Kitura/Swift-SMTP", .upToNextMinor(from: "5.1.0")), // add the dependency
],
targets: [
.target(
name: "MyProject",
dependencies: ["SwiftSMTP"]), // add targets
.testTarget( // note "SwiftSMTP" (NO HYPHEN)
name: "MyProjectTests",
dependencies: ["MyProject"]),
]
)After adding the dependency and saving, run swift package generate-xcodeproj in the root directory of your project. This will fetch dependencies and create an Xcode project which you can open and begin editing.
Version 5.0.0 brings breaking changes. See the quick migration guide here.
Initialize an SMTP instance:
import SwiftSMTP
let smtp = SMTP(
hostname: "smtp.gmail.com", // SMTP server address
email: "user@gmail.com", // username to login
password: "password" // password to login
)Additional parameters of SMTP struct:
public init(hostname: String,
email: String,
password: String,
port: Int32 = 587,
tlsMode: TLSMode = .requireSTARTTLS,
tlsConfiguration: TLSConfiguration? = nil,
authMethods: [AuthMethod] = [],
domainName: String = "localhost",
timeout: UInt = 10)By default, the SMTP struct connects on port 587 and sends mail only if a TLS connection can be established. It also uses a TLSConfiguration that uses no backing certificates. View the docs for more configuration options.
Create a Mail object and use your SMTP handle to send it. To set the sender and receiver of an email, use the User struct:
let drLight = Mail.User(name: "Dr. Light", email: "drlight@gmail.com")
let megaman = Mail.User(name: "Megaman", email: "megaman@gmail.com")
let mail = Mail(
from: drLight,
to: [megaman],
subject: "Humans and robots living together in harmony and equality.",
text: "That was my ultimate wish."
)
smtp.send(mail) { (error) in
if let error = error {
print(error)
}
}Add Cc and Bcc:
let roll = Mail.User(name: "Roll", email: "roll@gmail.com")
let zero = Mail.User(name: "Zero", email: "zero@gmail.com")
let mail = Mail(
from: drLight,
to: [megaman],
cc: [roll],
bcc: [zero],
subject: "Robots should be used for the betterment of mankind.",
text: "Any other use would be...unethical."
)
smtp.send(mail)Create an Attachment, attach it to your Mail, and send it through the SMTP handle. Here's an example of how you can send the three supported types of attachments--a local file, HTML, and raw data:
// Create a file `Attachment`
let fileAttachment = Attachment(
filePath: "~/img.png",
// "CONTENT-ID" lets you reference this in another attachment
additionalHeaders: ["CONTENT-ID": "img001"]
)
// Create an HTML `Attachment`
let htmlAttachment = Attachment(
htmlContent: "<html>Here's an image: <img src=\"cid:img001\"/></html>",
// To reference `fileAttachment`
related: [fileAttachment]
)
// Create a data `Attachment`
let data = "{\"key\": \"hello world\"}".data(using: .utf8)!
let dataAttachment = Attachment(
data: data,
mime: "application/json",
name: "file.json",
// send as a standalone attachment
inline: false
)
// Create a `Mail` and include the `Attachment`s
let mail = Mail(
from: from,
to: [to],
subject: "Check out this image and JSON file!",
// The attachments we created earlier
attachments: [htmlAttachment, dataAttachment]
)
// Send the mail
smtp.send(mail)
/* Each type of attachment has additional parameters for further customization */let mail1: Mail = //...
let mail2: Mail = //...
smtp.send([mail1, mail2],
// This optional callback gets called after each `Mail` is sent.
// `mail` is the attempted `Mail`, `error` is the error if one occured.
progress: { (mail, error) in
},
// This optional callback gets called after all the mails have been sent.
// `sent` is an array of the successfully sent `Mail`s.
// `failed` is an array of (Mail, Error)--the failed `Mail`s and their corresponding errors.
completion: { (sent, failed) in
}
)Swift-SMTP can frame an RFC 3156 multipart/encrypted body when Mail.pgp is set. Encryption itself is out of scope — bring your own OpenPGP implementation (e.g. ObjectivePGP). The library's job is the MIME envelope: it labels the body multipart/encrypted; protocol="application/pgp-encrypted" and emits the two body parts in order, refusing to emit Mail.text so plaintext cannot leak alongside the ciphertext.
RFC 3156 §4 requires exactly two body parts:
application/pgp-encryptedcontainingVersion: 1- The ASCII-armored ciphertext (typically
application/octet-stream)
Both parts are supplied by the caller as Attachments, in that order:
// 1. The required Version part.
let versionPart = Attachment(
pgp: "Version: 1",
mime: "application/pgp-encrypted",
name: ""
)
// 2. The ciphertext. `cipherText` is the ASCII-armored output of your PGP
// encryption step ("-----BEGIN PGP MESSAGE----- ... -----END PGP MESSAGE-----").
let ciphertextPart = Attachment(
pgp: cipherText,
mime: "application/octet-stream",
name: "encrypted.asc"
)
let mail = Mail(
from: alice,
to: [bob],
subject: "Encrypted message",
text: "", // ignored when pgp:true
pgp: true,
attachments: [versionPart, ciphertextPart]
)
smtp.send(mail) { error in
if let error = error { print(error) }
}On the wire this produces:
Content-Type: multipart/encrypted; boundary="..."; protocol="application/pgp-encrypted"
--<boundary>
Content-Type: application/pgp-encrypted
Version: 1
--<boundary>
Content-Type: application/octet-stream
Content-Disposition: inline; filename="encrypted.asc"
-----BEGIN PGP MESSAGE-----
...ASCII-armored ciphertext...
-----END PGP MESSAGE-----
--<boundary>--
Notes:
Mail.textis dropped whenpgp == true. Any cleartext you want the recipient to see must be inside the encrypted payload.- If
attachmentsis empty,sendreturnsSMTPError.missingPGPAttachment— the library will not emit amultipart/encryptedenvelope with no body parts. - The library does not validate the MIME types of the two parts; the caller is responsible for supplying a correct Version part and ciphertext part.
Mail.render(to:) writes the fully-formed MIME body (Content-Type onward, no SMTP envelope and no message headers) to any OutputStream. Useful when you want to compose or inspect the body offline — for example, when feeding the pre-encryption MIME structure (a multipart/mixed of text + attachments, with pgp: false) into your encryption step:
let stream = OutputStream(toMemory: ())
stream.open()
try mail.render(to: stream)
let data = stream.property(forKey: .dataWrittenToMemoryStreamKey) as! Data
let body = String(decoding: data, as: UTF8.self)Inspired by Hedwig and Perfect-SMTP.
Apache v2.0
