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"