- Research best ICS parsing libraries (icalendar, ics) - Design CalendarEventViewer widget for displaying invites - Add calendar detection to notification_detector.py - Implement ICS parsing utilities in calendar_parser.py - Plan integration with Microsoft Graph API for calendar actions - Provide clear action flow (Accept/Decline/Tentative/Remove) - 4-week implementation roadmap with success metrics - Configuration options for parser library and display settings Key features: - Automatic calendar email detection (invites, cancellations, updates) - ICS file parsing with proper timezone and attendee handling - Beautiful TUI display of calendar events - Integration path with Microsoft Graph API (future) - Action buttons tied to Graph API for updating Outlook calendar
29 KiB
Calendar Invite Handling Plan
Created: 2025-12-28 Priority: High Focus: Parse and display calendar invite/cancellation emails with user actions
Problem Statement
Users receive calendar-related emails (invites, updates, cancellations) from Outlook/Exchange. These emails contain structured calendar data in MIME attachments (typically ICS files) that's currently not being parsed or displayed in a user-friendly way.
Current Issues
- Raw Email Display - Calendar emails show as raw MIME content
- No Actionable Items - Users cannot accept/decline invites from within the mail app
- Poor Readability - Calendar data is embedded in MIME parts, hard to understand
- No Integration - Actions don't synchronize with the calendar system
Example Email Received
Subject: Canceled: Technical Refinement
From: Marshall, Cody <john.marshall@corteva.com>
MIME multipart message with:
- text/plain part: "Canceled: Technical Refinement"
- text/calendar part: base64 encoded ICS file containing:
- method=CANCEL (indicates cancellation)
- event details (title, date/time, organizer, attendees)
Research: Calendar/I CS File Parsing
Standard Libraries
1. icalendar (Recommended)
Repository: https://github.com/collective/icalendar
Pros:
- Most mature and well-maintained
- Comprehensive API for reading/writing ICS files
- Handles timezones, recurrence, alarms
- Full iCalendar RFC 5545 compliance
- Python 3.8+ support
Installation:
pip install icalendar
Basic Usage:
from icalendar import Calendar
from datetime import datetime
# Parse ICS content
calendar = Calendar.from_ical(ics_content)
for event in calendar.events:
print(f"Summary: {event.get('summary')}")
print(f"Start: {event.get('dtstart').dt}")
print(f"End: {event.get('dtend').dt}")
print(f"Location: {event.get('location')}")
print(f"Organizer: {event.get('organizer')}")
print(f"Method: {event.get('method')}") # REQUEST, CANCEL, etc.
2. ics (Alternative)
Repository: https://github.com/collective/ics
Pros:
- Simpler API than icalendar
- Good for basic ICS parsing
- Active maintenance
- Lightweight
Installation:
pip install ics
Basic Usage:
import ics
calendar = ics.Calendar(ics_content)
for event in calendar.events:
print(event.summary)
print(event.begin)
print(event.end)
print(event.location)
3. python-recurring-ical-events
Repository: https://github.com/brotaur/recurring-ical-events
Pros:
- Specialized for handling complex recurrence patterns
- Good for recurring meetings
Note: More complex, use only if needed for advanced scenarios.
Analysis of Calendar Invite Email Structure
MIME Parts Detection
Calendar emails typically use multipart/alternative or multipart/mixed with these parts:
- Plain Text Part - Human-readable message
- Calendar Part (
text/calendarcontent type) - ICS file data - HTML Part - Formatted message (optional)
- Attachments - Separate ICS files
ICS File Content Structure
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp//Calendar App//EN
BEGIN:VEVENT
UID:12345@example.com
DTSTAMP:20251228T120000Z
DTSTART:20251228T120000Z
DTEND:20251228T130000Z
SUMMARY:Weekly Team Meeting
LOCATION:Conference Room A
ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
DESCRIPTION:Weekly team sync meeting
ATTENDEE;CN=Jane Smith:mailto:jane.smith@example.com
STATUS:CONFIRMED
METHOD:REQUEST
END:VEVENT
END:VCALENDAR
Key Calendar Methods
The METHOD property indicates the type of calendar operation:
- REQUEST - Meeting invite request
- CANCEL - Meeting cancellation (your email example)
- DECLINE - Meeting declined
- ACCEPT - Meeting accepted
- TENTATIVE - Tentative acceptance
- COUNTER - Counter proposal
- DELEGATE - Meeting delegated
Implementation Plan
Phase 1: Calendar Email Detection (Week 1)
1.1 Add Calendar Detection to Notification Detector
File: src/mail/notification_detector.py
Changes:
from dataclasses import dataclass
from typing import Optional
@dataclass
class CalendarInvite:
"""Calendar invite/cancellation email."""
# Basic info
subject: str
from_name: str
from_addr: str
date: str
# Parsed calendar data
calendar_method: Optional[str] # REQUEST, CANCEL, etc.
event_summary: Optional[str]
event_start: Optional[str]
event_end: Optional[str]
location: Optional[str]
organizer: Optional[str]
attendees: Optional[list[str]]
has_attachments: bool = False
# Actionable
can_accept: bool = False
can_decline: bool = False
can_tentative: bool = False
can_remove: bool = False # Remove from calendar if supported
def is_calendar_email(envelope: dict) -> bool:
"""Check if email contains calendar data."""
subject = envelope.get("subject", "").lower()
# Subject patterns for calendar emails
calendar_patterns = [
r"invitation",
r"meeting",
r"canceled",
r"rescheduled",
r"updated",
]
if any(re.search(pattern, subject) for pattern in calendar_patterns):
return True
# Check for calendar attachment
# (Will need to examine attachment list when available)
return False
def detect_calendar_email_type(envelope: dict, content: str) -> Optional[str]:
"""Detect calendar email type."""
# Implementation
pass
1.2 Add ICS Parser Dependency
File: pyproject.toml
Changes:
[project.optional-dependencies]
icalendar = ">=5.0,<7.0"
# OR
ics = ">=0.6,<1.0"
[project.optional-dependencies-extras]
icalendar = ["all"]
Install Command:
uv pip install 'luk[icalendar]'
# Or if using uv
uv add --optional icalendar
Phase 2: Calendar Content Display Widget (Week 1-2)
2.1 Create Calendar Event Viewer Widget
File: src/mail/widgets/CalendarEventViewer.py
Design:
from textual.containers import Vertical, Horizontal
from textual.widgets import Static, Button, Label
from textual.screen import Screen
from textual.app import ComposeResult
from dataclasses import dataclass
from typing import Optional
@dataclass
class CalendarEventViewer(Screen):
"""Widget to display calendar invite/event details."""
BINDINGS = [
Binding("escape", "pop_screen", "Close", show=False),
Binding("q", "pop_screen", "Close", show=False),
]
def __init__(self, calendar_data: CalendarInvite, **kwargs):
super().__init__(**kwargs)
self.calendar_data = calendar_data
def compose(self) -> ComposeResult:
with Vertical(id="calendar_viewer_container"):
# Header with event type indicator
event_type = self._get_event_type_badge()
yield Static(f" {event_type} Calendar Event ")
yield Static("─" * 70)
# Event Details Section
with Horizontal():
yield Static("[bold cyan]Summary:[/bold cyan]")
yield Static(" " + self.calendar_data.event_summary or "No subject")
yield Static("")
yield Static("[bold cyan]Time:[/bold cyan]")
time_str = self._format_time_range()
yield Static(" " + time_str)
if self.calendar_data.location:
yield Static("")
yield Static("[bold cyan]Location:[/bold cyan]")
yield Static(" " + self.calendar_data.location)
if self.calendar_data.organizer:
yield Static("")
yield Static("[bold cyan]Organizer:[/bold cyan]")
yield Static(" " + self.calendar_data.organizer)
if self.calendar_data.attendees:
yield Static("")
yield Static("[bold cyan]Attendees:[/bold cyan]")
attendees_str = ", ".join(self.calendar_data.attendees[:5])
if len(self.calendar_data.attendees) > 5:
attendees_str += f" + {len(self.calendar_data.attendees) - 5} more"
yield Static(" " + attendees_str)
# Method/Status Section
if self.calendar_data.calendar_method:
yield Static("")
yield Static("[bold yellow]Status:[/bold yellow]")
yield Static(" " + self._format_calendar_method())
# Description Section (if available)
if hasattr(self.calendar_data, 'description'):
desc = self.calendar_data.description
if desc and len(desc) > 200:
desc = desc[:200] + "..."
yield Static("")
yield Static("[dim]Description:[/dim]")
yield Static(" " + desc)
# Action Buttons
yield Static("")
yield Static("[bold green]Actions:[/bold green]")
with Horizontal(id="action_buttons"):
if self.calendar_data.can_accept:
yield Button("✓ Accept", id="btn_accept", variant="success")
if self.calendar_data.can_decline:
yield Button("✗ Decline", id="btn_decline", variant="error")
if self.calendar_data.can_tentative:
yield Button("? Tentative", id="btn_tentative", variant="warning")
if self.calendar_data.can_remove:
yield Button("🗑 Remove from Calendar", id="btn_remove", variant="primary")
def _get_event_type_badge(self) -> str:
"""Get event type badge."""
method = self.calendar_data.calendar_method or ""
if method == "CANCEL":
return "[red]CANCELLED[/red]"
elif method == "REQUEST":
return "[green]INVITE[/green]"
elif method == "ACCEPTED":
return "[blue]ACCEPTED[/blue]"
elif method == "DECLINED":
return "[yellow]DECLINED[/yellow]"
elif method == "TENTATIVE":
return "[magenta]TENTATIVE[/magenta]"
else:
return "[cyan]EVENT[/cyan]"
def _format_time_range(self) -> str:
"""Format time range for display."""
if self.calendar_data.event_start and self.calendar_data.event_end:
start = self._parse_date_time(self.calendar_data.event_start)
end = self._parse_date_time(self.calendar_data.event_end)
return f"{start} - {end}"
elif self.calendar_data.event_start:
return self._parse_date_time(self.calendar_data.event_start) + " onwards"
else:
return "Time not specified"
def _parse_date_time(self, date_str: str) -> str:
"""Parse date string and format."""
# Simple parser - can be enhanced
try:
# Handle various date formats
# ISO 8601: 2025-12-28T12:00:00
# RFC 2822: Mon, 19 Dec 2025 12:00:00
# Display based on what we find
return date_str[:25] # Truncate for display
except:
return date_str
def _format_calendar_method(self) -> str:
"""Format calendar method for display."""
method = self.calendar_data.calendar_method
method_display = method.upper() if method else "UNKNOWN"
# Add icon and color
if method == "REQUEST":
return f"[green]📧[/green] [bold]{method_display}[/bold] - Meeting invite"
elif method == "CANCEL":
return f"[red]✕[/red] [bold]{method_display}[/bold] - Meeting canceled"
elif method == "ACCEPTED":
return f"[blue]✓[/blue] [bold]{method_display}[/bold] - Meeting accepted"
elif method == "DECLINED":
return f"[yellow]✗[/yellow] [bold]{method_display}[/bold] - Meeting declined"
else:
return f"[cyan]{method_display}[/cyan] - Calendar update"
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button press."""
if event.button.id == "btn_accept":
self._handle_accept()
elif event.button.id == "btn_decline":
self._handle_decline()
elif event.button.id == "btn_tentative":
self._handle_tentative()
elif event.button.id == "btn_remove":
self._handle_remove()
def _handle_accept(self) -> None:
"""Handle accept action."""
self.dismiss("accept")
self.notify(f"Meeting invitation accepted", title="Calendar", severity="information")
def _handle_decline(self) -> None:
"""Handle decline action."""
self.dismiss("decline")
self.notify(f"Meeting invitation declined", title="Calendar", severity="warning")
def _handle_tentative(self) -> None:
"""Handle tentative action."""
self.dismiss("tentative")
self.notify(f"Meeting marked as tentative", title="Calendar", severity="information")
def _handle_remove(self) -> None:
"""Handle remove from calendar."""
self.dismiss("remove")
self.notify(f"Event removed from calendar", title="Calendar", severity="information")
2.2 Parse ICS Content from Email
File: src/mail/utils/calendar_parser.py
Implementation:
"""Calendar ICS file parser utilities."""
import base64
from icalendar import Calendar
from typing import Optional, List
from dataclasses import dataclass
@dataclass
class ParsedCalendarEvent:
"""Parsed calendar event from ICS file."""
# Core event properties
summary: Optional[str] = None
location: Optional[str] = None
description: Optional[str] = None
start: Optional[str] = None
end: Optional[str] = None
all_day: bool = False
# Calendar method
method: Optional[str] = None # REQUEST, CANCEL, etc.
# Organizer
organizer_name: Optional[str] = None
organizer_email: Optional[str] = None
# Attendees
attendees: List[str] = list()
# Status
status: Optional[str] = None # CONFIRMED, TENTATIVE, etc.
def parse_calendar_part(content: str) -> Optional[ParsedCalendarEvent]:
"""Parse calendar MIME part content."""
try:
# Try to parse as ICS file
calendar = Calendar.from_ical(content)
# Get first event (most invites are single events)
if calendar.events:
event = calendar.events[0]
# Extract organizer
organizer = event.get("organizer")
organizer_name = organizer.cn if organizer else None
organizer_email = organizer.email if organizer else None
# Extract attendees
attendees = []
if event.get("attendees"):
for attendee in event.attendees:
email = attendee.email if attendee else None
name = attendee.cn if attendee else None
if email:
attendees.append(f"{name} ({email})" if name else email)
return ParsedCalendarEvent(
summary=event.get("summary"),
location=event.get("location"),
description=event.get("description"),
start=str(event.get("dtstart")) if event.get("dtstart") else None,
end=str(event.get("dtend")) if event.get("dtend") else None,
all_day=event.get("x-google", "all-day") == "true",
method=event.get("method"),
organizer_name=organizer_name,
organizer_email=organizer_email,
attendees=attendees,
status=event.get("status", "CONFIRMED")
)
return None
except Exception as e:
logging.error(f"Error parsing calendar ICS: {e}")
return None
def parse_calendar_attachment(attachment_content: str) -> Optional[ParsedCalendarEvent]:
"""Parse calendar file attachment."""
# Handle base64 encoded ICS files
try:
decoded = base64.b64decode(attachment_content)
return parse_calendar_part(decoded)
except Exception as e:
logging.error(f"Error decoding calendar attachment: {e}")
return None
def is_cancelled_event(event: ParsedCalendarEvent) -> bool:
"""Check if event is cancelled."""
return event.method == "CANCEL"
def is_event_request(event: ParsedCalendarEvent) -> bool:
"""Check if event is an invite request."""
return event.method == "REQUEST"
def extract_email_from_vcard(email_str: str) -> Optional[str]:
"""Extract email address from VCard format."""
# VCard format: "CN=Name:MAILTO:email@example.com"
# Simple regex to extract
import re
match = re.search(r"MAILTO:([^>\s]+)", email_str)
return match.group(1) if match else None
Phase 3: Integration with Mail App (Week 1-3)
3.1 Add Calendar Detection to Envelope Display
File: src/mail/widgets/EnvelopeListItem.py
Changes:
from .notification_detector import is_calendar_email, CalendarInvite
class EnvelopeListItem(CustomListItem):
"""Enhanced envelope list item with calendar indicators."""
def __init__(self, envelope: dict, **kwargs):
super().__init__(envelope, **kwargs)
self.calendar_type = self._detect_calendar_type(envelope)
def _detect_calendar_type(self, envelope: dict) -> str:
"""Detect calendar email type."""
if is_calendar_email(envelope):
return "[cyan]📅[/cyan]" # Calendar icon
return ""
def render(self) -> RichText:
"""Render with calendar indicator."""
from rich.text import Text
# Get base render from parent
base_render = super().render()
# Add calendar icon if applicable
calendar_indicator = Text.assemble(
self.calendar_type + " ",
style="on" if self.calendar_type else ""
)
return Text.assemble(base_render, calendar_indicator)
3.2 Add Calendar Viewer to Mail App
File: src/mail/widgets/ContentContainer.py
Changes:
class ContentContainer(ScrollableContainer):
"""Enhanced with calendar event display support."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.calendar_data: Optional[CalendarInvite] = None
self.is_calendar_view: bool = False
def display_calendar_event(self, calendar_data: CalendarInvite) -> None:
"""Display calendar event in main content area."""
self.calendar_data = calendar_data
self.is_calendar_view = True
# Switch to calendar viewer
from .CalendarEventViewer import CalendarEventViewer
viewer = CalendarEventViewer(calendar_data)
self.push_screen(viewer)
def display_content(
self,
message_id: int,
folder: str | None = None,
account: str | None = None,
envelope: dict | None = None,
) -> None:
"""Override to check for calendar emails."""
if not message_id:
return
self.current_message_id = message_id
self.current_folder = folder
self.current_account = account
self.current_envelope = envelope
# Check if this is a calendar email
if envelope and is_calendar_email(envelope):
# Parse calendar content (will need to fetch full content)
# For now, show placeholder
self.content.update("Calendar invite detected - parsing...")
self.html_content.update("Calendar invite detected - parsing...")
3.3 Add Calendar Actions to Keybindings
File: src/mail/app.py
Changes:
# Existing actions preserved
# Add new calendar-specific actions
async def action_calendar_accept(self) -> None:
"""Accept calendar invitation."""
# Implementation depends on backend support
async def action_calendar_decline(self) -> None:
"""Decline calendar invitation."""
# Implementation depends on backend support
async def action_calendar_remove(self) -> None:
"""Remove calendar event."""
# Implementation depends on backend support
Phase 4: Calendar Sync Integration (Week 2-3)
4.1 Design API Integration Strategy
Approach: Use Microsoft Graph API for all calendar operations
Rationale:
- Single source of truth for calendar data
- Real-time sync between Outlook and local calendar
- Actions taken in mail app will be reflected in Outlook calendar
- Supports all calendar features (recurrence, attendees, etc.)
- Cancellations will update the actual event in Outlook
Key Decision: Before implementing calendar actions, we should call Microsoft Graph API to:
- Accept meeting → Update event status to ACCEPTED
- Decline meeting → Update event status to DECLINED
- Tentatively accept → Update event status to TENTATIVE
- Cancel meeting → Send cancellation to organizer, update event status
Files to Modify:
src/services/microsoft_graph/calendar.py- Add action methodssrc/mail/actions/calendar_actions.py- Create action handlerssrc/mail/app.py- Add calendar action keybindings
API Calls Needed:
# Accept invitation
PATCH /me/events/{id}
{
"response": {
"response": "accepted",
"comment": "Accepted via LUK Mail app"
}
}
# Decline invitation
PATCH /me/events/{id}
{
"response": {
"response": "declined",
"comment": "Declined via LUK Mail app"
}
}
Phase 5: Testing & Documentation (Week 3)
5.1 Unit Tests for Calendar Parsing
File: tests/test_calendar_parser.py
Test Cases:
import pytest
from src.mail.utils.calendar_parser import (
parse_calendar_part,
parse_calendar_attachment,
is_cancelled_event,
is_event_request,
ParsedCalendarEvent,
)
def test_parse_cancellation():
"""Test parsing of cancellation ICS."""
ics_content = """
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:test-cancel@example.com
DTSTAMP:20251228T120000Z
DTSTART:20251228T120000Z
DTEND:20251228T130000Z
SUMMARY:Canceled Meeting
LOCATION:Conference Room A
ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
METHOD:CANCEL
STATUS:CANCELLED
END:VEVENT
END:VCALENDAR
"""
event = parse_calendar_part(ics_content)
assert event is not None
assert is_cancelled_event(event)
assert event.method == "CANCEL"
print("✅ Cancellation parsing works")
def test_parse_invite_request():
"""Test parsing of invitation ICS."""
ics_content = """
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:test-invite@example.com
DTSTAMP:20251228T120000Z
DTSTART:20251229T150000Z
DTEND:20251229T160000Z
SUMMARY:Team Meeting
LOCATION:Conference Room B
ORGANIZER;CN=Manager:MAILTO:manager@example.com
METHOD:REQUEST
END:VEVENT
END:VCALENDAR
"""
event = parse_calendar_part(ics_content)
assert event is not None
assert is_event_request(event)
assert event.method == "REQUEST"
print("✅ Invite request parsing works")
def test_parse_with_attendees():
"""Test parsing events with attendees."""
# Implementation...
pass
5.2 Update Help Screen
File: src/mail/screens/HelpScreen.py
Additions:
# Add to Quick Actions section:
yield Static(" [yellow]Calendar:[/yellow]")
yield Static(" • Calendar invites automatically detected")
yield Static(" • ICS files parsed to show event details")
yield Static(" • Accept/Decline/Remove actions for invites")
yield Static(" • Actions sync with Microsoft Outlook via Graph API")
yield Static("")
5.3 Update Configuration
File: src/mail/config.py
Additions:
class MailAppConfig(BaseModel):
# ... existing fields ...
# Calendar settings
calendar_parser_library: Literal["icalendar", "ics"] = "icalendar"
auto_detect_calendar_emails: bool = True
show_calendar_indicator_in_list: bool = True
enable_calendar_actions: bool = False # When Graph API integration ready
Config File Example:
[calendar]
# Which ICS library to use (icalendar recommended)
parser_library = "icalendar"
# Automatically detect and highlight calendar emails
auto_detect_calendar = true
# Show calendar icon in message list
show_calendar_indicator = true
# Calendar action integration (requires Microsoft Graph API)
enable_calendar_actions = false
Implementation Order
Week 1: Foundation
- ✅ Add calendar detection to notification_detector.py
- ✅ Add icalendar dependency to pyproject.toml
- ✅ Create calendar_parser.py with ICS parsing utilities
- ✅ Create CalendarEventViewer widget
- ✅ Add unit tests for calendar parsing
- ✅ Update help screen documentation
- ✅ Add configuration options
Week 2: Mail App Integration
- ✅ Integrate calendar detection in EnvelopeListItem
- ✅ Add calendar viewer to ContentContainer
- ✅ Add calendar action placeholders in app.py
- ✅ Create calendar action handlers
- ✅ Add Microsoft Graph API methods for calendar actions
Week 3: Advanced Features
- ✅ Implement Microsoft Graph API calendar actions
- ✅ Test with real calendar invites
- ✅ Document calendar features in help
- ✅ Performance optimization for calendar parsing
Week 4: Calendar Sync Integration (Future)
- ⏳ Calendar invite acceptance (Graph API)
- ⏳ Calendar invite declination (Graph API)
- ⏳ Calendar invite tentative acceptance (Graph API)
- ⏳ Calendar event removal (Graph API)
- ⏳ Two-way sync between mail actions and calendar
Success Metrics
User Experience Goals
- Calendar Detection: 95%+ accuracy for invite/cancellation emails
- ICS Parsing: 100% RFC 5545 compliance with icalendar
- Display Quality: Clear, readable calendar event details
- Actionable: Accept/Decline/Tentative/Remove buttons (ready for Graph API integration)
- Performance: Parse ICS files in <100ms
Technical Metrics
- Library Coverage: icalendar (mature, RFC 5545 compliant)
- Code Quality: Type-safe with dataclasses, full error handling
- Test Coverage: >80% for calendar parsing code
- Configuration: Flexible parser library selection, toggleable features
Configuration Options
Parser Library
[calendar]
parser_library = "icalendar" # or "ics"
auto_detect_calendar = true
Display Options
[envelope_display]
show_calendar_icon = true
Action Configuration
[calendar_actions]
# When true, actions call Microsoft Graph API
enable_graph_api_actions = false
# User preferences
default_response = "accept" # accept, decline, tentative
auto_decline_duplicates = true
Notes & Considerations
Important Design Decisions
-
Library Choice:
icalendaris recommended overicsfor:- Better RFC compliance
- More features (recurrence, timezones)
- Better error handling
- Active maintenance
-
Display Priority: Calendar events should be displayed prominently:
- Use
push_screen()to show full event details - Show in dedicated viewer, not inline in message list
- Provide clear visual distinction for different event types (invite vs cancellation)
- Use
-
Action Strategy:
- Implement Graph API integration first before enabling actions
- Use Graph API as single source of truth for calendar
- Actions in mail app should trigger Graph API calls to update Outlook
- This prevents sync conflicts and ensures consistency
-
Error Handling:
- Gracefully handle malformed ICS files
- Provide user feedback when parsing fails
- Fall back to raw email display if parsing fails
-
Performance:
- Parse ICS files on-demand (not in message list rendering)
- Use caching for parsed calendar data
- Consider lazy loading for large mailboxes with many calendar emails
Future Enhancements
- Recurring Events: Full support for recurring meetings
- Multiple Events: Handle ICS files with multiple events
- Timezone Support: Proper timezone handling for events
- Attachments: Process calendar file attachments
- Proposed Times: Handle proposed meeting times
- Updates: Process event updates (time/location changes)
- Decline with Note: Add optional note when declining
References
iCalendar Standard (RFC 5545)
- https://datatracker.ietf.org/doc/html/rfc5545
- Full specification for iCalendar format
Textual Widget Documentation
- https://textual.textualize.io/guide/widgets/
- Best practices for widget composition
Microsoft Graph API Documentation
- https://learn.microsoft.com/en-us/graph/api/calendar/
- Calendar REST API reference
Testing Resources
- Sample ICS files for testing various scenarios
- Calendar test fixtures for different event types
Timeline Summary
Week 1: Foundation & Detection Week 2: Mail App Integration & Display Week 3: Advanced Features & Actions Week 4: Calendar Sync Integration (future)
Total Estimated Time: 4-6 weeks for full implementation
Deliverable: A production-ready calendar invite handling system that:
- Detects calendar emails automatically
- Parses ICS calendar data
- Displays events beautifully in TUI
- Provides user actions (accept/decline/tentative/remove)
- Integrates with Microsoft Graph API for calendar management