Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions Sources/VoiceMemo/Services/EmailService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)

Expand Down
6 changes: 3 additions & 3 deletions Sources/VoiceMemo/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions Tests/VoiceMemoTests/EmailServiceTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
4 changes: 2 additions & 2 deletions doc/10-email-notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions doc/10-email-notification.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)**:接收摘要的邮箱地址。支持多个邮箱,使用逗号分隔

## 工作流程

Expand Down
6 changes: 3 additions & 3 deletions openspec/specs/email-notification/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ 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
The system SHALL automatically send an email with the meeting summary upon successful pipeline completion.

#### 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 <token>`
- **Body**:
- `to`: `<Recipient Email>`
- `to`: `<Recipient Emails>` (comma-separated)
- `subject`: `Meeting Summary: <Meeting Title>`
- `body`: "Please find the attached meeting summary." (or the summary content itself if preferred)
- `attachments`: The generated Markdown file
Expand Down