feat: add Campaign delivery ownership and IMAP claim primitives

This commit is contained in:
2026-07-21 17:13:49 +02:00
parent 701c0fe184
commit 057e660b17
4 changed files with 214 additions and 0 deletions
@@ -92,7 +92,9 @@ class JobSendStatus(StrEnum):
class JobImapStatus(StrEnum):
NOT_REQUESTED = "not_requested"
PENDING = "pending"
APPENDING = "appending"
APPENDED = "appended"
OUTCOME_UNKNOWN = "outcome_unknown"
FAILED = "failed"
SKIPPED = "skipped"
@@ -213,6 +215,12 @@ class CampaignVersion(Base, TimestampMixin):
campaign: Mapped[Campaign] = relationship(back_populates="versions")
@property
def mail_profile_migration_required(self) -> bool:
from govoplan_campaign.backend.campaign.mail_profile_boundary import campaign_mail_profile_boundary_violations
return bool(campaign_mail_profile_boundary_violations(self.raw_json))
class CampaignJob(Base, TimestampMixin):
__tablename__ = "campaign_jobs"
@@ -246,6 +254,8 @@ class CampaignJob(Base, TimestampMixin):
claim_token: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
smtp_started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
outcome_unknown_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
imap_claimed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
imap_claim_token: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
resolved_recipients: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
@@ -315,10 +325,14 @@ class SendAttempt(Base, TimestampMixin):
class ImapAppendAttempt(Base, TimestampMixin):
__tablename__ = "imap_append_attempts"
__table_args__ = (
UniqueConstraint("job_id", "attempt_number", name="uq_imap_append_attempts_job_attempt"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
job_id: Mapped[str] = mapped_column(ForeignKey("campaign_jobs.id", ondelete="CASCADE"), nullable=False, index=True)
attempt_number: Mapped[int] = mapped_column(Integer, nullable=False)
claim_token: Mapped[str | None] = mapped_column(String(36), nullable=True)
folder: Mapped[str | None] = mapped_column(String(500))
status: Mapped[str] = mapped_column(String(50), nullable=False)
error_message: Mapped[str | None] = mapped_column(Text)