Add rich email notification with template file
All checks were successful
Build And Test / publish (push) Successful in 47s

- Created src/templates/notif.txt for customizable notification format
- Include sender, recipients, subject, date, body (truncated), attachments, unsubscribe info
- Fetch full email content for notifications

🤖 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 18:07:41 -08:00
parent 0c72186778
commit f557743c00
3 changed files with 108 additions and 6 deletions

View File

@@ -1,9 +1,35 @@
"""Pydantic models for webhook notification payloads.""" """Pydantic models for webhook notification payloads."""
import os
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
# Load notification template
_TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "..", "templates", "notif.txt")
_DEFAULT_TEMPLATE = """📧 New Email Received
From: {sender_name} <{sender_email}>
To: {recipients}
Subject: {subject}
Date: {date}
{body}
---
{attachments_line}
{unsubscribe_line}
"""
def _load_template() -> str:
"""Load notification template from file."""
try:
with open(_TEMPLATE_PATH, "r") as f:
return f.read()
except FileNotFoundError:
return _DEFAULT_TEMPLATE
class EmailNotificationPayload(BaseModel): class EmailNotificationPayload(BaseModel):
"""Webhook payload for new email notifications to Poke.""" """Webhook payload for new email notifications to Poke."""
@@ -27,23 +53,67 @@ class EmailNotificationPayload(BaseModel):
# Content # Content
subject: str subject: str
snippet: Optional[str] = None snippet: Optional[str] = None
body: Optional[str] = None
# Metadata # Metadata
date: datetime date: datetime
has_attachments: bool has_attachments: bool
is_flagged: bool is_flagged: bool
attachment_count: int = 0
# Unsubscribe info
can_unsubscribe: bool = False
unsubscribe_url: Optional[str] = None
# Threading # Threading
in_reply_to: Optional[str] = None in_reply_to: Optional[str] = None
def to_webhook_format(self) -> dict: def to_webhook_format(self) -> dict:
"""Convert to the format expected by Poke webhook.""" """Convert to the format expected by Poke webhook."""
# Format sender template = _load_template()
sender = self.from_name if self.from_name else self.from_email
# Build message # Format sender
message = f"New email from {sender}\nSubject: {self.subject}" sender_name = self.from_name or self.from_email.split("@")[0]
if self.snippet:
message += f"\n\n{self.snippet}" # Format recipients
recipients = ", ".join(self.to_emails[:3])
if len(self.to_emails) > 3:
recipients += f" (+{len(self.to_emails) - 3} more)"
# Format date
date_str = self.date.strftime("%b %d, %Y at %H:%M")
# Format body (truncate if too long)
body = self.body or self.snippet or "(No content)"
max_body_len = 500
if len(body) > max_body_len:
body = body[:max_body_len].rsplit(" ", 1)[0] + "..."
# Format attachments line
if self.has_attachments:
attachments_line = f"📎 {self.attachment_count} attachment(s)"
else:
attachments_line = ""
# Format unsubscribe line
if self.can_unsubscribe:
unsubscribe_line = "🔕 This email can be unsubscribed"
else:
unsubscribe_line = ""
# Build message from template
message = template.format(
sender_name=sender_name,
sender_email=self.from_email,
recipients=recipients,
subject=self.subject,
date=date_str,
body=body,
attachments_line=attachments_line,
unsubscribe_line=unsubscribe_line,
)
# Clean up empty lines at the end
message = message.strip()
return {"message": message} return {"message": message}

View File

@@ -278,6 +278,22 @@ class EmailMonitor:
self, email: EmailSummary, mailbox: str self, email: EmailSummary, mailbox: str
) -> tuple[bool, Optional[str]]: ) -> tuple[bool, Optional[str]]:
"""Send webhook notification for a new email.""" """Send webhook notification for a new email."""
# Fetch full email for body and unsubscribe info
full_email = self.email_service.read_email(mailbox, email.id, format="text")
# Get body and unsubscribe details from full email if available
body = None
can_unsubscribe = False
unsubscribe_url = None
attachment_count = 0
if full_email:
body = full_email.body_text
attachment_count = len(full_email.attachments)
if full_email.unsubscribe:
can_unsubscribe = full_email.unsubscribe.available
unsubscribe_url = full_email.unsubscribe.http_url
payload = EmailNotificationPayload( payload = EmailNotificationPayload(
timestamp=datetime.now(timezone.utc), timestamp=datetime.now(timezone.utc),
email_id=email.id, email_id=email.id,
@@ -287,9 +303,13 @@ class EmailMonitor:
to_emails=[addr.email for addr in email.to_addresses], to_emails=[addr.email for addr in email.to_addresses],
subject=email.subject, subject=email.subject,
snippet=email.snippet, snippet=email.snippet,
body=body,
date=email.date, date=email.date,
has_attachments=email.has_attachments, has_attachments=email.has_attachments,
is_flagged=email.is_flagged, is_flagged=email.is_flagged,
attachment_count=attachment_count,
can_unsubscribe=can_unsubscribe,
unsubscribe_url=unsubscribe_url,
) )
return await self.webhook_service.send_new_email_notification(payload) return await self.webhook_service.send_new_email_notification(payload)

12
src/templates/notif.txt Normal file
View File

@@ -0,0 +1,12 @@
📧 New Email Received
From: {sender_name} <{sender_email}>
To: {recipients}
Subject: {subject}
Date: {date}
{body}
---
{attachments_line}
{unsubscribe_line}