337:class SendAttempt(Base, TimestampMixin):
338-    __tablename__ = "send_attempts"
339-    id = Column(Integer, primary_key=True)
340-    send_job_id = Column(Integer, ForeignKey("send_jobs.id", ondelete="CASCADE"), nullable=False, index=True)
341-    campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), nullable=False, index=True)
342-    gmail_account_id = Column(Integer, ForeignKey("gmail_accounts.id", ondelete="SET NULL"))
343-    recipient_email = Column(String(320), nullable=False)
344-    subject = Column(String(500), default="")
345-    template_name = Column(String(250), default="")
346-    attachment_names = Column(JSON, default=list)
347-    method = Column(String(20), default="api")  # api|smtp
348-    outcome = Column(String(30), nullable=False)  # submitted|accepted|temp_failed|perm_failed|skipped
349-    provider_response = Column(Text, default="")   # sanitized provider response
350-    message_id = Column(String(200), default="")
351-    thread_id = Column(String(200), default="")
352-    retry_no = Column(Integer, default=0)
353-    failure_reason = Column(Text, default="")
354-    error_class = Column(String(60), default="")  # temporary|permanent|auth|quota|unknown
355-
356-    send_job = relationship("SendJob")
357-
358-
359-class EmailEvent(Base, TimestampMixin):
360-    __tablename__ = "email_events"
361-    id = Column(Integer, primary_key=True)
362-    campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), index=True)
