Skip to main content

ADR-001: Email Provider Abstraction Pattern

Status: Accepted Date: December 17, 2025 Decision Makers: CODITECT Engineering Team


Context

CODITECT requires email capabilities for meeting notifications, reminders, AI-powered email analysis, and workflow automation. While Gmail is the primary provider, we need a design that:

  1. Allows future support for other email providers (Outlook, SMTP)
  2. Enables consistent behavior across different providers
  3. Supports provider-agnostic business logic in integration-core

Decision

We will implement an Email Provider Abstraction Pattern using a Python Protocol/Abstract Base Class that defines the interface all email providers must implement.

Interface Design

class EmailProviderInterface(ABC):
"""Abstract interface for email providers."""

@property
@abstractmethod
def provider_name(self) -> str:
"""Return provider identifier (e.g., 'gmail', 'outlook')."""
pass

@abstractmethod
async def authenticate(self, credentials: Dict[str, Any]) -> bool:
"""Authenticate with the email provider."""
pass

@abstractmethod
async def send_email(
self,
to: List[str],
subject: str,
body: str,
cc: List[str] = None,
bcc: List[str] = None,
attachments: List[Attachment] = None,
html_body: str = None,
) -> EmailMessage:
"""Send an email message."""
pass

@abstractmethod
async def get_message(self, message_id: str) -> Optional[EmailMessage]:
"""Retrieve a specific email message."""
pass

@abstractmethod
async def list_messages(
self,
query: str = None,
max_results: int = 100,
) -> Tuple[List[EmailMessage], Optional[str]]:
"""List email messages with optional filtering."""
pass

@abstractmethod
async def search(self, query: str) -> List[EmailMessage]:
"""Search emails using provider-specific query syntax."""
pass

@abstractmethod
async def watch_inbox(
self,
callback: Callable,
label_ids: List[str] = None,
) -> str:
"""Start watching inbox for new messages."""
pass

@abstractmethod
async def stop_watch(self, watch_id: str) -> bool:
"""Stop watching inbox."""
pass

Implementation Pattern

class GmailProvider(EmailProviderInterface):
@property
def provider_name(self) -> str:
return "gmail"

async def send_email(self, to, subject, body, **kwargs) -> EmailMessage:
# Gmail-specific MIME message building and API call
...

class OutlookProvider(EmailProviderInterface):
@property
def provider_name(self) -> str:
return "outlook"

async def send_email(self, to, subject, body, **kwargs) -> EmailMessage:
# Microsoft Graph API call
...

Consequences

Positive

  • Provider Independence: Business logic doesn't depend on Gmail-specific code
  • Easy Provider Addition: New providers implement the same interface
  • Testability: Can mock providers for unit testing
  • Consistent API: Same methods work across all providers

Negative

  • Lowest Common Denominator: Some provider-specific features may be hidden
  • Abstraction Overhead: Extra layer between business logic and API

Mitigation

  • Provider-specific features accessible via **kwargs or provider subclasses
  • Thin abstraction layer with minimal overhead

Alternatives Considered

  1. Direct Gmail API Usage: Simpler but locks us into Gmail
  2. Generic SMTP: Limited features (no read, no labels, no push)
  3. Third-party Abstraction (e.g., Nylas): External dependency and cost
  • ADR-002: Zero-Cost Architecture
  • ADR-003: OAuth Strategy
  • Google Meet ADR-001: Meeting Provider Abstraction (parallel pattern)

Document Control:

  • Created: December 17, 2025
  • Author: CODITECT Engineering Team