Add email flags and unsubscribe features
All checks were successful
Build And Test / publish (push) Successful in 48s

- Add set_email_flags tool for IMAP flags (standard + custom keywords)
- Add unsubscribe_email tool with List-Unsubscribe header parsing
- Support RFC 8058 one-click unsubscribe
- Add UnsubscribeInfo model to email responses
- Add data field to OperationResult for extra context
- Include test.sh script for MCP server testing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 17:23:49 -08:00
parent 47f483ea1b
commit ba57c5fba4
5 changed files with 321 additions and 2 deletions

View File

@@ -17,9 +17,11 @@ from models.email_models import (
EmailSummary,
Email,
EmailList,
UnsubscribeInfo,
)
from models.common import OperationResult
from config import Settings
from services.unsubscribe_service import parse_unsubscribe_header, execute_unsubscribe
def decode_mime_header(header) -> str:
@@ -239,11 +241,14 @@ class EmailService:
# Get headers
headers = {}
for key in ["Message-ID", "In-Reply-To", "References", "X-Priority"]:
for key in ["Message-ID", "In-Reply-To", "References", "X-Priority", "List-Unsubscribe", "List-Unsubscribe-Post"]:
value = msg.get(key)
if value:
headers[key] = decode_mime_header(value)
# Parse unsubscribe info
unsubscribe_info = parse_unsubscribe_header(msg)
return Email(
id=str(uid),
mailbox=mailbox,
@@ -262,6 +267,7 @@ class EmailService:
headers=headers,
in_reply_to=headers.get("In-Reply-To"),
references=headers.get("References", "").split() if headers.get("References") else [],
unsubscribe=unsubscribe_info if unsubscribe_info.available else None,
)
def search_emails(
@@ -568,3 +574,76 @@ class EmailService:
if name in trash_names or b"\\Trash" in flags:
return name
return None
def set_flags(
self,
email_id: str,
mailbox: str,
add_flags: Optional[list[str]] = None,
remove_flags: Optional[list[str]] = None,
) -> OperationResult:
"""
Set or remove IMAP flags on an email.
Standard flags: \\Seen, \\Answered, \\Flagged, \\Deleted, \\Draft
Custom keywords are also supported (server-dependent).
Args:
email_id: The unique ID of the email
mailbox: The mailbox containing the email
add_flags: List of flags to add
remove_flags: List of flags to remove
"""
try:
client = self._get_imap_client()
client.select_folder(mailbox)
uid = int(email_id)
if add_flags:
client.add_flags([uid], add_flags)
if remove_flags:
client.remove_flags([uid], remove_flags)
return OperationResult(
success=True,
message=f"Flags updated for email {email_id}",
id=email_id,
data={
"added": add_flags or [],
"removed": remove_flags or [],
}
)
except Exception as e:
return OperationResult(success=False, message=str(e))
async def unsubscribe(
self,
mailbox: str,
email_id: str,
) -> OperationResult:
"""
Attempt to unsubscribe from a mailing list based on email headers.
Args:
mailbox: The mailbox containing the email
email_id: The unique ID of the email
Returns:
OperationResult with unsubscribe status
"""
# First, read the email to get unsubscribe info
email_data = self.read_email(mailbox, email_id)
if not email_data:
return OperationResult(
success=False,
message=f"Email {email_id} not found in {mailbox}"
)
if not email_data.unsubscribe:
return OperationResult(
success=False,
message="This email does not have unsubscribe information"
)
# Execute unsubscribe
return await execute_unsubscribe(email_data.unsubscribe)