    status = Column(String(30), default="draft")  # draft|queued|running|paused|completed|cancelled|failed
    # selections
    recipient_list_id = Column(Integer, ForeignKey("recipient_lists.id", ondelete="SET NULL"))
    subject_set_ids = Column(JSON, default=list)   # list of subject line ids
    template_ids = Column(JSON, default=list)      # ordered template ids for sequencing
    message_type = Column(String(40), default="html")
    # attachment behaviour
    attach_pdf = Column(Boolean, default=False)
    attach_pdf_template_id = Column(Integer, ForeignKey("html_templates.id", ondelete="SET NULL"))
    attachment_id = Column(Integer, ForeignKey("attachments.id", ondelete="SET NULL"))
    attach_mode = Column(String(40), default="none")  # none|pdf|file|image
    # dynamic variables config
    variable_config = Column(JSON, default=dict)   # invoice date, due date, ranges etc.
    # rotation / limits
    account_ids = Column(JSON, default=list)
    rotation_strategy = Column(String(30), default="sequential")  # sequential|weighted
    rotation_weights = Column(JSON, default=dict)  # account_id -> weight
    subject_rotation = Column(String(30), default="round_robin")  # round_robin|random|fixed_first
    template_sequence_mode = Column(String(30), default="repeat")  # repeat|stop|random
    phone_selection = Column(String(30), default="round_robin")   # round_robin|random|fixed
    max_per_account = Column(Integer, default=200)
    send_interval_seconds = Column(Float, default=1.0)
    rate_per_minute = Column(Integer, default=60)
    max_retries = Column(Integer, default=3)
    retry_delay_seconds = Column(Integer, default=60)
    # counters
    total_recipients = Column(Integer, default=0)
    sent_count = Column(Integer, default=0)
    failed_count = Column(Integer, default=0)
    skipped_count = Column(Integer, default=0)
    suppressed_count = Column(Integer, default=0)
    bounced_count = Column(Integer, default=0)
    queued_count = Column(Integer, default=0)
    started_at = Column(DateTime(timezone=True))
    finished_at = Column(DateTime(timezone=True))
    preview_sample_size = Column(Integer, default=5)
    # compliance
    unsubscribe_enabled = Column(Boolean, default=True, nullable=False)

    recipient_list = relationship("RecipientList")
    send_jobs = relationship("SendJob", back_populates="campaign", cascade="all, delete-orphan")


class CampaignRecipient(Base, TimestampMixin):
    """Materialized campaign×recipient rows with generated values & audit info."""
    __tablename__ = "campaign_recipients"
