diff --git "a/app.py" "b/app.py"
--- "a/app.py"
+++ "b/app.py"
@@ -1,6 +1,1560 @@
+# import os
+# import json
+# import time
+# from datetime import datetime
+# from typing import List, Dict, Any, Optional, Union
+# from pydantic import BaseModel, Field, EmailStr, field_validator
+# from fastapi import FastAPI, HTTPException, Query, Depends, Request
+# from fastapi.responses import JSONResponse, Response
+# from fastapi.middleware.cors import CORSMiddleware
+# from fastapi.openapi.utils import get_openapi
+# import httpx
+# from dotenv import load_dotenv
+# import pandas as pd
+# import psycopg2
+# from sqlalchemy import create_engine, inspect, text
+
+# # LangChain and OpenAI imports
+# try:
+# from langchain_openai import ChatOpenAI
+# from langchain.prompts import ChatPromptTemplate
+# LANGCHAIN_AVAILABLE = True
+# except ImportError:
+# LANGCHAIN_AVAILABLE = False
+# print("Warning: LangChain not available. Install with: pip install langchain langchain-openai")
+
+# load_dotenv(override=True)
+
+# # Configuration
+# SMARTLEAD_API_KEY = os.getenv("SMARTLEAD_API_KEY", "your-api-key-here")
+# SMARTLEAD_BASE_URL = "https://server.smartlead.ai/api/v1"
+# DB_PARAMS = {
+# 'dbname': os.getenv("DB_NAME"),
+# 'user': os.getenv("DB_USER"),
+# 'password': os.getenv("DB_PASSWORD"),
+# 'host': os.getenv("DB_HOST"),
+# 'port': os.getenv("DB_PORT")
+# }
+# DATABASE_URL = f"postgresql://{DB_PARAMS['user']}:{DB_PARAMS['password']}@{DB_PARAMS['host']}:{DB_PARAMS['port']}/{DB_PARAMS['dbname']}"
+
+# # Initialize FastAPI app
+# app = FastAPI(
+# title="Smartlead API - Complete Integration",
+# version="2.0.0",
+# description="Comprehensive FastAPI wrapper for Smartlead email automation platform",
+# docs_url="/docs",
+# redoc_url="/redoc"
+# )
+
+# # Add CORS middleware
+# app.add_middleware(
+# CORSMiddleware,
+# allow_origins=["*"],
+# allow_credentials=True,
+# allow_methods=["*"],
+# allow_headers=["*"],
+# )
+
+# # ============================================================================
+# # DATA MODELS
+# # ============================================================================
+
+# class CreateCampaignRequest(BaseModel):
+# name: str = Field(..., description="Campaign name")
+# client_id: Optional[int] = Field(None, description="Client ID (leave null if no client)")
+
+# class CampaignScheduleRequest(BaseModel):
+# timezone: str = Field(..., description="Timezone for the campaign schedule (e.g., 'America/Los_Angeles')")
+# days_of_the_week: List[int] = Field(..., description="Days of the week for scheduling [0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday]")
+# start_hour: str = Field(..., description="Start hour for sending emails in HH:MM format (e.g., '09:00')")
+# end_hour: str = Field(..., description="End hour for sending emails in HH:MM format (e.g., '18:00')")
+# min_time_btw_emails: int = Field(..., description="Minimum time in minutes between sending emails")
+# max_new_leads_per_day: int = Field(..., description="Maximum number of new leads to process per day")
+# schedule_start_time: str = Field(..., description="Schedule start time in ISO 8601 format (e.g., '2023-04-25T07:29:25.978Z')")
+
+# class CampaignSettingsRequest(BaseModel):
+# track_settings: List[str] = Field(..., description="Tracking settings array (allowed values: DONT_TRACK_EMAIL_OPEN, DONT_TRACK_LINK_CLICK, DONT_TRACK_REPLY_TO_AN_EMAIL)")
+# stop_lead_settings: str = Field(..., description="Settings for stopping leads (allowed values: CLICK_ON_A_LINK, OPEN_AN_EMAIL)")
+# unsubscribe_text: str = Field(..., description="Text for the unsubscribe link")
+# send_as_plain_text: bool = Field(..., description="Whether emails should be sent as plain text")
+# follow_up_percentage: int = Field(ge=0, le=100, description="Follow-up percentage (max 100, min 0)")
+# client_id: Optional[int] = Field(None, description="Client ID (leave as null if not needed)")
+# enable_ai_esp_matching: bool = Field(False, description="Enable AI ESP matching (by default is false)")
+
+# class LeadInput(BaseModel):
+# first_name: Optional[str] = Field(None, description="Lead's first name")
+# last_name: Optional[str] = Field(None, description="Lead's last name")
+# email: str = Field(..., description="Lead's email address")
+# phone_number: Optional[Union[str, int]] = Field(None, description="Lead's phone number (can be string or integer)")
+# company_name: Optional[str] = Field(None, description="Lead's company name")
+# website: Optional[str] = Field(None, description="Lead's website")
+# location: Optional[str] = Field(None, description="Lead's location")
+# custom_fields: Optional[Dict[str, str]] = Field(None, description="Custom fields as key-value pairs (max 20 fields)")
+# linkedin_profile: Optional[str] = Field(None, description="Lead's LinkedIn profile URL")
+# company_url: Optional[str] = Field(None, description="Company website URL")
+
+# @field_validator('custom_fields')
+# @classmethod
+# def validate_custom_fields(cls, v):
+# if v is not None and len(v) > 20:
+# raise ValueError('Custom fields cannot exceed 20 fields')
+# return v
+
+# @field_validator('phone_number')
+# @classmethod
+# def validate_phone_number(cls, v):
+# if v is not None:
+# # Convert to string if it's an integer
+# return str(v)
+# return v
+
+# class LeadSettings(BaseModel):
+# ignore_global_block_list: bool = Field(True, description="Ignore leads if they are in the global block list")
+# ignore_unsubscribe_list: bool = Field(True, description="Ignore leads if they are in the unsubscribe list")
+# ignore_duplicate_leads_in_other_campaign: bool = Field(False, description="Allow leads to be added even if they are duplicates in other campaigns")
+
+# class AddLeadsRequest(BaseModel):
+# lead_list: List[LeadInput] = Field(..., max_items=100, description="List of leads to add (maximum 100 leads)")
+# settings: Optional[LeadSettings] = Field(None, description="Settings for lead processing")
+
+# class AddLeadsResponse(BaseModel):
+# ok: bool = Field(..., description="Indicates if the operation was successful")
+# upload_count: int = Field(..., description="Number of leads successfully uploaded")
+# total_leads: int = Field(..., description="Total number of leads attempted to upload")
+# already_added_to_campaign: int = Field(..., description="Number of leads already present in the campaign")
+# duplicate_count: int = Field(..., description="Number of duplicate emails found")
+# invalid_email_count: int = Field(..., description="Number of leads with invalid email format")
+# unsubscribed_leads: Any = Field(..., description="Number of leads that had previously unsubscribed (can be int or empty list)")
+
+# class SeqDelayDetails(BaseModel):
+# delay_in_days: int = Field(..., description="Delay in days before sending this sequence")
+
+# class SeqVariant(BaseModel):
+# subject: str = Field(..., description="Email subject line")
+# email_body: str = Field(..., description="Email body content (HTML format)")
+# variant_label: str = Field(..., description="Variant label (A, B, C, etc.)")
+# id: Optional[int] = Field(None, description="Variant ID (only for updating, not for creating)")
+
+# class CampaignSequence(BaseModel):
+# id: Optional[int] = Field(None, description="Sequence ID (only for updating, not for creating)")
+# seq_number: int = Field(..., description="Sequence number (1, 2, 3, etc.)")
+# seq_delay_details: SeqDelayDetails = Field(..., description="Delay details for this sequence")
+# seq_variants: Optional[List[SeqVariant]] = Field(None, description="Email variants for A/B testing")
+# subject: Optional[str] = Field("", description="Subject line (blank for follow-up in same thread)")
+# email_body: Optional[str] = Field(None, description="Email body content (HTML format)")
+
+# class SaveSequencesRequest(BaseModel):
+# sequences: List[CampaignSequence] = Field(..., description="List of campaign sequences")
+
+# class Job(BaseModel):
+# id: str
+# company_name: Optional[str] = None
+# job_title: Optional[str] = None
+# job_location: Optional[str] = None
+# company_blurb: Optional[str] = None
+# company_culture: Optional[str] = None
+# description: Optional[str] = None
+# company_size: Optional[str] = None
+# requirements: Optional[str] = None
+# salary: Optional[str] = None
+
+# class GenerateSequencesRequest(BaseModel):
+# job_id: str = Field(..., description="Job ID to fetch from database and generate sequences for")
+
+# class Campaign(BaseModel):
+# id: int
+# user_id: int
+# created_at: datetime
+# updated_at: datetime
+# status: str
+# name: str
+# track_settings: Union[str, List[Any]] # FIX: Accept string or list
+# scheduler_cron_value: Optional[Union[str, Dict[str, Any]]] = None # FIX: Accept string or dict
+# min_time_btwn_emails: int
+# max_leads_per_day: int
+# stop_lead_settings: str
+# unsubscribe_text: Optional[str] = None
+# client_id: Optional[int] = None
+# enable_ai_esp_matching: bool
+# send_as_plain_text: bool
+# follow_up_percentage: Optional[Union[str, int]] = None # FIX: Accept string or int
+
+# class CampaignListResponse(BaseModel):
+# campaigns: List[Campaign]
+# total: int
+# source: str
+
+# class Lead(BaseModel):
+# id: int
+# email: EmailStr
+# first_name: Optional[str] = None
+# last_name: Optional[str] = None
+# company: Optional[str] = None
+# position: Optional[str] = None
+# phone_number: Optional[str] = None
+# linkedin_url: Optional[str] = None
+# status: Optional[str] = None
+
+# class WarmupDetails(BaseModel):
+# status: str
+# total_sent_count: int
+# total_spam_count: int
+# warmup_reputation: Union[str, int]
+# warmup_key_id: Optional[str] = None
+# warmup_created_at: Optional[datetime] = None
+# reply_rate: Optional[int] = None
+# blocked_reason: Optional[str] = None
+
+# class EmailAccount(BaseModel):
+# id: int
+# created_at: datetime
+# updated_at: datetime
+# user_id: int
+# from_name: str
+# from_email: str
+# username: str
+# password: Optional[str] = None
+# smtp_host: Optional[str] = None
+# smtp_port: Optional[int] = None
+# smtp_port_type: Optional[str] = None
+# message_per_day: int
+# different_reply_to_address: Optional[str] = None
+# is_different_imap_account: bool
+# imap_username: Optional[str] = None
+# imap_password: Optional[str] = None
+# imap_host: Optional[str] = None
+# imap_port: Optional[int] = None
+# imap_port_type: Optional[str] = None
+# signature: Optional[str] = None
+# custom_tracking_domain: Optional[str] = None
+# bcc_email: Optional[str] = None
+# is_smtp_success: bool
+# is_imap_success: bool
+# smtp_failure_error: Optional[str] = None
+# imap_failure_error: Optional[str] = None
+# type: str
+# daily_sent_count: int
+# client_id: Optional[int] = None
+# campaign_count: Optional[int] = None
+# warmup_details: Optional[WarmupDetails] = None
+
+# class WarmupSettingsRequest(BaseModel):
+# warmup_enabled: bool
+# total_warmup_per_day: Optional[int] = None
+# daily_rampup: Optional[int] = None
+# reply_rate_percentage: Optional[int] = None
+# warmup_key_id: Optional[str] = Field(None, description="String value if passed will update the custom warmup-key identifier")
+
+# class LeadCategoryUpdateRequest(BaseModel):
+# category_id: int = Field(..., description="Category ID to assign to the lead")
+# pause_lead: bool = Field(False, description="Whether to pause the lead after category update")
+
+# class CampaignStatusUpdateRequest(BaseModel):
+# status: str = Field(..., description="New campaign status (PAUSED, STOPPED, START)")
+
+# class ResumeLeadRequest(BaseModel):
+# resume_lead_with_delay_days: Optional[int] = Field(None, description="Delay in days before resuming (defaults to 0)")
+
+# class DomainBlockListRequest(BaseModel):
+# domain_block_list: List[str] = Field(..., description="List of domains/emails to block")
+# client_id: Optional[int] = Field(None, description="Client ID if blocking is client-specific")
+
+# class WebhookRequest(BaseModel):
+# id: Optional[int] = Field(None, description="Webhook ID (null for creating new)")
+# name: str = Field(..., description="Webhook name")
+# webhook_url: str = Field(..., description="Webhook URL")
+# event_types: List[str] = Field(..., description="List of event types to listen for")
+# categories: Optional[List[str]] = Field(None, description="List of categories to filter by")
+
+# class WebhookDeleteRequest(BaseModel):
+# id: int = Field(..., description="Webhook ID to delete")
+
+# class ClientRequest(BaseModel):
+# name: str = Field(..., description="Client name")
+# email: str = Field(..., description="Client email")
+# permission: List[str] = Field(..., description="List of permissions")
+# logo: Optional[str] = Field(None, description="Client logo text")
+# logo_url: Optional[str] = Field(None, description="Client logo URL")
+# password: str = Field(..., description="Client password")
+
+# class MessageHistoryRequest(BaseModel):
+# email_stats_id: str = Field(..., description="Email stats ID for the specific email")
+# email_body: str = Field(..., description="Reply message email body")
+# reply_message_id: str = Field(..., description="Message ID to reply to")
+# reply_email_time: str = Field(..., description="Time of the message being replied to")
+# reply_email_body: str = Field(..., description="Body of the message being replied to")
+# cc: Optional[str] = Field(None, description="CC recipients")
+# bcc: Optional[str] = Field(None, description="BCC recipients")
+# add_signature: bool = Field(True, description="Whether to add signature")
+
+# # ============================================================================
+# # DATABASE HELPER FUNCTIONS
+# # ============================================================================
+
+# def get_database_connection():
+# """Get database connection"""
+# try:
+# conn_string = f"postgresql://{DB_PARAMS['user']}:{DB_PARAMS['password']}@{DB_PARAMS['host']}:{DB_PARAMS['port']}/{DB_PARAMS['dbname']}"
+# return create_engine(conn_string)
+# except Exception as e:
+# raise HTTPException(status_code=500, detail=f"Database connection failed: {str(e)}")
+
+# def fetch_job_by_id(job_id: str) -> Job:
+# """Fetch job details from database by ID using pandas DataFrame"""
+# try:
+# conn = get_database_connection()
+# df = pd.read_sql_table("jobs", con=conn)
+
+# # Filter the DataFrame to find the job with the specified ID
+# job_row = df[df['job_id'] == job_id]
+
+# if job_row.empty:
+# raise HTTPException(status_code=404, detail=f"Job with ID {job_id} not found")
+
+# # Get the first (and should be only) row
+# return Job(
+# id=str(job_row.iloc[0]['job_id']),
+# company_name=str(job_row.iloc[0]['company_name']) if pd.notna(job_row.iloc[0]['company_name']) else None,
+# job_title=str(job_row.iloc[0]['job_title']) if pd.notna(job_row.iloc[0]['job_title']) else None,
+# job_location=str(job_row.iloc[0]['job_location']) if pd.notna(job_row.iloc[0]['job_location']) else None,
+# company_blurb=str(job_row.iloc[0]['company_blurb']) if pd.notna(job_row.iloc[0]['company_blurb']) else None,
+# company_culture=str(job_row.iloc[0]['company_culture']) if pd.notna(job_row.iloc[0]['company_culture']) else None,
+# description=str(job_row.iloc[0]['description']) if pd.notna(job_row.iloc[0]['description']) else None,
+# company_size=str(job_row.iloc[0]['company_size']) if pd.notna(job_row.iloc[0]['company_size']) else None,
+# requirements=str(job_row.iloc[0]['requirements']) if pd.notna(job_row.iloc[0]['requirements']) else None,
+# salary=str(job_row.iloc[0]['salary']) if pd.notna(job_row.iloc[0]['salary']) else None
+# )
+
+# except HTTPException:
+# raise
+# except Exception as e:
+# raise HTTPException(status_code=500, detail=f"Error fetching job: {str(e)}")
+
+# def list_available_jobs(limit: int = 10) -> List[Dict[str, Any]]:
+# """List available jobs from database using pandas DataFrame"""
+# try:
+# conn = get_database_connection()
+# df = pd.read_sql_table("jobs", con=conn)
+
+# # Select only the required columns and limit the results
+# selected_columns = ['job_id', 'company_name', 'job_title', 'job_location', 'company_size', 'salary']
+# df_subset = df[selected_columns].head(limit)
+
+# # Convert DataFrame to list of dictionaries
+# jobs_list = []
+# for _, row in df_subset.iterrows():
+# jobs_list.append({
+# "id": str(row['job_id']), # Ensure ID is returned as string
+# "company_name": str(row['company_name']) if pd.notna(row['company_name']) else None,
+# "job_title": str(row['job_title']) if pd.notna(row['job_title']) else None,
+# "job_location": str(row['job_location']) if pd.notna(row['job_location']) else None,
+# "company_size": str(row['company_size']) if pd.notna(row['company_size']) else None,
+# "salary": str(row['salary']) if pd.notna(row['salary']) else None
+# })
+
+# return jobs_list
+
+# except Exception as e:
+# raise HTTPException(status_code=500, detail=f"Error fetching jobs: {str(e)}")
+
+# def build_job_description(job: Job) -> str:
+# """Build a comprehensive job description from job details"""
+# parts = []
+
+# if job.company_name:
+# parts.append(f"Company: {job.company_name}")
+
+# if job.job_title:
+# parts.append(f"Position: {job.job_title}")
+
+# if job.job_location:
+# parts.append(f"Location: {job.job_location}")
+
+# if job.company_size:
+# parts.append(f"Company Size: {job.company_size}")
+
+# if job.salary:
+# parts.append(f"Salary: {job.salary}")
+
+# if job.company_blurb:
+# parts.append(f"About the Company: {job.company_blurb}")
+
+# if job.company_culture:
+# parts.append(f"Company Culture: {job.company_culture}")
+
+# if job.description:
+# parts.append(f"Job Description: {job.description}")
+
+# if job.requirements:
+# parts.append(f"Requirements: {job.requirements}")
+
+# return "\n\n".join(parts)
+
+# # ============================================================================
+# # HELPER FUNCTIONS
+# # ============================================================================
+
+# def _get_smartlead_url(endpoint: str) -> str:
+# return f"{SMARTLEAD_BASE_URL}/{endpoint.lstrip('/')}"
+
+# async def call_smartlead_api(method: str, endpoint: str, data: Any = None, params: Dict[str, Any] = None) -> Any:
+# if SMARTLEAD_API_KEY == "your-api-key-here":
+# raise HTTPException(status_code=400, detail="Smartlead API key not configured")
+# if params is None:
+# params = {}
+# params['api_key'] = SMARTLEAD_API_KEY
+# url = _get_smartlead_url(endpoint)
+
+# try:
+# async with httpx.AsyncClient(timeout=30.0) as client:
+# request_kwargs = {"params": params}
+# if data is not None:
+# request_kwargs["json"] = data
+
+# resp = await client.request(method, url, **request_kwargs)
+
+# if resp.status_code >= 400:
+# try:
+# error_data = resp.json()
+# error_message = error_data.get('message', error_data.get('error', 'Unknown error'))
+# raise HTTPException(status_code=resp.status_code, detail=error_message)
+# except (ValueError, KeyError):
+# raise HTTPException(status_code=resp.status_code, detail=resp.text)
+
+# return resp.json()
+
+# except httpx.TimeoutException:
+# raise HTTPException(status_code=408, detail="Request to Smartlead API timed out")
+# except httpx.RequestError as e:
+# raise HTTPException(status_code=503, detail=f"Failed to connect to Smartlead API: {str(e)}")
+
+# # ============================================================================
+# # CAMPAIGN ENDPOINTS
+# # ============================================================================
+
+# @app.post("/campaigns/create", response_model=Dict[str, Any], tags=["Campaigns"])
+# async def create_campaign(campaign: CreateCampaignRequest):
+# """Create a new campaign in Smartlead"""
+# return await call_smartlead_api("POST", "campaigns/create", data=campaign.dict())
+
+# @app.get("/campaigns", response_model=CampaignListResponse, tags=["Campaigns"])
+# async def list_campaigns():
+# """Fetch all campaigns from Smartlead API"""
+# campaigns = await call_smartlead_api("GET", "campaigns")
+# return {"campaigns": campaigns, "total": len(campaigns), "source": "smartlead"}
+
+# @app.get("/campaigns/{campaign_id}", response_model=Campaign, tags=["Campaigns"])
+# async def get_campaign(campaign_id: int):
+# """Get Campaign By Id"""
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}")
+
+# @app.post("/campaigns/{campaign_id}/settings", response_model=Dict[str, Any], tags=["Campaigns"])
+# async def update_campaign_settings(campaign_id: int, settings: CampaignSettingsRequest):
+# """Update Campaign General Settings"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/settings", data=settings.dict())
+
+# @app.post("/campaigns/{campaign_id}/schedule", response_model=Dict[str, Any], tags=["Campaigns"])
+# async def schedule_campaign(campaign_id: int, schedule: CampaignScheduleRequest):
+# """Update Campaign Schedule"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/schedule", data=schedule.dict())
+
+# @app.delete("/campaigns/{campaign_id}", response_model=Dict[str, Any], tags=["Campaigns"])
+# async def delete_campaign(campaign_id: int):
+# """Delete Campaign"""
+# return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}")
+
+# @app.post("/campaigns/{campaign_id}/status", response_model=Dict[str, Any], tags=["Campaigns"])
+# async def patch_campaign_status(campaign_id: int, request: CampaignStatusUpdateRequest):
+# """Patch campaign status"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/status", data=request.dict())
+
+# @app.get("/campaigns/{campaign_id}/analytics", response_model=Any, tags=["Analytics"])
+# async def campaign_analytics(campaign_id: int):
+# """Fetch analytics for a campaign"""
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/analytics")
+
+# @app.get("/campaigns/{campaign_id}/statistics", response_model=Dict[str, Any], tags=["Analytics"])
+# async def fetch_campaign_statistics_by_campaign_id(
+# campaign_id: int,
+# offset: int = 0,
+# limit: int = 100,
+# email_sequence_number: Optional[int] = None,
+# email_status: Optional[str] = None
+# ):
+# """Fetch Campaign Statistics By Campaign Id"""
+# params = {"offset": offset, "limit": limit}
+# if email_sequence_number:
+# params["email_sequence_number"] = email_sequence_number
+# if email_status:
+# params["email_status"] = email_status
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/statistics", params=params)
+
+# @app.get("/campaigns/{campaign_id}/analytics-by-date", response_model=Dict[str, Any], tags=["Analytics"])
+# async def fetch_campaign_statistics_by_date_range(
+# campaign_id: int,
+# start_date: str,
+# end_date: str
+# ):
+# """Fetch Campaign Statistics By Campaign Id And Date Range"""
+# params = {"start_date": start_date, "end_date": end_date}
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/analytics-by-date", params=params)
+
+# # ============================================================================
+# # LEAD MANAGEMENT ENDPOINTS
+# # ============================================================================
+
+# @app.get("/campaigns/{campaign_id}/leads", response_model=Dict[str, Any], tags=["Leads"])
+# async def get_campaign_leads(campaign_id: int, offset: int = 0, limit: int = 100):
+# """List all leads by campaign id"""
+# params = {"offset": offset, "limit": limit}
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/leads", params=params)
+
+# @app.post("/campaigns/{campaign_id}/leads", response_model=Dict[str, Any], tags=["Leads"])
+# async def add_leads_to_campaign(campaign_id: int, request: AddLeadsRequest):
+# """Add leads to a campaign by ID with personalized welcome and closing messages"""
+# request_data = request.dict()
+
+# # Process each lead to generate personalized messages and clean up data
+# for lead in request_data.get("lead_list", []):
+# lead_cleaned = {k: v for k, v in lead.items() if v is not None and v != ""}
+
+# # Generate personalized welcome and closing messages using LLM
+# try:
+# personalized_messages = await generate_welcome_closing_messages(lead_cleaned)
+
+# # Initialize custom_fields if it doesn't exist
+# if "custom_fields" not in lead_cleaned:
+# lead_cleaned["custom_fields"] = {}
+
+# # Add the generated messages to custom_fields
+# if personalized_messages.get("welcome_message"):
+# lead_cleaned["custom_fields"]["Welcome_Message"] = personalized_messages["welcome_message"]
+# if personalized_messages.get("closing_message"):
+# lead_cleaned["custom_fields"]["Closing_Message"] = personalized_messages["closing_message"]
+
+# except Exception as e:
+# print(f"Error generating personalized messages for lead {lead_cleaned.get('email', 'unknown')}: {str(e)}")
+# # Continue with template messages if LLM fails
+# template_messages = generate_template_welcome_closing_messages(lead_cleaned)
+# if "custom_fields" not in lead_cleaned:
+# lead_cleaned["custom_fields"] = {}
+# if template_messages.get("welcome_message"):
+# lead_cleaned["custom_fields"]["Welcome_Message"] = template_messages["welcome_message"]
+# if template_messages.get("closing_message"):
+# lead_cleaned["custom_fields"]["Closing_Message"] = template_messages["closing_message"]
+
+# # Clean up custom_fields - remove None values and empty strings
+# if "custom_fields" in lead_cleaned:
+# custom_fields = lead_cleaned["custom_fields"]
+# if custom_fields:
+# custom_fields_cleaned = {k: v for k, v in custom_fields.items() if v is not None and v != ""}
+# if custom_fields_cleaned:
+# lead_cleaned["custom_fields"] = custom_fields_cleaned
+# else:
+# lead_cleaned.pop("custom_fields", None)
+# else:
+# lead_cleaned.pop("custom_fields", None)
+
+# lead.clear()
+# lead.update(lead_cleaned)
+
+# request_data["lead_list"] = [lead for lead in request_data["lead_list"] if lead]
+
+# if not request_data["lead_list"]:
+# raise HTTPException(status_code=400, detail="No valid leads to add.")
+
+# if "settings" not in request_data or request_data["settings"] is None:
+# request_data["settings"] = LeadSettings().dict()
+
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads", data=request_data)
+
+# @app.post("/campaigns/{campaign_id}/leads/bulk", response_model=Dict[str, Any], tags=["Leads"])
+# async def add_bulk_leads(campaign_id: int, leads: List[LeadInput]):
+# """Add multiple leads to a Smartlead campaign with personalized messages (legacy endpoint)"""
+# request = AddLeadsRequest(lead_list=leads)
+# return await add_leads_to_campaign(campaign_id, request)
+
+# @app.post("/campaigns/{campaign_id}/leads/{lead_id}/resume", response_model=Dict[str, Any], tags=["Leads"])
+# async def resume_lead_by_campaign_id(campaign_id: int, lead_id: int, request: ResumeLeadRequest):
+# """Resume Lead By Campaign ID"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/resume", data=request.dict())
+
+# @app.post("/campaigns/{campaign_id}/leads/{lead_id}/pause", response_model=Dict[str, Any], tags=["Leads"])
+# async def pause_lead_by_campaign_id(campaign_id: int, lead_id: int):
+# """Pause Lead By Campaign ID"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/pause")
+
+# @app.delete("/campaigns/{campaign_id}/leads/{lead_id}", response_model=Dict[str, Any], tags=["Leads"])
+# async def delete_lead_by_campaign_id(campaign_id: int, lead_id: int):
+# """Delete Lead By Campaign ID"""
+# return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}/leads/{lead_id}")
+
+# @app.post("/campaigns/{campaign_id}/leads/{lead_id}/unsubscribe", response_model=Dict[str, Any], tags=["Leads"])
+# async def unsubscribe_lead_from_campaign(campaign_id: int, lead_id: int):
+# """Unsubscribe/Pause Lead From Campaign"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/unsubscribe")
+
+# @app.post("/leads/{lead_id}/unsubscribe", response_model=Dict[str, Any], tags=["Leads"])
+# async def unsubscribe_lead_from_all_campaigns(lead_id: int):
+# """Unsubscribe Lead From All Campaigns"""
+# return await call_smartlead_api("POST", f"leads/{lead_id}/unsubscribe")
+
+# @app.post("/leads/{lead_id}", response_model=Dict[str, Any], tags=["Leads"])
+# async def update_lead(lead_id: int, lead_data: Dict[str, Any]):
+# """Update lead using the Lead ID"""
+# return await call_smartlead_api("POST", f"leads/{lead_id}", data=lead_data)
+
+# @app.post("/campaigns/{campaign_id}/leads/{lead_id}/category", response_model=Dict[str, Any], tags=["Leads"])
+# async def update_lead_category_by_campaign(campaign_id: int, lead_id: int, request: LeadCategoryUpdateRequest):
+# """Update a lead's category based on their campaign"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/category", data=request.dict())
+
+# @app.post("/leads/add-domain-block-list", response_model=Dict[str, Any], tags=["Leads"])
+# async def add_domain_to_global_block_list(request: DomainBlockListRequest):
+# """Add Lead/Domain to Global Block List"""
+# return await call_smartlead_api("POST", "leads/add-domain-block-list", data=request.dict())
+
+# @app.get("/leads/fetch-categories", response_model=List[Dict[str, Any]], tags=["Leads"])
+# async def fetch_lead_categories():
+# """Fetch lead categories"""
+# return await call_smartlead_api("GET", "leads/fetch-categories")
+
+# @app.get("/leads", response_model=Dict[str, Any], tags=["Leads"])
+# async def fetch_lead_by_email_address(email: str):
+# """Fetch lead by email address"""
+# return await call_smartlead_api("GET", "leads", params={"email": email})
+
+# @app.get("/leads/{lead_id}/campaigns", response_model=List[Dict[str, Any]], tags=["Leads"])
+# async def campaigns_for_lead(lead_id: int):
+# """Fetch all campaigns that a lead belongs to"""
+# return await call_smartlead_api("GET", f"leads/{lead_id}/campaigns")
+
+# @app.get("/campaigns/{campaign_id}/leads/check", response_model=Dict[str, Any], tags=["Leads"])
+# async def check_lead_in_campaign(campaign_id: int, email: str):
+# """Check if a lead exists in a campaign using efficient indexed lookups"""
+# try:
+# lead_response = await call_smartlead_api("GET", "leads", params={"email": email})
+
+# if not lead_response or "id" not in lead_response:
+# return {"exists": False, "message": "Lead not found"}
+
+# lead_id = lead_response["id"]
+# campaigns_response = await call_smartlead_api("GET", f"leads/{lead_id}/campaigns")
+
+# if not campaigns_response:
+# return {"exists": False, "message": "No campaigns found for lead"}
+
+# campaign_exists = any(campaign.get("id") == campaign_id for campaign in campaigns_response)
+
+# return {"exists": campaign_exists, "message": "Lead found in campaign" if campaign_exists else "Lead not found in campaign"}
+
+# except HTTPException as e:
+# if e.status_code == 404:
+# return {"exists": False, "message": "Lead not found"}
+# raise e
+# except Exception as e:
+# raise HTTPException(status_code=500, detail=f"Error checking lead in campaign: {str(e)}")
+
+# @app.get("/campaigns/{campaign_id}/leads-export", tags=["Leads"])
+# async def export_data_from_campaign(campaign_id: int):
+# """Export data from a campaign as CSV"""
+# if SMARTLEAD_API_KEY == "your-api-key-here":
+# raise HTTPException(status_code=400, detail="Smartlead API key not configured")
+
+# url = _get_smartlead_url(f"campaigns/{campaign_id}/leads-export")
+# params = {"api_key": SMARTLEAD_API_KEY}
+
+# try:
+# async with httpx.AsyncClient(timeout=30.0) as client:
+# resp = await client.get(url, params=params)
+
+# if resp.status_code >= 400:
+# try:
+# error_data = resp.json()
+# error_message = error_data.get('message', error_data.get('error', 'Unknown error'))
+# raise HTTPException(status_code=resp.status_code, detail=error_message)
+# except (ValueError, KeyError):
+# raise HTTPException(status_code=resp.status_code, detail=resp.text)
+
+# return Response(
+# content=resp.text,
+# media_type="text/csv",
+# headers={"Content-Disposition": f"attachment; filename=campaign_{campaign_id}_leads.csv"}
+# )
+
+# except httpx.TimeoutException:
+# raise HTTPException(status_code=408, detail="Request to Smartlead API timed out")
+# except httpx.RequestError as e:
+# raise HTTPException(status_code=503, detail=f"Failed to connect to Smartlead API: {str(e)}")
+
+# # ============================================================================
+# # SEQUENCE ENDPOINTS
+# # ============================================================================
+
+# @app.get("/campaigns/{campaign_id}/sequences", response_model=Any, tags=["Sequences"])
+# async def get_campaign_sequences(campaign_id: int):
+# """Fetch email sequences for a campaign"""
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/sequences")
+
+# @app.post("/campaigns/{campaign_id}/sequences", response_model=Dict[str, Any], tags=["Sequences"])
+# async def save_campaign_sequences(campaign_id: int, request: SaveSequencesRequest):
+# """Save Campaign Sequence"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/sequences", data=request.dict())
+
+# @app.post("/campaigns/{campaign_id}/sequences/generate", response_model=Dict[str, Any], tags=["Sequences"])
+# async def generate_campaign_sequences(campaign_id: int, request: GenerateSequencesRequest):
+# """Generate Campaign Sequences using LLM"""
+# # Fetch job details from database using job_id
+# job = fetch_job_by_id(request.job_id)
+# job_description = build_job_description(job)
+
+# generated_sequences = await generate_sequences_with_llm(job_description)
+# save_request = SaveSequencesRequest(sequences=generated_sequences)
+# result = await call_smartlead_api("POST", f"campaigns/{campaign_id}/sequences", data=save_request.dict())
+
+# return {
+# "ok": True,
+# "message": "Sequences generated and saved successfully",
+# "job_details": {
+# "id": job.id,
+# "company_name": job.company_name,
+# "job_title": job.job_title,
+# "job_location": job.job_location
+# },
+# "generated_sequences": [seq for seq in generated_sequences],
+# "save_result": result
+# }
+
+# # ============================================================================
+# # WEBHOOK ENDPOINTS
+# # ============================================================================
+
+# @app.get("/campaigns/{campaign_id}/webhooks", response_model=List[Dict[str, Any]], tags=["Webhooks"])
+# async def fetch_webhooks_by_campaign_id(campaign_id: int):
+# """Fetch Webhooks By Campaign ID"""
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/webhooks")
+
+# @app.post("/campaigns/{campaign_id}/webhooks", response_model=Dict[str, Any], tags=["Webhooks"])
+# async def add_update_campaign_webhook(campaign_id: int, request: WebhookRequest):
+# """Add / Update Campaign Webhook"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/webhooks", data=request.dict())
+
+# @app.delete("/campaigns/{campaign_id}/webhooks", response_model=Dict[str, Any], tags=["Webhooks"])
+# async def delete_campaign_webhook(campaign_id: int, request: WebhookDeleteRequest):
+# """Delete Campaign Webhook"""
+# return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}/webhooks", data=request.dict())
+
+# # ============================================================================
+# # CLIENT MANAGEMENT ENDPOINTS
+# # ============================================================================
+
+# @app.post("/client/save", response_model=Dict[str, Any], tags=["Clients"])
+# async def add_client_to_system(request: ClientRequest):
+# """Add Client To System (Whitelabel or not)"""
+# return await call_smartlead_api("POST", "client/save", data=request.dict())
+
+# @app.get("/client", response_model=List[Dict[str, Any]], tags=["Clients"])
+# async def fetch_all_clients():
+# """Fetch all clients"""
+# return await call_smartlead_api("GET", "client")
+
+# # ============================================================================
+# # MESSAGE HISTORY AND REPLY ENDPOINTS
+# # ============================================================================
+
+# @app.get("/campaigns/{campaign_id}/leads/{lead_id}/message-history", response_model=Dict[str, Any], tags=["Messages"])
+# async def fetch_lead_message_history_based_on_campaign(campaign_id: int, lead_id: int):
+# """Fetch Lead Message History Based On Campaign"""
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/leads/{lead_id}/message-history")
+
+# @app.post("/campaigns/{campaign_id}/reply-email-thread", response_model=Dict[str, Any], tags=["Messages"])
+# async def reply_to_lead_from_master_inbox(campaign_id: int, request: MessageHistoryRequest):
+# """Reply To Lead From Master Inbox via API"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/reply-email-thread", data=request.dict())
+
+# # ============================================================================
+# # EMAIL ACCOUNT ENDPOINTS
+# # ============================================================================
+
+# @app.get("/email-accounts", response_model=List[EmailAccount], tags=["Email Accounts"])
+# async def list_email_accounts(offset: int = 0, limit: int = 100):
+# """List all email accounts with optional pagination"""
+# params = {"offset": offset, "limit": limit}
+# return await call_smartlead_api("GET", "email-accounts", params=params)
+
+# @app.post("/email-accounts/save", response_model=Any, tags=["Email Accounts"])
+# async def save_email_account(account: Dict[str, Any]):
+# """Create an Email Account"""
+# return await call_smartlead_api("POST", "email-accounts/save", data=account)
+
+# @app.post("/email-accounts/reconnect-failed-email-accounts", response_model=Dict[str, Any], tags=["Email Accounts"])
+# async def reconnect_failed_email_accounts(body: Optional[Dict] = {}):
+# """Reconnect failed email accounts"""
+# return await call_smartlead_api("POST", "email-accounts/reconnect-failed-email-accounts", data={})
+
+# @app.get("/email-accounts/{account_id}", response_model=EmailAccount, tags=["Email Accounts"])
+# async def get_email_account(account_id: int):
+# """Fetch Email Account By ID"""
+# return await call_smartlead_api("GET", f"email-accounts/{account_id}")
+
+# @app.post("/email-accounts/{account_id}", response_model=Any, tags=["Email Accounts"])
+# async def update_email_account(account_id: int, payload: Dict[str, Any]):
+# """Update Email Account"""
+# return await call_smartlead_api("POST", f"email-accounts/{account_id}", data=payload)
+
+# @app.post("/email-accounts/{account_id}/warmup", response_model=Any, tags=["Email Accounts"])
+# async def set_warmup(account_id: int, payload: WarmupSettingsRequest):
+# """Add/Update Warmup To Email Account"""
+# return await call_smartlead_api("POST", f"email-accounts/{account_id}/warmup", data=payload.dict(exclude_none=True))
+
+# @app.get("/email-accounts/{account_id}/warmup-stats", response_model=Any, tags=["Email Accounts"])
+# async def get_warmup_stats(account_id: int):
+# """Fetch Warmup Stats By Email Account ID"""
+# return await call_smartlead_api("GET", f"email-accounts/{account_id}/warmup-stats")
+
+# @app.get("/campaigns/{campaign_id}/email-accounts", response_model=List[EmailAccount], tags=["Email Accounts"])
+# async def list_campaign_email_accounts(campaign_id: int):
+# """List all email accounts per campaign"""
+# return await call_smartlead_api("GET", f"campaigns/{campaign_id}/email-accounts")
+
+# @app.post("/campaigns/{campaign_id}/email-accounts", response_model=Any, tags=["Email Accounts"])
+# async def add_campaign_email_accounts(campaign_id: int, payload: Dict[str, Any]):
+# """Add Email Account To A Campaign"""
+# return await call_smartlead_api("POST", f"campaigns/{campaign_id}/email-accounts", data=payload)
+
+# @app.delete("/campaigns/{campaign_id}/email-accounts", response_model=Any, tags=["Email Accounts"])
+# async def remove_campaign_email_accounts(campaign_id: int, payload: Dict[str, Any]):
+# """Remove Email Account From A Campaign"""
+# return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}/email-accounts", data=payload)
+
+# # ============================================================================
+# # UTILITY ENDPOINTS
+# # ============================================================================
+
+# @app.get("/health", response_model=Dict[str, Any], tags=["Utilities"])
+# async def health_check():
+# """Health check endpoint to verify API connectivity"""
+# try:
+# campaigns = await call_smartlead_api("GET", "campaigns")
+# return {
+# "status": "healthy",
+# "message": "Smartlead API is accessible",
+# "campaigns_count": len(campaigns) if isinstance(campaigns, list) else 0,
+# "timestamp": datetime.now().isoformat()
+# }
+# except Exception as e:
+# return {
+# "status": "unhealthy",
+# "message": f"Smartlead API connection failed: {str(e)}",
+# "timestamp": datetime.now().isoformat()
+# }
+
+# @app.get("/api-info", response_model=Dict[str, Any], tags=["Utilities"])
+# async def api_info():
+# """Get information about the API and available endpoints"""
+# return {
+# "name": "Smartlead API - Complete Integration",
+# "version": "2.0.0",
+# "description": "Comprehensive FastAPI wrapper for Smartlead email automation platform",
+# "base_url": SMARTLEAD_BASE_URL,
+# "available_endpoints": [
+# "Campaign Management",
+# "Lead Management",
+# "Sequence Management",
+# "Webhook Management",
+# "Client Management",
+# "Message History & Reply",
+# "Analytics",
+# "Email Account Management",
+# "Job Management"
+# ],
+# "documentation": "Based on Smartlead API documentation",
+# "timestamp": datetime.now().isoformat()
+# }
+
+# # ============================================================================
+# # JOB MANAGEMENT ENDPOINTS
+# # ============================================================================
+
+# @app.get("/jobs", response_model=List[Dict[str, Any]], tags=["Jobs"])
+# async def get_available_jobs(limit: int = Query(10, ge=1, le=100, description="Number of jobs to return")):
+# """Get list of available jobs from database"""
+# return list_available_jobs(limit)
+
+# @app.get("/jobs/{job_id}", response_model=Job, tags=["Jobs"])
+# async def get_job_by_id(job_id: str):
+# """Get job details by ID from database"""
+# return fetch_job_by_id(job_id)
+
+# # ============================================================================
+# # AI SEQUENCE GENERATION FUNCTIONS
+# # ============================================================================
+
+# async def generate_welcome_closing_messages(lead_data: Dict[str, Any]) -> Dict[str, str]:
+# class structure(BaseModel):
+# welcome_message: str = Field(description="Welcome message for the candidate")
+# closing_message: str = Field(description="Closing message for the candidate")
+
+# """Generate personalized welcome and closing messages using LLM based on candidate details"""
+
+# if not LANGCHAIN_AVAILABLE:
+# return generate_template_welcome_closing_messages(lead_data)
+
+# try:
+# openai_api_key = os.getenv("OPENAI_API_KEY")
+# if not openai_api_key:
+# print("Warning: OPENAI_API_KEY not set. Using template messages.")
+# return generate_template_welcome_closing_messages(lead_data)
+
+# llm = ChatOpenAI(
+# model="gpt-4o-mini",
+# temperature=0.7,
+# openai_api_key=openai_api_key
+# )
+# str_llm = llm.with_structured_output(structure, method="function_calling")
+
+# # Extract relevant information from lead data
+# first_name = lead_data.get("first_name", "")
+# last_name = lead_data.get("last_name", "")
+# company_name = lead_data.get("company_name", "")
+# location = lead_data.get("location", "")
+# title = lead_data.get("custom_fields", {}).get("Title", "")
+# linkedin_profile = lead_data.get("linkedin_profile", "")
+
+# # Create a summary of the candidate's background
+# candidate_info = f"""
+# Name: {first_name}
+# Company: {company_name}
+# Location: {location}
+# Title: {title}
+# LinkedIn: {linkedin_profile}
+# """
+
+# system_prompt = """You are an expert recruiter who creates personalized welcome and closing messages for email campaigns.
+
+# Based on the candidate's information, generate:
+# 1. A personalized welcome message (2-3 sentences) starting with "Hi first_name then change the line using
tag and continue with a friendly introduction about their background/company/role.
+# 2. A personalized closing message (1-2 sentences)
+
+# Requirements:
+# - Professional but friendly tone
+# - Reference their specific background/company/role when possible
+# - Keep messages concise and engaging
+# - Make them feel valued and understood
+
+# IMPORTANT: Respond with ONLY valid JSON. No additional text."""
+
+# prompt_template = ChatPromptTemplate.from_messages([
+# ("system", system_prompt),
+# ("human", "Generate personalized messages for this candidate: {candidate_info}")
+# ])
+
+# messages = prompt_template.format_messages(candidate_info=candidate_info)
+# response = await str_llm.ainvoke(messages)
+
+# try:
+
+# return {
+# "welcome_message": response.welcome_message,
+# "closing_message": response.closing_message
+# }
+
+# except Exception as parse_error:
+# print(f"JSON parsing failed for welcome/closing messages: {parse_error}")
+# return generate_template_welcome_closing_messages(lead_data)
+
+# except Exception as e:
+# print(f"Error generating welcome/closing messages with LLM: {str(e)}")
+# return generate_template_welcome_closing_messages(lead_data)
+
+# def generate_template_welcome_closing_messages(lead_data: Dict[str, Any]) -> Dict[str, str]:
+# """Generate template-based welcome and closing messages as fallback"""
+
+# first_name = lead_data.get("first_name", "")
+# company_name = lead_data.get("company_name", "")
+# title = lead_data.get("custom_fields", {}).get("Title", "")
+
+# # Personalized welcome message
+# if first_name and company_name:
+# welcome_message = f"Hi {first_name}, I came across your profile and was impressed by your work at {company_name}."
+# elif first_name:
+# welcome_message = f"Hi {first_name}, I came across your profile and was impressed by your background."
+# elif company_name:
+# welcome_message = f"Hi there, I came across your profile and was impressed by your work at {company_name}."
+# else:
+# welcome_message = "Hi there, I came across your profile and was impressed by your background."
+
+# # Personalized closing message
+# if first_name:
+# closing_message = f"Looking forward to connecting with you, {first_name}!"
+# else:
+# closing_message = "Looking forward to connecting with you!"
+
+# return {
+# "welcome_message": welcome_message,
+# "closing_message": closing_message
+# }
+
+# async def generate_sequences_with_llm(job_description: str) -> List[CampaignSequence]:
+# class email_seq(BaseModel):
+# subject: str = Field(description="Subject line for the email")
+# body: str = Field(description="Body of the email")
+# class structure(BaseModel):
+# introduction: email_seq = Field(description="Email sequence for sequence 1 asking for consent and interest in the role")
+# email_sequence_2: email_seq = Field(description="Email sequence for sequence 2 following up on updates and next steps")
+# email_sequence_3: email_seq = Field(description="Email sequence for sequence 3 Another variant on following up on updates and next steps")
+
+
+
+# """Generate email sequences using LangChain and OpenAI based on job description"""
+
+# if not LANGCHAIN_AVAILABLE:
+# return await generate_template_sequences(job_description)
+
+# try:
+# openai_api_key = os.getenv("OPENAI_API_KEY")
+# if not openai_api_key:
+# print("Warning: OPENAI_API_KEY not set. Using template sequences.")
+# return await generate_template_sequences(job_description)
+
+# llm = ChatOpenAI(
+# model="gpt-4o-mini",
+# temperature=0.7,
+# openai_api_key=openai_api_key
+# )
+# str_llm = llm.with_structured_output(structure, method="function_calling")
+
+# system_prompt = """You are an expert email sequence template generator for recruitment campaigns.
+
+# Generate ONLY the subject lines and email body content for 3 professional email sequences.
+# Write the email on behalf of Ali Taghikhani, CEO SRN.
+
+# Use the following placeholders in your templates, EXACTLY as writtenm, AlWAYS use double braces for placeholders:
+# - `{{first_name}}`
+# - `{{company_name}}`
+# - `{{Title}}`
+# - `{{Welcome_Message}}`
+# - `{{Closing_Message}}`
+
+# Email Sequence Structure:
+# 1. INTRODUCTION (Day 1): Start the email with `{{Welcome_Message}}`. Ask for consent and interest in the role. End the email with `{{Closing_Message}}` followed by the sender's name and title.
+# 2. OUTREACH (Day 3): Provide detailed job information. This is a follow-up, so it should not have a subject line.
+# 3. FOLLOW-UP (Day 5): Another follow-up on updates and next steps. Also no subject line.
+
+# Requirements:
+# - The first sequence must start with `{{Welcome_Message}}` and end with `{{Closing_Message}}`.
+# - Always end the email with Best regards
+# - The second and third sequences are follow-ups and should not have a subject line.
+# - All emails should be HTML formatted with proper `
` tags.
+# - Professional but friendly tone.
+# - Include clear call-to-actions.
+# - Focus on building consent and trust.
+
+# IMPORTANT: Respond with ONLY valid JSON. No additional text."""
+
+# prompt_template = ChatPromptTemplate.from_messages([
+# ("system", system_prompt),
+# ("human", "Generate email content for this job description: {job_description}")
+# ])
+
+# messages = prompt_template.format_messages(job_description=job_description)
+# response = await str_llm.ainvoke(messages)
+
+# try:
+# # Handle the response structure correctly
+# sequences = []
+
+# # Sequence 1: Introduction with A/B variants
+# if hasattr(response, 'introduction') and response.introduction:
+# # Check if body is a string or dict
+# intro_body = response.introduction.body
+# if isinstance(intro_body, dict):
+# # If it's a dict, extract the content
+# intro_body = str(intro_body)
+
+# sequences.append(CampaignSequence(
+# seq_number=1,
+# seq_delay_details=SeqDelayDetails(delay_in_days=1),
+# seq_variants=[
+# SeqVariant(
+# subject=response.introduction.subject,
+# email_body=intro_body,
+# variant_label="A"
+# )
+# ]
+# ))
+
+# # Sequence 2: Outreach
+# if hasattr(response, 'email_sequence_2') and response.email_sequence_2:
+# seq2_body = response.email_sequence_2.body
+# if isinstance(seq2_body, dict):
+# seq2_body = str(seq2_body)
+
+# sequences.append(CampaignSequence(
+# seq_number=2,
+# seq_delay_details=SeqDelayDetails(delay_in_days=3),
+# subject="",
+# email_body=seq2_body
+# ))
+
+# # Sequence 3: Follow-up
+# if hasattr(response, 'email_sequence_3') and response.email_sequence_3:
+# seq3_body = response.email_sequence_3.body
+# if isinstance(seq3_body, dict):
+# seq3_body = str(seq3_body)
+
+# sequences.append(CampaignSequence(
+# seq_number=3,
+# seq_delay_details=SeqDelayDetails(delay_in_days=5),
+# subject="",
+# email_body=seq3_body
+# ))
+
+# # Fill with templates if needed
+# while len(sequences) < 3:
+# if len(sequences) == 0:
+# sequences.append(CampaignSequence(
+# seq_number=1,
+# seq_delay_details=SeqDelayDetails(delay_in_days=1),
+# seq_variants=[
+# SeqVariant(
+# subject=f"Quick question about {job_description}",
+# email_body=f"""
Hi there,
+# I came across your profile and noticed your experience in {job_description}.
+# I'm reaching out because we have some exciting opportunities that might be a great fit for your background.
+# Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?
+# Would you be interested in hearing more about the roles we have available?
+# Best regards,
+# [Your Name]
Hello,
+# I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.
+# I'd love to connect and share some opportunities that align with your expertise.
+# Are you currently open to exploring new roles in this space?
+# If so, I can send you specific details about the positions we have available.
+# Thanks,
+# [Your Name]
Hi,
+# Thanks for your interest! Here are more details about the {job_description} opportunities:
+# Role Details:
+# • [Specific responsibilities]
+# • [Required skills and experience]
+# • [Team and company information]
+# Benefits:
+# • [Compensation and benefits]
+# • [Growth opportunities]
+# • [Work environment]
+# Would you be interested in a quick call to discuss this role in more detail?
+# Best regards,
+# [Your Name]
Hi,
+# Just wanted to follow up on the {job_description} opportunity I shared.
+# Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.
+# If you're interested, I can help schedule next steps. If not, no worries at all!
+# Thanks for your time!
+# [Your Name]
Hi there,
+# I came across your profile and noticed your experience in {job_description}.
+# I'm reaching out because we have some exciting opportunities that might be a great fit for your background.
+# Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?
+# Would you be interested in hearing more about the roles we have available?
+# Best regards,
+# [Your Name]
Hello,
+# I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.
+# I'd love to connect and share some opportunities that align with your expertise.
+# Are you currently open to exploring new roles in this space?
+# If so, I can send you specific details about the positions we have available.
+# Thanks,
+# [Your Name]
Hi,
+# Thanks for your interest! Here are more details about the {job_description} opportunities:
+# Role Details:
+# • [Specific responsibilities]
+# • [Required skills and experience]
+# • [Team and company information]
+# Benefits:
+# • [Compensation and benefits]
+# • [Growth opportunities]
+# • [Work environment]
+# Would you be interested in a quick call to discuss this role in more detail?
+# Best regards,
+# [Your Name]
Hi,
+# Just wanted to follow up on the {job_description} opportunity I shared.
+# Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.
+# If you're interested, I can help schedule next steps. If not, no worries at all!
+# Thanks for your time!
+# [Your Name]
Hi there,
+# I came across your profile and noticed your experience in {job_description}.
+# I'm reaching out because we have some exciting opportunities that might be a great fit for your background.
+# Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?
+# Would you be interested in hearing more about the roles we have available?
+# Best regards,
+# [Your Name]
Hello,
+# I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.
+# I'd love to connect and share some opportunities that align with your expertise.
+# Are you currently open to exploring new roles in this space?
+# If so, I can send you specific details about the positions we have available.
+# Thanks,
+# [Your Name]
Hi,
+# Thanks for your interest! Here are more details about the {job_description} opportunities:
+# Role Details:
+# • [Specific responsibilities]
+# • [Required skills and experience]
+# • [Team and company information]
+# Benefits:
+# • [Compensation and benefits]
+# • [Growth opportunities]
+# • [Work environment]
+# Would you be interested in a quick call to discuss this role in more detail?
+# Best regards,
+# [Your Name]
Hi,
+# Just wanted to follow up on the {job_description} opportunity I shared.
+# Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.
+# If you're interested, I can help schedule next steps. If not, no worries at all!
+# Thanks for your time!
+# [Your Name]
Hi there,
-I came across your profile and noticed your experience in {job_description}.
-I'm reaching out because we have some exciting opportunities that might be a great fit for your background.
-Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?
-Would you be interested in hearing more about the roles we have available?
-Best regards,
-[Your Name]
Hello,
-I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.
-I'd love to connect and share some opportunities that align with your expertise.
-Are you currently open to exploring new roles in this space?
-If so, I can send you specific details about the positions we have available.
-Thanks,
-[Your Name]
Hi,
-Thanks for your interest! Here are more details about the {job_description} opportunities:
-Role Details:
-• [Specific responsibilities]
-• [Required skills and experience]
-• [Team and company information]
-Benefits:
-• [Compensation and benefits]
-• [Growth opportunities]
-• [Work environment]
-Would you be interested in a quick call to discuss this role in more detail?
-Best regards,
-[Your Name]
Hi,
-Just wanted to follow up on the {job_description} opportunity I shared.
-Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.
-If you're interested, I can help schedule next steps. If not, no worries at all!
-Thanks for your time!
-[Your Name]
Hi there,
-I came across your profile and noticed your experience in {job_description}.
-I'm reaching out because we have some exciting opportunities that might be a great fit for your background.
-Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?
-Would you be interested in hearing more about the roles we have available?
-Best regards,
-[Your Name]
Hello,
-I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.
-I'd love to connect and share some opportunities that align with your expertise.
-Are you currently open to exploring new roles in this space?
-If so, I can send you specific details about the positions we have available.
-Thanks,
-[Your Name]
Hi,
-Thanks for your interest! Here are more details about the {job_description} opportunities:
-Role Details:
-• [Specific responsibilities]
-• [Required skills and experience]
-• [Team and company information]
-Benefits:
-• [Compensation and benefits]
-• [Growth opportunities]
-• [Work environment]
-Would you be interested in a quick call to discuss this role in more detail?
-Best regards,
-[Your Name]
Hi,
-Just wanted to follow up on the {job_description} opportunity I shared.
-Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.
-If you're interested, I can help schedule next steps. If not, no worries at all!
-Thanks for your time!
-[Your Name]
{{{{custom.Welcome_Message}}}}
+I'm reaching out because we have some exciting opportunities for a {job_description} that might be a great fit for your background. Are you currently open to exploring new roles?
+{{{{custom.Closing_Message}}}}
+Best regards,
Ali Taghikhani
CEO, SRN
Hi {{{{first_name}}}},
+Just wanted to follow up on my previous email regarding the {job_description} role. I'd love to hear your thoughts when you have a moment.
+Best regards,
Ali Taghikhani
CEO, SRN
Hi {{{{first_name}}}},
+Checking in one last time about the {job_description} opportunity. If the timing isn't right, no worries at all. Otherwise, I look forward to hearing from you.
+Best regards,
Ali Taghikhani
CEO, SRN
Hi there,
-I came across your profile and noticed your experience in {job_description}.
-I'm reaching out because we have some exciting opportunities that might be a great fit for your background.
-Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?
-Would you be interested in hearing more about the roles we have available?
-Best regards,
-[Your Name]
Hello,
-I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.
-I'd love to connect and share some opportunities that align with your expertise.
-Are you currently open to exploring new roles in this space?
-If so, I can send you specific details about the positions we have available.
-Thanks,
-[Your Name]
Hi,
-Thanks for your interest! Here are more details about the {job_description} opportunities:
-Role Details:
-• [Specific responsibilities]
-• [Required skills and experience]
-• [Team and company information]
-Benefits:
-• [Compensation and benefits]
-• [Growth opportunities]
-• [Work environment]
-Would you be interested in a quick call to discuss this role in more detail?
-Best regards,
-[Your Name]
Hi,
-Just wanted to follow up on the {job_description} opportunity I shared.
-Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.
-If you're interested, I can help schedule next steps. If not, no worries at all!
-Thanks for your time!
-[Your Name]