""" Mail Aggregator Service — barrel re-export. All implementation split into: aggregator_imap — IMAP connection, sync, email parsing aggregator_smtp — SMTP connection, email sending Multi-account IMAP aggregation with async support. """ import asyncio import logging from typing import Optional, List, Dict, Any from .credentials import get_credentials_service from .mail_db import get_email_accounts, get_unified_inbox from .models import AccountTestResult from .aggregator_imap import IMAPMixin, IMAPConnectionError from .aggregator_smtp import SMTPMixin, SMTPConnectionError logger = logging.getLogger(__name__) class MailAggregator(IMAPMixin, SMTPMixin): """ Aggregates emails from multiple IMAP accounts into a unified inbox. Features: - Connect to multiple IMAP accounts - Fetch and cache emails in PostgreSQL - Send emails via SMTP - Handle connection pooling """ def __init__(self): self._credentials_service = get_credentials_service() self._imap_connections: Dict[str, Any] = {} self._sync_lock = asyncio.Lock() async def test_account_connection( self, imap_host: str, imap_port: int, imap_ssl: bool, smtp_host: str, smtp_port: int, smtp_ssl: bool, email_address: str, password: str, ) -> AccountTestResult: """ Test IMAP and SMTP connection with provided credentials. Returns: AccountTestResult with connection status """ result = AccountTestResult( success=False, imap_connected=False, smtp_connected=False, ) # Test IMAP imap_ok, imap_err, folders = await self.test_imap_connection( imap_host, imap_port, imap_ssl, email_address, password ) result.imap_connected = imap_ok if folders: result.folders_found = folders if imap_err: result.error_message = imap_err # Test SMTP smtp_ok, smtp_err = await self.test_smtp_connection( smtp_host, smtp_port, smtp_ssl, email_address, password ) result.smtp_connected = smtp_ok if smtp_err: if result.error_message: result.error_message += f"; {smtp_err}" else: result.error_message = smtp_err result.success = result.imap_connected and result.smtp_connected return result async def sync_all_accounts(self, user_id: str, tenant_id: Optional[str] = None) -> Dict[str, Any]: """ Sync all accounts for a user. Returns: Dict with sync results per account """ async with self._sync_lock: accounts = await get_email_accounts(user_id, tenant_id) results = {} for account in accounts: account_id = account["id"] try: new_count, total_count = await self.sync_account( account_id, user_id, max_emails=50 ) results[account_id] = { "status": "success", "new_emails": new_count, "total_emails": total_count, } except Exception as e: results[account_id] = { "status": "error", "error": str(e), } return results async def get_unified_inbox_emails( self, user_id: str, account_ids: Optional[List[str]] = None, categories: Optional[List[str]] = None, is_read: Optional[bool] = None, is_starred: Optional[bool] = None, limit: int = 50, offset: int = 0, ) -> List[Dict]: """ Get unified inbox with all filters. Returns: List of email dictionaries """ return await get_unified_inbox( user_id=user_id, account_ids=account_ids, categories=categories, is_read=is_read, is_starred=is_starred, limit=limit, offset=offset, ) # Global instance _aggregator: Optional[MailAggregator] = None def get_mail_aggregator() -> MailAggregator: """Get or create the global MailAggregator instance.""" global _aggregator if _aggregator is None: _aggregator = MailAggregator() return _aggregator