diff --git a/Sources/VoiceMemo/Services/EmailService.swift b/Sources/VoiceMemo/Services/EmailService.swift index 100cb8e..7db5093 100644 --- a/Sources/VoiceMemo/Services/EmailService.swift +++ b/Sources/VoiceMemo/Services/EmailService.swift @@ -25,14 +25,25 @@ class EmailService { self.settings = settings } + // Helper for testing + static func parseRecipients(_ raw: String) -> String { + return raw.split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + .joined(separator: ",") + } + func sendEmail(subject: String, body: String, attachmentPath: String?) async throws { let gatewayUrlString = settings.fastmailUrl let token = settings.getFastmailToken() - let recipient = settings.recipientEmail + let rawRecipients = settings.recipientEmail + + // Clean up and validate recipients + let recipients = Self.parseRecipients(rawRecipients) guard !gatewayUrlString.isEmpty, let token = token, !token.isEmpty, - !recipient.isEmpty else { + !recipients.isEmpty else { throw EmailError.missingConfiguration } @@ -61,7 +72,7 @@ class EmailService { append("\(value)\r\n") } - appendField(name: "to", value: recipient) + appendField(name: "to", value: recipients) appendField(name: "subject", value: subject) appendField(name: "body", value: body) diff --git a/Sources/VoiceMemo/Views/SettingsView.swift b/Sources/VoiceMemo/Views/SettingsView.swift index 63a8260..54693db 100644 --- a/Sources/VoiceMemo/Views/SettingsView.swift +++ b/Sources/VoiceMemo/Views/SettingsView.swift @@ -629,11 +629,11 @@ struct SettingsView: View { } } - FormRow(label: "Recipient") { - TextField("your-email@example.com", text: $settings.recipientEmail) + FormRow(label: "Recipients") { + TextField("email1@example.com, email2@example.com", text: $settings.recipientEmail) .textFieldStyle(.roundedBorder) .frame(maxWidth: .infinity) - .help("Where the meeting summary will be sent") + .help("Where the meeting summary will be sent. Separate multiple emails with commas.") } } } diff --git a/Tests/VoiceMemoTests/EmailServiceTests.swift b/Tests/VoiceMemoTests/EmailServiceTests.swift new file mode 100644 index 0000000..ccd0e7f --- /dev/null +++ b/Tests/VoiceMemoTests/EmailServiceTests.swift @@ -0,0 +1,40 @@ +import XCTest +@testable import VoiceMemo + +final class EmailServiceTests: XCTestCase { + + func testParseRecipients_SingleEmail() { + let input = "test@example.com" + let expected = "test@example.com" + let result = EmailService.parseRecipients(input) + XCTAssertEqual(result, expected) + } + + func testParseRecipients_MultipleEmails() { + let input = "a@b.com,c@d.com" + let expected = "a@b.com,c@d.com" + let result = EmailService.parseRecipients(input) + XCTAssertEqual(result, expected) + } + + func testParseRecipients_WithSpaces() { + let input = " a@b.com , c@d.com " + let expected = "a@b.com,c@d.com" + let result = EmailService.parseRecipients(input) + XCTAssertEqual(result, expected) + } + + func testParseRecipients_EmptyParts() { + let input = "a@b.com,,c@d.com," + let expected = "a@b.com,c@d.com" + let result = EmailService.parseRecipients(input) + XCTAssertEqual(result, expected) + } + + func testParseRecipients_OnlySpaces() { + let input = " " + let expected = "" + let result = EmailService.parseRecipients(input) + XCTAssertEqual(result, expected) + } +} diff --git a/doc/10-email-notification.md b/doc/10-email-notification.md index 804aea8..95e6d4f 100644 --- a/doc/10-email-notification.md +++ b/doc/10-email-notification.md @@ -4,14 +4,14 @@ This document outlines the integration of automated email notifications using the [FastMail Gateway](https://github.com/mistbit/fastmail). ## Feature Description -The system will automatically generate a Markdown summary of the meeting and send it via email to a configured recipient upon successful completion of the processing pipeline. +The system will automatically generate a Markdown summary of the meeting and send it via email to configured recipients (supports multiple recipients) upon successful completion of the processing pipeline. ## Configuration Users can configure the email gateway in the application settings: - **Gateway URL**: The endpoint of the deployed FastMail service (e.g., `http://localhost:8080`). - **Authentication Token**: The secure token for accessing the gateway. -- **Recipient Email**: The email address where the summary should be sent. +- **Recipient Emails**: The email addresses where the summary should be sent. Separate multiple emails with commas. ## Workflow diff --git a/doc/10-email-notification.zh-CN.md b/doc/10-email-notification.zh-CN.md index c564f47..424686a 100644 --- a/doc/10-email-notification.zh-CN.md +++ b/doc/10-email-notification.zh-CN.md @@ -4,14 +4,14 @@ 本文档概述了使用 [FastMail Gateway](https://github.com/mistbit/fastmail) 集成自动邮件通知的功能。 ## 功能描述 -当处理流水线成功完成后,系统将自动生成会议的 Markdown 摘要,并通过邮件发送给配置的收件人。 +当处理流水线成功完成后,系统将自动生成会议的 Markdown 摘要,并通过邮件发送给配置的收件人(支持多个收件人)。 ## 配置 用户可以在应用设置中配置邮件网关: - **网关 URL (Gateway URL)**:部署的 FastMail 服务端点(例如 `http://localhost:8080`)。 - **认证令牌 (Authentication Token)**:用于访问网关的安全令牌。 -- **收件人邮箱 (Recipient Email)**:接收摘要的邮箱地址。 +- **收件人邮箱 (Recipient Emails)**:接收摘要的邮箱地址。支持多个邮箱,使用逗号分隔。 ## 工作流程 diff --git a/openspec/specs/email-notification/spec.md b/openspec/specs/email-notification/spec.md index bb5802a..390bb58 100644 --- a/openspec/specs/email-notification/spec.md +++ b/openspec/specs/email-notification/spec.md @@ -16,7 +16,7 @@ The system SHALL support configuration for the `fastmail` gateway. - **THEN** the user MUST be able to input: - `FastMail Gateway URL` (e.g., `http://localhost:8080`) - `FastMail Token` (for authentication) - - `Recipient Email` (the default "corresponding user" email) + - `Recipient Emails` (comma-separated email addresses) - **AND** these settings MUST be persisted securely (Token in Keychain). ### Requirement: Automated Email Sending @@ -24,13 +24,13 @@ The system SHALL automatically send an email with the meeting summary upon succe #### Scenario: Pipeline completes successfully - **WHEN** the meeting pipeline reaches the `completed` state (after transcription and summarization) -- **AND** the `FastMail Gateway URL` and `Recipient Email` are configured +- **AND** the `FastMail Gateway URL` and `Recipient Emails` are configured - **THEN** the system MUST generate the Markdown summary of the meeting - **AND** the system MUST send a POST request to the configured gateway URL - **Endpoint**: `/api/v1/send` (based on `fastmail` API) - **Headers**: `Authorization: Bearer ` - **Body**: - - `to`: `` + - `to`: `` (comma-separated) - `subject`: `Meeting Summary: ` - `body`: "Please find the attached meeting summary." (or the summary content itself if preferred) - `attachments`: The generated Markdown file