#3 — Duplicate prospect handling: add_lead now checks (campaign_id, url) before insert; bulk_add_leads skips existing URLs. #8 — Atomic rate limiting: try_increment_* methods atomically check cap and increment in a single session; router uses these before send. #10 — Reply matching via Message-ID: sender generates Message-ID header, stored on OutreachAttempt; reply monitor parses In-Reply-To/References; poll_replies matches by message_id first, falls back to from_email. #11 — Save-to-campaign uses existing store results instead of re-running expensive deepDiscover. #12 — Lead status Literal type: Pydantic models enforce valid status values; backend validates via LEAD_VALID_STATUSES frozenset; frontend API typed as LeadStatus union.
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field, HttpUrl
|
||||
from typing import Dict, List, Optional
|
||||
from typing_extensions import Literal
|
||||
|
||||
|
||||
class BacklinkKeywordInput(BaseModel):
|
||||
@@ -93,7 +94,7 @@ class LeadListResponse(BaseModel):
|
||||
|
||||
|
||||
class LeadStatusUpdateRequest(BaseModel):
|
||||
status: str = Field(..., min_length=1)
|
||||
status: Literal["discovered", "contacted", "replied", "placed", "bounced", "unsubscribed"]
|
||||
notes: Optional[str] = None
|
||||
campaign_id: Optional[str] = Field(default=None, min_length=1)
|
||||
|
||||
@@ -329,7 +330,7 @@ class ConversionFunnelResponse(BaseModel):
|
||||
|
||||
class BulkStatusUpdateRequest(BaseModel):
|
||||
lead_ids: List[str] = Field(..., min_length=1)
|
||||
status: str = Field(..., min_length=1)
|
||||
status: Literal["discovered", "contacted", "replied", "placed", "bounced", "unsubscribed"]
|
||||
notes: Optional[str] = None
|
||||
campaign_id: Optional[str] = Field(default=None, min_length=1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user