class SendJob(Base, TimestampMixin): __tablename__ = "send_jobs" id = Column(Integer, primary_key=True) campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), nullable=False, index=True) campaign_recipient_id = Column(Integer, ForeignKey("campaign_recipients.id", ondelete="CASCADE"), nullable=False, index=True) status = Column(String(30), default="queued") # queued|processing|sent|failed|retrying|cancelled|dead attempts = Column(Integer, default=0) max_attempts = Column(Integer, default=3) retry_after = Column(DateTime(timezone=True)) last_error = Column(Text, default="") locked_at = Column(DateTime(timezone=True)) priority = Column(Integer, default=0) scheduled_at = Column(DateTime(timezone=True), default=utcnow, index=True) campaign = relationship("Campaign", back_populates="send_jobs") campaign_recipient = relationship("CampaignRecipient") class SendAttempt(Base, TimestampMixin): __tablename__ = "send_attempts" id = Column(Integer, primary_key=True) send_job_id = Column(Integer, ForeignKey("send_jobs.id", ondelete="CASCADE"), nullable=False, index=True) campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), nullable=False, index=True) gmail_account_id = Column(Integer, ForeignKey("gmail_accounts.id", ondelete="SET NULL")) recipient_email = Column(String(320), nullable=False) subject = Column(String(500), default="") template_name = Column(String(250), default="") attachment_names = Column(JSON, default=list) method = Column(String(20), default="api") # api|smtp outcome = Column(String(30), nullable=False) # submitted|accepted|temp_failed|perm_failed|skipped provider_response = Column(Text, default="") # sanitized provider response message_id = Column(String(200), default="") thread_id = Column(String(200), default="") retry_no = Column(Integer, default=0) failure_reason = Column(Text, default="") error_class = Column(String(60), default="") # temporary|permanent|auth|quota|unknown send_job = relationship("SendJob") class EmailEvent(Base, TimestampMixin): __tablename__ = "email_events" id = Column(Integer, primary_key=True) campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), index=True) campaign_recipient_id = Column(Integer, ForeignKey("campaign_recipients.id", ondelete="CASCADE"), index=True) gmail_account_id = Column(Integer, ForeignKey("gmail_accounts.id", ondelete="SET NULL")) event_type = Column(String(40), nullable=False) # sent|failed|bounced|suppressed|unsubscribed|complained detail = Column(Text, default="") message_id = Column(String(200), default="") __table_args__ = (Index("ix_email_events_campaign", "campaign_id", "event_type"),) class AuditLog(Base, TimestampMixin):