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)
===
