315- 316- 317:class SendJob(Base, TimestampMixin): 318- __tablename__ = "send_jobs" 319- id = Column(Integer, primary_key=True) 320- campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), nullable=False, index=True) 321- campaign_recipient_id = Column(Integer, ForeignKey("campaign_recipients.id", ondelete="CASCADE"), nullable=False, index=True) 322- status = Column(String(30), default="queued") # queued|processing|sent|failed|retrying|cancelled|dead 323- attempts = Column(Integer, default=0) 324- max_attempts = Column(Integer, default=3) 325- retry_after = Column(DateTime(timezone=True)) 326- last_error = Column(Text, default="") 327- locked_at = Column(DateTime(timezone=True)) 328- priority = Column(Integer, default=0) 329- scheduled_at = Column(DateTime(timezone=True), default=utcnow, index=True) 330- 331- campaign = relationship("Campaign", back_populates="send_jobs") 332- campaign_recipient = relationship("CampaignRecipient") 333- 334- 335-class SendAttempt(Base, TimestampMixin): 336- __tablename__ = "send_attempts" 337- id = Column(Integer, primary_key=True) -- 355- 356- 357:class EmailEvent(Base, TimestampMixin): 358- __tablename__ = "email_events" 359- id = Column(Integer, primary_key=True) 360- campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), index=True) 361- campaign_recipient_id = Column(Integer, ForeignKey("campaign_recipients.id", ondelete="CASCADE"), index=True) 362- gmail_account_id = Column(Integer, ForeignKey("gmail_accounts.id", ondelete="SET NULL")) 363- event_type = Column(String(40), nullable=False) # sent|failed|bounced|suppressed|unsubscribed|complained 364- detail = Column(Text, default="") 365- message_id = Column(String(200), default="") 366- 367- __table_args__ = (Index("ix_email_events_campaign", "campaign_id", "event_type"),) 368- 369- 370-class AuditLog(Base, TimestampMixin): 371- __tablename__ = "audit_logs" 372- id = Column(Integer, primary_key=True) 373- user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), index=True) 374- action = Column(String(120), nullable=False, index=True) 375- entity = Column(String(60), default="") 376- entity_id = Column(Integer) 377- detail = Column(JSON, default=dict) ===