Files
MagnumOpus/Packages/MagnumOpusCore/Sources/SMTPClient/SMTPClient.swift
2026-03-14 05:25:03 +01:00

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)
}
}