Add Gmail API injection, provider presets, delivery method, and frontend wizard
- Add gmail_service.py with Gmail API users.messages.insert() for direct email injection - Add DeliveryMethod enum and GmailCredential model to database models - Add delivery_method field to MailAccount schema and model - Create providers.py endpoint with 12 provider presets (Gmail, GMX, WEB.DE, Outlook, Yahoo, AOL, T-Online, 1&1/IONOS, Freenet, Posteo, mail.de, iCloud) - Expand MailServerAutoDetect with 30+ domain mappings - Update Celery tasks to prefer Gmail API injection, with SMTP fallback - Add ProviderWizard.tsx frontend component for quick provider setup - Update AddMailAccountModal.tsx with wizard integration - Add Google API Python client libraries to requirements.txt - Add Gmail API config settings - Add unit tests for Gmail service and provider presets Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/de3ef930-a980-4958-8a9d-a2c802918e81
This commit is contained in:
@@ -29,6 +29,12 @@ class MailProtocol(str, enum.Enum):
|
||||
IMAP_SSL = "imap_ssl"
|
||||
|
||||
|
||||
class DeliveryMethod(str, enum.Enum):
|
||||
"""How emails are delivered to Gmail"""
|
||||
SMTP = "smtp" # Forward via SMTP (legacy)
|
||||
GMAIL_API = "gmail_api" # Inject via Gmail API (preferred)
|
||||
|
||||
|
||||
class AccountStatus(str, enum.Enum):
|
||||
"""Mail account status"""
|
||||
ACTIVE = "active"
|
||||
@@ -104,6 +110,9 @@ class MailAccount(Base):
|
||||
# Forwarding destination
|
||||
forward_to = Column(String(255), nullable=False)
|
||||
|
||||
# Delivery method
|
||||
delivery_method = Column(SQLEnum(DeliveryMethod), default=DeliveryMethod.GMAIL_API)
|
||||
|
||||
# Status and settings
|
||||
status = Column(SQLEnum(AccountStatus), default=AccountStatus.ACTIVE)
|
||||
is_enabled = Column(Boolean, default=True)
|
||||
@@ -326,3 +335,33 @@ class AuditLog(Base):
|
||||
Index('idx_user_action', 'user_id', 'action'),
|
||||
Index('idx_timestamp_action', 'timestamp', 'action'),
|
||||
)
|
||||
|
||||
|
||||
class GmailCredential(Base):
|
||||
"""Stores OAuth2 credentials for Gmail API access (per-user)"""
|
||||
__tablename__ = "gmail_credentials"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True)
|
||||
|
||||
# Gmail account email
|
||||
gmail_email = Column(String(255), nullable=False)
|
||||
|
||||
# OAuth2 tokens (encrypted)
|
||||
encrypted_access_token = Column(Text, nullable=False)
|
||||
encrypted_refresh_token = Column(Text, nullable=True)
|
||||
|
||||
# Token metadata
|
||||
token_expiry = Column(DateTime, nullable=True)
|
||||
scopes = Column(JSON, nullable=True)
|
||||
|
||||
# Status
|
||||
is_valid = Column(Boolean, default=True)
|
||||
last_verified_at = Column(DateTime, nullable=True)
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", backref="gmail_credential")
|
||||
|
||||
@@ -29,6 +29,11 @@ class AccountStatus(str, Enum):
|
||||
TESTING = "testing"
|
||||
|
||||
|
||||
class DeliveryMethod(str, Enum):
|
||||
SMTP = "smtp"
|
||||
GMAIL_API = "gmail_api"
|
||||
|
||||
|
||||
class NotificationChannel(str, Enum):
|
||||
EMAIL = "email"
|
||||
TELEGRAM = "telegram"
|
||||
@@ -103,6 +108,7 @@ class MailAccountBase(BaseModel):
|
||||
use_tls: bool = False
|
||||
username: str = Field(..., max_length=255)
|
||||
forward_to: EmailStr
|
||||
delivery_method: DeliveryMethod = DeliveryMethod.GMAIL_API
|
||||
is_enabled: bool = True
|
||||
check_interval_minutes: int = Field(default=5, gt=0, le=1440)
|
||||
max_emails_per_check: int = Field(default=50, gt=0, le=1000)
|
||||
@@ -117,6 +123,7 @@ class MailAccountUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, max_length=255)
|
||||
password: Optional[str] = None
|
||||
forward_to: Optional[EmailStr] = None
|
||||
delivery_method: Optional[DeliveryMethod] = None
|
||||
is_enabled: Optional[bool] = None
|
||||
check_interval_minutes: Optional[int] = Field(None, gt=0, le=1440)
|
||||
max_emails_per_check: Optional[int] = Field(None, gt=0, le=1000)
|
||||
@@ -127,6 +134,7 @@ class MailAccountResponse(MailAccountBase):
|
||||
id: int
|
||||
user_id: int
|
||||
status: AccountStatus
|
||||
delivery_method: DeliveryMethod
|
||||
provider_name: Optional[str] = None
|
||||
auto_detected: bool
|
||||
total_emails_processed: int
|
||||
@@ -302,3 +310,38 @@ class MailServerPresetResponse(BaseModel):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Gmail Credential Schemas
|
||||
class GmailCredentialCreate(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: Optional[str] = None
|
||||
gmail_email: EmailStr
|
||||
|
||||
|
||||
class GmailCredentialResponse(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
gmail_email: str
|
||||
is_valid: bool
|
||||
last_verified_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Provider Wizard Schemas
|
||||
class ProviderPreset(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
icon: Optional[str] = None
|
||||
domains: List[str]
|
||||
imap_ssl: Optional[Dict[str, Any]] = None
|
||||
pop3_ssl: Optional[Dict[str, Any]] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class ProviderListResponse(BaseModel):
|
||||
providers: List[ProviderPreset]
|
||||
|
||||
Reference in New Issue
Block a user