81 lines
2.3 KiB
Swift
81 lines
2.3 KiB
Swift
import Foundation
|
|
import Models
|
|
|
|
/// Public SMTP client for sending email messages.
|
|
public actor SMTPClient {
|
|
private let host: String
|
|
private let port: Int
|
|
private let security: SMTPSecurity
|
|
private let credentials: Credentials
|
|
|
|
public init(host: String, port: Int, security: SMTPSecurity, credentials: Credentials) {
|
|
self.host = host
|
|
self.port = port
|
|
self.security = security
|
|
self.credentials = credentials
|
|
}
|
|
|
|
/// Send an outgoing email message through a full SMTP session.
|
|
/// Performs: connect → EHLO → [STARTTLS + re-EHLO] → AUTH → MAIL FROM → RCPT TO → DATA → QUIT
|
|
public func send(message: OutgoingMessage) async throws {
|
|
let connection = SMTPConnection(host: host, port: port, security: security)
|
|
let runner = SMTPCommandRunner(connection: connection)
|
|
let domain = MessageFormatter.domainFromEmail(message.from.address)
|
|
|
|
defer {
|
|
Task {
|
|
try? await runner.quit()
|
|
try? await connection.disconnect()
|
|
try? await connection.shutdown()
|
|
}
|
|
}
|
|
|
|
_ = try await connection.connect()
|
|
_ = try await runner.ehlo(hostname: domain)
|
|
|
|
if security == .starttls {
|
|
try await runner.startTLS()
|
|
_ = try await runner.ehlo(hostname: domain)
|
|
}
|
|
|
|
try await runner.authenticate(credentials: credentials)
|
|
|
|
try await runner.mailFrom(message.from.address)
|
|
|
|
// All recipients: to + cc + bcc
|
|
let allRecipients = message.to + message.cc + message.bcc
|
|
for recipient in allRecipients {
|
|
try await runner.rcptTo(recipient.address)
|
|
}
|
|
|
|
let formattedMessage = MessageFormatter.format(message)
|
|
try await runner.data(formattedMessage)
|
|
}
|
|
|
|
/// Test the SMTP connection by performing auth without sending.
|
|
/// Performs: connect → EHLO → [STARTTLS + re-EHLO] → AUTH → QUIT
|
|
public func testConnection() async throws {
|
|
let connection = SMTPConnection(host: host, port: port, security: security)
|
|
let runner = SMTPCommandRunner(connection: connection)
|
|
let domain = MessageFormatter.domainFromEmail(credentials.username)
|
|
|
|
defer {
|
|
Task {
|
|
try? await runner.quit()
|
|
try? await connection.disconnect()
|
|
try? await connection.shutdown()
|
|
}
|
|
}
|
|
|
|
_ = try await connection.connect()
|
|
_ = try await runner.ehlo(hostname: domain)
|
|
|
|
if security == .starttls {
|
|
try await runner.startTLS()
|
|
_ = try await runner.ehlo(hostname: domain)
|
|
}
|
|
|
|
try await runner.authenticate(credentials: credentials)
|
|
}
|
|
}
|