import Foundation /// Send an email via Mail.app using AppleScript. /// /// Mail.app was chosen as the send path (vs. direct SMTP) because: /// - Mail.app is already authenticated with the Apple ID — no credentials need to be stored. /// - The `send` AppleScript command is simple and well-tested on macOS. /// - Any outbound message appears in the Sent folder automatically, maintaining a consistent /// mail history without additional server-side bookkeeping. /// /// Mail.app must be running and the user must have granted automation access. public final class Sender: @unchecked Sendable { public static let shared = Sender() private init() {} public struct SendRequest { public let to: [String] public let cc: [String] public let bcc: [String] public let subject: String public let body: String public let isHTML: Bool public init(to: [String], cc: [String] = [], bcc: [String] = [], subject: String, body: String, isHTML: Bool = false) { self.to = to self.cc = cc self.bcc = bcc self.subject = subject self.body = body self.isHTML = isHTML } } public struct SendResult { public let success: Bool public let error: String? } /// Send a message via Mail.app AppleScript bridge. /// /// Constructs an AppleScript that: /// 1. Creates a new outgoing message. /// 2. Adds recipients. /// 3. Sets subject + body. /// 4. Calls `send`. /// /// Runs synchronously on the calling thread — callers should dispatch to a background queue. public func send(_ request: SendRequest) -> SendResult { guard !request.to.isEmpty else { return SendResult(success: false, error: "No recipients specified") } let toLines = request.to.map { "make new to recipient at end of to recipients of theMessage with properties {address:\"\($0)\"}" } let ccLines = request.cc.map { "make new cc recipient at end of cc recipients of theMessage with properties {address:\"\($0)\"}" } let bccLines = request.bcc.map { "make new bcc recipient at end of bcc recipients of theMessage with properties {address:\"\($0)\"}" } // Escape backslashes and double-quotes in body/subject let safeSubject = request.subject .replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "\"", with: "\\\"") let safeBody = request.body .replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "\"", with: "\\\"") let allRecipientLines = (toLines + ccLines + bccLines).joined(separator: "\n ") let script = """ tell application "Mail" set theMessage to make new outgoing message with properties {subject:"\(safeSubject)", content:"\(safeBody)", visible:false} tell theMessage \(allRecipientLines) end tell send theMessage end tell """ var errorDict: NSDictionary? guard let appleScript = NSAppleScript(source: script) else { return SendResult(success: false, error: "Failed to compile AppleScript") } appleScript.executeAndReturnError(&errorDict) if let err = errorDict { let msg = err[NSAppleScript.errorMessage] as? String ?? "Unknown AppleScript error" NSLog("IMailSender: AppleScript error: \(msg)") return SendResult(success: false, error: msg) } NSLog("IMailSender: sent '\(request.subject)' to \(request.to.joined(separator: ", "))") return SendResult(success: true, error: nil) } }