256:class CampaignRecipient(Base, TimestampMixin):
257-    """Materialized campaign×recipient rows with generated values & audit info."""
258-    __tablename__ = "campaign_recipients"
259-    id = Column(Integer, primary_key=True)
260-    campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), nullable=False, index=True)
261-    recipient_id = Column(Integer, ForeignKey("recipients.id", ondelete="CASCADE"), nullable=False)
262-    email = Column(String(320), nullable=False, index=True)
263-    status = Column(String(30), default="pending")  # pending|queued|processing|sent|temp_failed|perm_failed|suppressed|skipped
264-    generated_invoice_number = Column(String(100), default="")
265-    generated_amount = Column(Float)
266-    generated_shipping = Column(Float)
267-    generated_subject_id = Column(Integer, ForeignKey("subject_lines.id", ondelete="SET NULL"))
268-    generated_template_id = Column(Integer, ForeignKey("html_templates.id", ondelete="SET NULL"))
269-    generated_phone = Column(String(60), default="")
270-    assigned_account_id = Column(Integer, ForeignKey("gmail_accounts.id", ondelete="SET NULL"))
271-    attempt_count = Column(Integer, default=0)
272-    last_error = Column(Text, default="")
273-    msg_id = Column(String(200), default="")       # Gmail message id
274-    thread_id = Column(String(200), default="")    # Gmail thread id
275-    sent_at = Column(DateTime(timezone=True))
276-    seq = Column(Integer, default=0)               # ordering within campaign
277-
278-    campaign = relationship("Campaign")
279-    recipient = relationship("Recipient")
280-    subject = relationship("SubjectLine")
281-    template = relationship("EmailTemplate")
282-    account = relationship("GmailAccount")
283-
284-    __table_args__ = (UniqueConstraint("campaign_id", "recipient_id", name="uq_campaign_recipient"),
285-                      Index("ix_campaign_recipient_status", "campaign_id", "status"),)
286-
287-
288-class Attachment(Base, TimestampMixin):
64:class GmailAccount(Base, TimestampMixin):
65-    __tablename__ = "gmail_accounts"
66-    id = Column(Integer, primary_key=True)
67-    user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
68-    email = Column(String(320), nullable=False)
69-    display_name = Column(String(200), default="")
70-    status = Column(String(30), default="pending")  # pending|authorized|expired|revoked|error
71-    sending_method = Column(String(20), default="api")  # api|smtp
72-    auth_provider = Column(String(60), default="google")
73-    google_user_id = Column(String(120), default="")
74-    # Per-account config (administrator-configurable)
75-    daily_limit = Column(Integer, default=200)          # provider/configured daily cap
76-    campaign_limit = Column(Integer, default=200)       # per-account per-campaign cap
77-    sends_today = Column(Integer, default=0)
78-    last_send_date = Column(DateTime(timezone=True))
79-    cooldown_until = Column(DateTime(timezone=True))
80-    last_success_at = Column(DateTime(timezone=True))
81-    last_error = Column(Text, default="")
82-    error_count = Column(Integer, default=0)
83-    token_expiry = Column(DateTime(timezone=True))
84-    # Imported client config reference (optional; may use server-level config)
85-    oauth_config_id = Column(Integer, ForeignKey("oauth_client_configs.id", ondelete="SET NULL"))
86-    # Encrypted SMTP OAuth2 credentials mirror (kept in oauth_tokens; this is metadata only)
87-    is_active = Column(Boolean, default=True, nullable=False)
88-    revoked_by_user = Column(Boolean, default=False)
89-    revoked_at = Column(DateTime(timezone=True))
90-
91-    user = relationship("User", back_populates="gmail_accounts")
92-    oauth_token = relationship("OAuthToken", back_populates="account", uselist=False,
93-                               cascade="all, delete-orphan")
94-    oauth_config = relationship("OAuthClientConfig")
95-
96-    __table_args__ = (UniqueConstraint("user_id", "email", name="uq_gmail_account_user_email"),
97-                      Index("ix_gmail_status", "status"),)
98-
99-    def usage_summary(self):
100-        return {"daily_limit": self.daily_limit, "sends_today": self.sends_today,
101-                "campaign_limit": self.campaign_limit,
102-                "last_success_at": self.last_success_at.isoformat() if self.last_success_at else None,
103-                "errors": self.error_count, "status": self.status}
104-
