ak0601 commited on
Commit
6884b4f
·
verified ·
1 Parent(s): f39b364

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -1553
app.py CHANGED
@@ -1,1555 +1,3 @@
1
- # import os
2
- # import json
3
- # import time
4
- # from datetime import datetime
5
- # from typing import List, Dict, Any, Optional, Union
6
- # from pydantic import BaseModel, Field, EmailStr, field_validator
7
- # from fastapi import FastAPI, HTTPException, Query, Depends, Request
8
- # from fastapi.responses import JSONResponse, Response
9
- # from fastapi.middleware.cors import CORSMiddleware
10
- # from fastapi.openapi.utils import get_openapi
11
- # import httpx
12
- # from dotenv import load_dotenv
13
- # import pandas as pd
14
- # import psycopg2
15
- # from sqlalchemy import create_engine, inspect, text
16
-
17
- # # LangChain and OpenAI imports
18
- # try:
19
- # from langchain_openai import ChatOpenAI
20
- # from langchain.prompts import ChatPromptTemplate
21
- # LANGCHAIN_AVAILABLE = True
22
- # except ImportError:
23
- # LANGCHAIN_AVAILABLE = False
24
- # print("Warning: LangChain not available. Install with: pip install langchain langchain-openai")
25
-
26
- # load_dotenv(override=True)
27
-
28
- # # Configuration
29
- # SMARTLEAD_API_KEY = os.getenv("SMARTLEAD_API_KEY", "your-api-key-here")
30
- # SMARTLEAD_BASE_URL = "https://server.smartlead.ai/api/v1"
31
- # DB_PARAMS = {
32
- # 'dbname': os.getenv("DB_NAME"),
33
- # 'user': os.getenv("DB_USER"),
34
- # 'password': os.getenv("DB_PASSWORD"),
35
- # 'host': os.getenv("DB_HOST"),
36
- # 'port': os.getenv("DB_PORT")
37
- # }
38
- # DATABASE_URL = f"postgresql://{DB_PARAMS['user']}:{DB_PARAMS['password']}@{DB_PARAMS['host']}:{DB_PARAMS['port']}/{DB_PARAMS['dbname']}"
39
-
40
- # # Initialize FastAPI app
41
- # app = FastAPI(
42
- # title="Smartlead API - Complete Integration",
43
- # version="2.0.0",
44
- # description="Comprehensive FastAPI wrapper for Smartlead email automation platform",
45
- # docs_url="/docs",
46
- # redoc_url="/redoc"
47
- # )
48
-
49
- # # Add CORS middleware
50
- # app.add_middleware(
51
- # CORSMiddleware,
52
- # allow_origins=["*"],
53
- # allow_credentials=True,
54
- # allow_methods=["*"],
55
- # allow_headers=["*"],
56
- # )
57
-
58
- # # ============================================================================
59
- # # DATA MODELS
60
- # # ============================================================================
61
-
62
- # class CreateCampaignRequest(BaseModel):
63
- # name: str = Field(..., description="Campaign name")
64
- # client_id: Optional[int] = Field(None, description="Client ID (leave null if no client)")
65
-
66
- # class CampaignScheduleRequest(BaseModel):
67
- # timezone: str = Field(..., description="Timezone for the campaign schedule (e.g., 'America/Los_Angeles')")
68
- # 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]")
69
- # start_hour: str = Field(..., description="Start hour for sending emails in HH:MM format (e.g., '09:00')")
70
- # end_hour: str = Field(..., description="End hour for sending emails in HH:MM format (e.g., '18:00')")
71
- # min_time_btw_emails: int = Field(..., description="Minimum time in minutes between sending emails")
72
- # max_new_leads_per_day: int = Field(..., description="Maximum number of new leads to process per day")
73
- # schedule_start_time: str = Field(..., description="Schedule start time in ISO 8601 format (e.g., '2023-04-25T07:29:25.978Z')")
74
-
75
- # class CampaignSettingsRequest(BaseModel):
76
- # 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)")
77
- # stop_lead_settings: str = Field(..., description="Settings for stopping leads (allowed values: CLICK_ON_A_LINK, OPEN_AN_EMAIL)")
78
- # unsubscribe_text: str = Field(..., description="Text for the unsubscribe link")
79
- # send_as_plain_text: bool = Field(..., description="Whether emails should be sent as plain text")
80
- # follow_up_percentage: int = Field(ge=0, le=100, description="Follow-up percentage (max 100, min 0)")
81
- # client_id: Optional[int] = Field(None, description="Client ID (leave as null if not needed)")
82
- # enable_ai_esp_matching: bool = Field(False, description="Enable AI ESP matching (by default is false)")
83
-
84
- # class LeadInput(BaseModel):
85
- # first_name: Optional[str] = Field(None, description="Lead's first name")
86
- # last_name: Optional[str] = Field(None, description="Lead's last name")
87
- # email: str = Field(..., description="Lead's email address")
88
- # phone_number: Optional[Union[str, int]] = Field(None, description="Lead's phone number (can be string or integer)")
89
- # company_name: Optional[str] = Field(None, description="Lead's company name")
90
- # website: Optional[str] = Field(None, description="Lead's website")
91
- # location: Optional[str] = Field(None, description="Lead's location")
92
- # custom_fields: Optional[Dict[str, str]] = Field(None, description="Custom fields as key-value pairs (max 20 fields)")
93
- # linkedin_profile: Optional[str] = Field(None, description="Lead's LinkedIn profile URL")
94
- # company_url: Optional[str] = Field(None, description="Company website URL")
95
-
96
- # @field_validator('custom_fields')
97
- # @classmethod
98
- # def validate_custom_fields(cls, v):
99
- # if v is not None and len(v) > 20:
100
- # raise ValueError('Custom fields cannot exceed 20 fields')
101
- # return v
102
-
103
- # @field_validator('phone_number')
104
- # @classmethod
105
- # def validate_phone_number(cls, v):
106
- # if v is not None:
107
- # # Convert to string if it's an integer
108
- # return str(v)
109
- # return v
110
-
111
- # class LeadSettings(BaseModel):
112
- # ignore_global_block_list: bool = Field(True, description="Ignore leads if they are in the global block list")
113
- # ignore_unsubscribe_list: bool = Field(True, description="Ignore leads if they are in the unsubscribe list")
114
- # ignore_duplicate_leads_in_other_campaign: bool = Field(False, description="Allow leads to be added even if they are duplicates in other campaigns")
115
-
116
- # class AddLeadsRequest(BaseModel):
117
- # lead_list: List[LeadInput] = Field(..., max_items=100, description="List of leads to add (maximum 100 leads)")
118
- # settings: Optional[LeadSettings] = Field(None, description="Settings for lead processing")
119
-
120
- # class AddLeadsResponse(BaseModel):
121
- # ok: bool = Field(..., description="Indicates if the operation was successful")
122
- # upload_count: int = Field(..., description="Number of leads successfully uploaded")
123
- # total_leads: int = Field(..., description="Total number of leads attempted to upload")
124
- # already_added_to_campaign: int = Field(..., description="Number of leads already present in the campaign")
125
- # duplicate_count: int = Field(..., description="Number of duplicate emails found")
126
- # invalid_email_count: int = Field(..., description="Number of leads with invalid email format")
127
- # unsubscribed_leads: Any = Field(..., description="Number of leads that had previously unsubscribed (can be int or empty list)")
128
-
129
- # class SeqDelayDetails(BaseModel):
130
- # delay_in_days: int = Field(..., description="Delay in days before sending this sequence")
131
-
132
- # class SeqVariant(BaseModel):
133
- # subject: str = Field(..., description="Email subject line")
134
- # email_body: str = Field(..., description="Email body content (HTML format)")
135
- # variant_label: str = Field(..., description="Variant label (A, B, C, etc.)")
136
- # id: Optional[int] = Field(None, description="Variant ID (only for updating, not for creating)")
137
-
138
- # class CampaignSequence(BaseModel):
139
- # id: Optional[int] = Field(None, description="Sequence ID (only for updating, not for creating)")
140
- # seq_number: int = Field(..., description="Sequence number (1, 2, 3, etc.)")
141
- # seq_delay_details: SeqDelayDetails = Field(..., description="Delay details for this sequence")
142
- # seq_variants: Optional[List[SeqVariant]] = Field(None, description="Email variants for A/B testing")
143
- # subject: Optional[str] = Field("", description="Subject line (blank for follow-up in same thread)")
144
- # email_body: Optional[str] = Field(None, description="Email body content (HTML format)")
145
-
146
- # class SaveSequencesRequest(BaseModel):
147
- # sequences: List[CampaignSequence] = Field(..., description="List of campaign sequences")
148
-
149
- # class Job(BaseModel):
150
- # id: str
151
- # company_name: Optional[str] = None
152
- # job_title: Optional[str] = None
153
- # job_location: Optional[str] = None
154
- # company_blurb: Optional[str] = None
155
- # company_culture: Optional[str] = None
156
- # description: Optional[str] = None
157
- # company_size: Optional[str] = None
158
- # requirements: Optional[str] = None
159
- # salary: Optional[str] = None
160
-
161
- # class GenerateSequencesRequest(BaseModel):
162
- # job_id: str = Field(..., description="Job ID to fetch from database and generate sequences for")
163
-
164
- # class Campaign(BaseModel):
165
- # id: int
166
- # user_id: int
167
- # created_at: datetime
168
- # updated_at: datetime
169
- # status: str
170
- # name: str
171
- # track_settings: Union[str, List[Any]] # FIX: Accept string or list
172
- # scheduler_cron_value: Optional[Union[str, Dict[str, Any]]] = None # FIX: Accept string or dict
173
- # min_time_btwn_emails: int
174
- # max_leads_per_day: int
175
- # stop_lead_settings: str
176
- # unsubscribe_text: Optional[str] = None
177
- # client_id: Optional[int] = None
178
- # enable_ai_esp_matching: bool
179
- # send_as_plain_text: bool
180
- # follow_up_percentage: Optional[Union[str, int]] = None # FIX: Accept string or int
181
-
182
- # class CampaignListResponse(BaseModel):
183
- # campaigns: List[Campaign]
184
- # total: int
185
- # source: str
186
-
187
- # class Lead(BaseModel):
188
- # id: int
189
- # email: EmailStr
190
- # first_name: Optional[str] = None
191
- # last_name: Optional[str] = None
192
- # company: Optional[str] = None
193
- # position: Optional[str] = None
194
- # phone_number: Optional[str] = None
195
- # linkedin_url: Optional[str] = None
196
- # status: Optional[str] = None
197
-
198
- # class WarmupDetails(BaseModel):
199
- # status: str
200
- # total_sent_count: int
201
- # total_spam_count: int
202
- # warmup_reputation: Union[str, int]
203
- # warmup_key_id: Optional[str] = None
204
- # warmup_created_at: Optional[datetime] = None
205
- # reply_rate: Optional[int] = None
206
- # blocked_reason: Optional[str] = None
207
-
208
- # class EmailAccount(BaseModel):
209
- # id: int
210
- # created_at: datetime
211
- # updated_at: datetime
212
- # user_id: int
213
- # from_name: str
214
- # from_email: str
215
- # username: str
216
- # password: Optional[str] = None
217
- # smtp_host: Optional[str] = None
218
- # smtp_port: Optional[int] = None
219
- # smtp_port_type: Optional[str] = None
220
- # message_per_day: int
221
- # different_reply_to_address: Optional[str] = None
222
- # is_different_imap_account: bool
223
- # imap_username: Optional[str] = None
224
- # imap_password: Optional[str] = None
225
- # imap_host: Optional[str] = None
226
- # imap_port: Optional[int] = None
227
- # imap_port_type: Optional[str] = None
228
- # signature: Optional[str] = None
229
- # custom_tracking_domain: Optional[str] = None
230
- # bcc_email: Optional[str] = None
231
- # is_smtp_success: bool
232
- # is_imap_success: bool
233
- # smtp_failure_error: Optional[str] = None
234
- # imap_failure_error: Optional[str] = None
235
- # type: str
236
- # daily_sent_count: int
237
- # client_id: Optional[int] = None
238
- # campaign_count: Optional[int] = None
239
- # warmup_details: Optional[WarmupDetails] = None
240
-
241
- # class WarmupSettingsRequest(BaseModel):
242
- # warmup_enabled: bool
243
- # total_warmup_per_day: Optional[int] = None
244
- # daily_rampup: Optional[int] = None
245
- # reply_rate_percentage: Optional[int] = None
246
- # warmup_key_id: Optional[str] = Field(None, description="String value if passed will update the custom warmup-key identifier")
247
-
248
- # class LeadCategoryUpdateRequest(BaseModel):
249
- # category_id: int = Field(..., description="Category ID to assign to the lead")
250
- # pause_lead: bool = Field(False, description="Whether to pause the lead after category update")
251
-
252
- # class CampaignStatusUpdateRequest(BaseModel):
253
- # status: str = Field(..., description="New campaign status (PAUSED, STOPPED, START)")
254
-
255
- # class ResumeLeadRequest(BaseModel):
256
- # resume_lead_with_delay_days: Optional[int] = Field(None, description="Delay in days before resuming (defaults to 0)")
257
-
258
- # class DomainBlockListRequest(BaseModel):
259
- # domain_block_list: List[str] = Field(..., description="List of domains/emails to block")
260
- # client_id: Optional[int] = Field(None, description="Client ID if blocking is client-specific")
261
-
262
- # class WebhookRequest(BaseModel):
263
- # id: Optional[int] = Field(None, description="Webhook ID (null for creating new)")
264
- # name: str = Field(..., description="Webhook name")
265
- # webhook_url: str = Field(..., description="Webhook URL")
266
- # event_types: List[str] = Field(..., description="List of event types to listen for")
267
- # categories: Optional[List[str]] = Field(None, description="List of categories to filter by")
268
-
269
- # class WebhookDeleteRequest(BaseModel):
270
- # id: int = Field(..., description="Webhook ID to delete")
271
-
272
- # class ClientRequest(BaseModel):
273
- # name: str = Field(..., description="Client name")
274
- # email: str = Field(..., description="Client email")
275
- # permission: List[str] = Field(..., description="List of permissions")
276
- # logo: Optional[str] = Field(None, description="Client logo text")
277
- # logo_url: Optional[str] = Field(None, description="Client logo URL")
278
- # password: str = Field(..., description="Client password")
279
-
280
- # class MessageHistoryRequest(BaseModel):
281
- # email_stats_id: str = Field(..., description="Email stats ID for the specific email")
282
- # email_body: str = Field(..., description="Reply message email body")
283
- # reply_message_id: str = Field(..., description="Message ID to reply to")
284
- # reply_email_time: str = Field(..., description="Time of the message being replied to")
285
- # reply_email_body: str = Field(..., description="Body of the message being replied to")
286
- # cc: Optional[str] = Field(None, description="CC recipients")
287
- # bcc: Optional[str] = Field(None, description="BCC recipients")
288
- # add_signature: bool = Field(True, description="Whether to add signature")
289
-
290
- # # ============================================================================
291
- # # DATABASE HELPER FUNCTIONS
292
- # # ============================================================================
293
-
294
- # def get_database_connection():
295
- # """Get database connection"""
296
- # try:
297
- # conn_string = f"postgresql://{DB_PARAMS['user']}:{DB_PARAMS['password']}@{DB_PARAMS['host']}:{DB_PARAMS['port']}/{DB_PARAMS['dbname']}"
298
- # return create_engine(conn_string)
299
- # except Exception as e:
300
- # raise HTTPException(status_code=500, detail=f"Database connection failed: {str(e)}")
301
-
302
- # def fetch_job_by_id(job_id: str) -> Job:
303
- # """Fetch job details from database by ID using pandas DataFrame"""
304
- # try:
305
- # conn = get_database_connection()
306
- # df = pd.read_sql_table("jobs", con=conn)
307
-
308
- # # Filter the DataFrame to find the job with the specified ID
309
- # job_row = df[df['job_id'] == job_id]
310
-
311
- # if job_row.empty:
312
- # raise HTTPException(status_code=404, detail=f"Job with ID {job_id} not found")
313
-
314
- # # Get the first (and should be only) row
315
- # return Job(
316
- # id=str(job_row.iloc[0]['job_id']),
317
- # company_name=str(job_row.iloc[0]['company_name']) if pd.notna(job_row.iloc[0]['company_name']) else None,
318
- # job_title=str(job_row.iloc[0]['job_title']) if pd.notna(job_row.iloc[0]['job_title']) else None,
319
- # job_location=str(job_row.iloc[0]['job_location']) if pd.notna(job_row.iloc[0]['job_location']) else None,
320
- # company_blurb=str(job_row.iloc[0]['company_blurb']) if pd.notna(job_row.iloc[0]['company_blurb']) else None,
321
- # company_culture=str(job_row.iloc[0]['company_culture']) if pd.notna(job_row.iloc[0]['company_culture']) else None,
322
- # description=str(job_row.iloc[0]['description']) if pd.notna(job_row.iloc[0]['description']) else None,
323
- # company_size=str(job_row.iloc[0]['company_size']) if pd.notna(job_row.iloc[0]['company_size']) else None,
324
- # requirements=str(job_row.iloc[0]['requirements']) if pd.notna(job_row.iloc[0]['requirements']) else None,
325
- # salary=str(job_row.iloc[0]['salary']) if pd.notna(job_row.iloc[0]['salary']) else None
326
- # )
327
-
328
- # except HTTPException:
329
- # raise
330
- # except Exception as e:
331
- # raise HTTPException(status_code=500, detail=f"Error fetching job: {str(e)}")
332
-
333
- # def list_available_jobs(limit: int = 10) -> List[Dict[str, Any]]:
334
- # """List available jobs from database using pandas DataFrame"""
335
- # try:
336
- # conn = get_database_connection()
337
- # df = pd.read_sql_table("jobs", con=conn)
338
-
339
- # # Select only the required columns and limit the results
340
- # selected_columns = ['job_id', 'company_name', 'job_title', 'job_location', 'company_size', 'salary']
341
- # df_subset = df[selected_columns].head(limit)
342
-
343
- # # Convert DataFrame to list of dictionaries
344
- # jobs_list = []
345
- # for _, row in df_subset.iterrows():
346
- # jobs_list.append({
347
- # "id": str(row['job_id']), # Ensure ID is returned as string
348
- # "company_name": str(row['company_name']) if pd.notna(row['company_name']) else None,
349
- # "job_title": str(row['job_title']) if pd.notna(row['job_title']) else None,
350
- # "job_location": str(row['job_location']) if pd.notna(row['job_location']) else None,
351
- # "company_size": str(row['company_size']) if pd.notna(row['company_size']) else None,
352
- # "salary": str(row['salary']) if pd.notna(row['salary']) else None
353
- # })
354
-
355
- # return jobs_list
356
-
357
- # except Exception as e:
358
- # raise HTTPException(status_code=500, detail=f"Error fetching jobs: {str(e)}")
359
-
360
- # def build_job_description(job: Job) -> str:
361
- # """Build a comprehensive job description from job details"""
362
- # parts = []
363
-
364
- # if job.company_name:
365
- # parts.append(f"Company: {job.company_name}")
366
-
367
- # if job.job_title:
368
- # parts.append(f"Position: {job.job_title}")
369
-
370
- # if job.job_location:
371
- # parts.append(f"Location: {job.job_location}")
372
-
373
- # if job.company_size:
374
- # parts.append(f"Company Size: {job.company_size}")
375
-
376
- # if job.salary:
377
- # parts.append(f"Salary: {job.salary}")
378
-
379
- # if job.company_blurb:
380
- # parts.append(f"About the Company: {job.company_blurb}")
381
-
382
- # if job.company_culture:
383
- # parts.append(f"Company Culture: {job.company_culture}")
384
-
385
- # if job.description:
386
- # parts.append(f"Job Description: {job.description}")
387
-
388
- # if job.requirements:
389
- # parts.append(f"Requirements: {job.requirements}")
390
-
391
- # return "\n\n".join(parts)
392
-
393
- # # ============================================================================
394
- # # HELPER FUNCTIONS
395
- # # ============================================================================
396
-
397
- # def _get_smartlead_url(endpoint: str) -> str:
398
- # return f"{SMARTLEAD_BASE_URL}/{endpoint.lstrip('/')}"
399
-
400
- # async def call_smartlead_api(method: str, endpoint: str, data: Any = None, params: Dict[str, Any] = None) -> Any:
401
- # if SMARTLEAD_API_KEY == "your-api-key-here":
402
- # raise HTTPException(status_code=400, detail="Smartlead API key not configured")
403
- # if params is None:
404
- # params = {}
405
- # params['api_key'] = SMARTLEAD_API_KEY
406
- # url = _get_smartlead_url(endpoint)
407
-
408
- # try:
409
- # async with httpx.AsyncClient(timeout=30.0) as client:
410
- # request_kwargs = {"params": params}
411
- # if data is not None:
412
- # request_kwargs["json"] = data
413
-
414
- # resp = await client.request(method, url, **request_kwargs)
415
-
416
- # if resp.status_code >= 400:
417
- # try:
418
- # error_data = resp.json()
419
- # error_message = error_data.get('message', error_data.get('error', 'Unknown error'))
420
- # raise HTTPException(status_code=resp.status_code, detail=error_message)
421
- # except (ValueError, KeyError):
422
- # raise HTTPException(status_code=resp.status_code, detail=resp.text)
423
-
424
- # return resp.json()
425
-
426
- # except httpx.TimeoutException:
427
- # raise HTTPException(status_code=408, detail="Request to Smartlead API timed out")
428
- # except httpx.RequestError as e:
429
- # raise HTTPException(status_code=503, detail=f"Failed to connect to Smartlead API: {str(e)}")
430
-
431
- # # ============================================================================
432
- # # CAMPAIGN ENDPOINTS
433
- # # ============================================================================
434
-
435
- # @app.post("/campaigns/create", response_model=Dict[str, Any], tags=["Campaigns"])
436
- # async def create_campaign(campaign: CreateCampaignRequest):
437
- # """Create a new campaign in Smartlead"""
438
- # return await call_smartlead_api("POST", "campaigns/create", data=campaign.dict())
439
-
440
- # @app.get("/campaigns", response_model=CampaignListResponse, tags=["Campaigns"])
441
- # async def list_campaigns():
442
- # """Fetch all campaigns from Smartlead API"""
443
- # campaigns = await call_smartlead_api("GET", "campaigns")
444
- # return {"campaigns": campaigns, "total": len(campaigns), "source": "smartlead"}
445
-
446
- # @app.get("/campaigns/{campaign_id}", response_model=Campaign, tags=["Campaigns"])
447
- # async def get_campaign(campaign_id: int):
448
- # """Get Campaign By Id"""
449
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}")
450
-
451
- # @app.post("/campaigns/{campaign_id}/settings", response_model=Dict[str, Any], tags=["Campaigns"])
452
- # async def update_campaign_settings(campaign_id: int, settings: CampaignSettingsRequest):
453
- # """Update Campaign General Settings"""
454
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/settings", data=settings.dict())
455
-
456
- # @app.post("/campaigns/{campaign_id}/schedule", response_model=Dict[str, Any], tags=["Campaigns"])
457
- # async def schedule_campaign(campaign_id: int, schedule: CampaignScheduleRequest):
458
- # """Update Campaign Schedule"""
459
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/schedule", data=schedule.dict())
460
-
461
- # @app.delete("/campaigns/{campaign_id}", response_model=Dict[str, Any], tags=["Campaigns"])
462
- # async def delete_campaign(campaign_id: int):
463
- # """Delete Campaign"""
464
- # return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}")
465
-
466
- # @app.post("/campaigns/{campaign_id}/status", response_model=Dict[str, Any], tags=["Campaigns"])
467
- # async def patch_campaign_status(campaign_id: int, request: CampaignStatusUpdateRequest):
468
- # """Patch campaign status"""
469
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/status", data=request.dict())
470
-
471
- # @app.get("/campaigns/{campaign_id}/analytics", response_model=Any, tags=["Analytics"])
472
- # async def campaign_analytics(campaign_id: int):
473
- # """Fetch analytics for a campaign"""
474
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/analytics")
475
-
476
- # @app.get("/campaigns/{campaign_id}/statistics", response_model=Dict[str, Any], tags=["Analytics"])
477
- # async def fetch_campaign_statistics_by_campaign_id(
478
- # campaign_id: int,
479
- # offset: int = 0,
480
- # limit: int = 100,
481
- # email_sequence_number: Optional[int] = None,
482
- # email_status: Optional[str] = None
483
- # ):
484
- # """Fetch Campaign Statistics By Campaign Id"""
485
- # params = {"offset": offset, "limit": limit}
486
- # if email_sequence_number:
487
- # params["email_sequence_number"] = email_sequence_number
488
- # if email_status:
489
- # params["email_status"] = email_status
490
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/statistics", params=params)
491
-
492
- # @app.get("/campaigns/{campaign_id}/analytics-by-date", response_model=Dict[str, Any], tags=["Analytics"])
493
- # async def fetch_campaign_statistics_by_date_range(
494
- # campaign_id: int,
495
- # start_date: str,
496
- # end_date: str
497
- # ):
498
- # """Fetch Campaign Statistics By Campaign Id And Date Range"""
499
- # params = {"start_date": start_date, "end_date": end_date}
500
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/analytics-by-date", params=params)
501
-
502
- # # ============================================================================
503
- # # LEAD MANAGEMENT ENDPOINTS
504
- # # ============================================================================
505
-
506
- # @app.get("/campaigns/{campaign_id}/leads", response_model=Dict[str, Any], tags=["Leads"])
507
- # async def get_campaign_leads(campaign_id: int, offset: int = 0, limit: int = 100):
508
- # """List all leads by campaign id"""
509
- # params = {"offset": offset, "limit": limit}
510
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/leads", params=params)
511
-
512
- # @app.post("/campaigns/{campaign_id}/leads", response_model=Dict[str, Any], tags=["Leads"])
513
- # async def add_leads_to_campaign(campaign_id: int, request: AddLeadsRequest):
514
- # """Add leads to a campaign by ID with personalized welcome and closing messages"""
515
- # request_data = request.dict()
516
-
517
- # # Process each lead to generate personalized messages and clean up data
518
- # for lead in request_data.get("lead_list", []):
519
- # lead_cleaned = {k: v for k, v in lead.items() if v is not None and v != ""}
520
-
521
- # # Generate personalized welcome and closing messages using LLM
522
- # try:
523
- # personalized_messages = await generate_welcome_closing_messages(lead_cleaned)
524
-
525
- # # Initialize custom_fields if it doesn't exist
526
- # if "custom_fields" not in lead_cleaned:
527
- # lead_cleaned["custom_fields"] = {}
528
-
529
- # # Add the generated messages to custom_fields
530
- # if personalized_messages.get("welcome_message"):
531
- # lead_cleaned["custom_fields"]["Welcome_Message"] = personalized_messages["welcome_message"]
532
- # if personalized_messages.get("closing_message"):
533
- # lead_cleaned["custom_fields"]["Closing_Message"] = personalized_messages["closing_message"]
534
-
535
- # except Exception as e:
536
- # print(f"Error generating personalized messages for lead {lead_cleaned.get('email', 'unknown')}: {str(e)}")
537
- # # Continue with template messages if LLM fails
538
- # template_messages = generate_template_welcome_closing_messages(lead_cleaned)
539
- # if "custom_fields" not in lead_cleaned:
540
- # lead_cleaned["custom_fields"] = {}
541
- # if template_messages.get("welcome_message"):
542
- # lead_cleaned["custom_fields"]["Welcome_Message"] = template_messages["welcome_message"]
543
- # if template_messages.get("closing_message"):
544
- # lead_cleaned["custom_fields"]["Closing_Message"] = template_messages["closing_message"]
545
-
546
- # # Clean up custom_fields - remove None values and empty strings
547
- # if "custom_fields" in lead_cleaned:
548
- # custom_fields = lead_cleaned["custom_fields"]
549
- # if custom_fields:
550
- # custom_fields_cleaned = {k: v for k, v in custom_fields.items() if v is not None and v != ""}
551
- # if custom_fields_cleaned:
552
- # lead_cleaned["custom_fields"] = custom_fields_cleaned
553
- # else:
554
- # lead_cleaned.pop("custom_fields", None)
555
- # else:
556
- # lead_cleaned.pop("custom_fields", None)
557
-
558
- # lead.clear()
559
- # lead.update(lead_cleaned)
560
-
561
- # request_data["lead_list"] = [lead for lead in request_data["lead_list"] if lead]
562
-
563
- # if not request_data["lead_list"]:
564
- # raise HTTPException(status_code=400, detail="No valid leads to add.")
565
-
566
- # if "settings" not in request_data or request_data["settings"] is None:
567
- # request_data["settings"] = LeadSettings().dict()
568
-
569
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads", data=request_data)
570
-
571
- # @app.post("/campaigns/{campaign_id}/leads/bulk", response_model=Dict[str, Any], tags=["Leads"])
572
- # async def add_bulk_leads(campaign_id: int, leads: List[LeadInput]):
573
- # """Add multiple leads to a Smartlead campaign with personalized messages (legacy endpoint)"""
574
- # request = AddLeadsRequest(lead_list=leads)
575
- # return await add_leads_to_campaign(campaign_id, request)
576
-
577
- # @app.post("/campaigns/{campaign_id}/leads/{lead_id}/resume", response_model=Dict[str, Any], tags=["Leads"])
578
- # async def resume_lead_by_campaign_id(campaign_id: int, lead_id: int, request: ResumeLeadRequest):
579
- # """Resume Lead By Campaign ID"""
580
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/resume", data=request.dict())
581
-
582
- # @app.post("/campaigns/{campaign_id}/leads/{lead_id}/pause", response_model=Dict[str, Any], tags=["Leads"])
583
- # async def pause_lead_by_campaign_id(campaign_id: int, lead_id: int):
584
- # """Pause Lead By Campaign ID"""
585
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/pause")
586
-
587
- # @app.delete("/campaigns/{campaign_id}/leads/{lead_id}", response_model=Dict[str, Any], tags=["Leads"])
588
- # async def delete_lead_by_campaign_id(campaign_id: int, lead_id: int):
589
- # """Delete Lead By Campaign ID"""
590
- # return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}/leads/{lead_id}")
591
-
592
- # @app.post("/campaigns/{campaign_id}/leads/{lead_id}/unsubscribe", response_model=Dict[str, Any], tags=["Leads"])
593
- # async def unsubscribe_lead_from_campaign(campaign_id: int, lead_id: int):
594
- # """Unsubscribe/Pause Lead From Campaign"""
595
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/unsubscribe")
596
-
597
- # @app.post("/leads/{lead_id}/unsubscribe", response_model=Dict[str, Any], tags=["Leads"])
598
- # async def unsubscribe_lead_from_all_campaigns(lead_id: int):
599
- # """Unsubscribe Lead From All Campaigns"""
600
- # return await call_smartlead_api("POST", f"leads/{lead_id}/unsubscribe")
601
-
602
- # @app.post("/leads/{lead_id}", response_model=Dict[str, Any], tags=["Leads"])
603
- # async def update_lead(lead_id: int, lead_data: Dict[str, Any]):
604
- # """Update lead using the Lead ID"""
605
- # return await call_smartlead_api("POST", f"leads/{lead_id}", data=lead_data)
606
-
607
- # @app.post("/campaigns/{campaign_id}/leads/{lead_id}/category", response_model=Dict[str, Any], tags=["Leads"])
608
- # async def update_lead_category_by_campaign(campaign_id: int, lead_id: int, request: LeadCategoryUpdateRequest):
609
- # """Update a lead's category based on their campaign"""
610
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/leads/{lead_id}/category", data=request.dict())
611
-
612
- # @app.post("/leads/add-domain-block-list", response_model=Dict[str, Any], tags=["Leads"])
613
- # async def add_domain_to_global_block_list(request: DomainBlockListRequest):
614
- # """Add Lead/Domain to Global Block List"""
615
- # return await call_smartlead_api("POST", "leads/add-domain-block-list", data=request.dict())
616
-
617
- # @app.get("/leads/fetch-categories", response_model=List[Dict[str, Any]], tags=["Leads"])
618
- # async def fetch_lead_categories():
619
- # """Fetch lead categories"""
620
- # return await call_smartlead_api("GET", "leads/fetch-categories")
621
-
622
- # @app.get("/leads", response_model=Dict[str, Any], tags=["Leads"])
623
- # async def fetch_lead_by_email_address(email: str):
624
- # """Fetch lead by email address"""
625
- # return await call_smartlead_api("GET", "leads", params={"email": email})
626
-
627
- # @app.get("/leads/{lead_id}/campaigns", response_model=List[Dict[str, Any]], tags=["Leads"])
628
- # async def campaigns_for_lead(lead_id: int):
629
- # """Fetch all campaigns that a lead belongs to"""
630
- # return await call_smartlead_api("GET", f"leads/{lead_id}/campaigns")
631
-
632
- # @app.get("/campaigns/{campaign_id}/leads/check", response_model=Dict[str, Any], tags=["Leads"])
633
- # async def check_lead_in_campaign(campaign_id: int, email: str):
634
- # """Check if a lead exists in a campaign using efficient indexed lookups"""
635
- # try:
636
- # lead_response = await call_smartlead_api("GET", "leads", params={"email": email})
637
-
638
- # if not lead_response or "id" not in lead_response:
639
- # return {"exists": False, "message": "Lead not found"}
640
-
641
- # lead_id = lead_response["id"]
642
- # campaigns_response = await call_smartlead_api("GET", f"leads/{lead_id}/campaigns")
643
-
644
- # if not campaigns_response:
645
- # return {"exists": False, "message": "No campaigns found for lead"}
646
-
647
- # campaign_exists = any(campaign.get("id") == campaign_id for campaign in campaigns_response)
648
-
649
- # return {"exists": campaign_exists, "message": "Lead found in campaign" if campaign_exists else "Lead not found in campaign"}
650
-
651
- # except HTTPException as e:
652
- # if e.status_code == 404:
653
- # return {"exists": False, "message": "Lead not found"}
654
- # raise e
655
- # except Exception as e:
656
- # raise HTTPException(status_code=500, detail=f"Error checking lead in campaign: {str(e)}")
657
-
658
- # @app.get("/campaigns/{campaign_id}/leads-export", tags=["Leads"])
659
- # async def export_data_from_campaign(campaign_id: int):
660
- # """Export data from a campaign as CSV"""
661
- # if SMARTLEAD_API_KEY == "your-api-key-here":
662
- # raise HTTPException(status_code=400, detail="Smartlead API key not configured")
663
-
664
- # url = _get_smartlead_url(f"campaigns/{campaign_id}/leads-export")
665
- # params = {"api_key": SMARTLEAD_API_KEY}
666
-
667
- # try:
668
- # async with httpx.AsyncClient(timeout=30.0) as client:
669
- # resp = await client.get(url, params=params)
670
-
671
- # if resp.status_code >= 400:
672
- # try:
673
- # error_data = resp.json()
674
- # error_message = error_data.get('message', error_data.get('error', 'Unknown error'))
675
- # raise HTTPException(status_code=resp.status_code, detail=error_message)
676
- # except (ValueError, KeyError):
677
- # raise HTTPException(status_code=resp.status_code, detail=resp.text)
678
-
679
- # return Response(
680
- # content=resp.text,
681
- # media_type="text/csv",
682
- # headers={"Content-Disposition": f"attachment; filename=campaign_{campaign_id}_leads.csv"}
683
- # )
684
-
685
- # except httpx.TimeoutException:
686
- # raise HTTPException(status_code=408, detail="Request to Smartlead API timed out")
687
- # except httpx.RequestError as e:
688
- # raise HTTPException(status_code=503, detail=f"Failed to connect to Smartlead API: {str(e)}")
689
-
690
- # # ============================================================================
691
- # # SEQUENCE ENDPOINTS
692
- # # ============================================================================
693
-
694
- # @app.get("/campaigns/{campaign_id}/sequences", response_model=Any, tags=["Sequences"])
695
- # async def get_campaign_sequences(campaign_id: int):
696
- # """Fetch email sequences for a campaign"""
697
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/sequences")
698
-
699
- # @app.post("/campaigns/{campaign_id}/sequences", response_model=Dict[str, Any], tags=["Sequences"])
700
- # async def save_campaign_sequences(campaign_id: int, request: SaveSequencesRequest):
701
- # """Save Campaign Sequence"""
702
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/sequences", data=request.dict())
703
-
704
- # @app.post("/campaigns/{campaign_id}/sequences/generate", response_model=Dict[str, Any], tags=["Sequences"])
705
- # async def generate_campaign_sequences(campaign_id: int, request: GenerateSequencesRequest):
706
- # """Generate Campaign Sequences using LLM"""
707
- # # Fetch job details from database using job_id
708
- # job = fetch_job_by_id(request.job_id)
709
- # job_description = build_job_description(job)
710
-
711
- # generated_sequences = await generate_sequences_with_llm(job_description)
712
- # save_request = SaveSequencesRequest(sequences=generated_sequences)
713
- # result = await call_smartlead_api("POST", f"campaigns/{campaign_id}/sequences", data=save_request.dict())
714
-
715
- # return {
716
- # "ok": True,
717
- # "message": "Sequences generated and saved successfully",
718
- # "job_details": {
719
- # "id": job.id,
720
- # "company_name": job.company_name,
721
- # "job_title": job.job_title,
722
- # "job_location": job.job_location
723
- # },
724
- # "generated_sequences": [seq for seq in generated_sequences],
725
- # "save_result": result
726
- # }
727
-
728
- # # ============================================================================
729
- # # WEBHOOK ENDPOINTS
730
- # # ============================================================================
731
-
732
- # @app.get("/campaigns/{campaign_id}/webhooks", response_model=List[Dict[str, Any]], tags=["Webhooks"])
733
- # async def fetch_webhooks_by_campaign_id(campaign_id: int):
734
- # """Fetch Webhooks By Campaign ID"""
735
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/webhooks")
736
-
737
- # @app.post("/campaigns/{campaign_id}/webhooks", response_model=Dict[str, Any], tags=["Webhooks"])
738
- # async def add_update_campaign_webhook(campaign_id: int, request: WebhookRequest):
739
- # """Add / Update Campaign Webhook"""
740
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/webhooks", data=request.dict())
741
-
742
- # @app.delete("/campaigns/{campaign_id}/webhooks", response_model=Dict[str, Any], tags=["Webhooks"])
743
- # async def delete_campaign_webhook(campaign_id: int, request: WebhookDeleteRequest):
744
- # """Delete Campaign Webhook"""
745
- # return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}/webhooks", data=request.dict())
746
-
747
- # # ============================================================================
748
- # # CLIENT MANAGEMENT ENDPOINTS
749
- # # ============================================================================
750
-
751
- # @app.post("/client/save", response_model=Dict[str, Any], tags=["Clients"])
752
- # async def add_client_to_system(request: ClientRequest):
753
- # """Add Client To System (Whitelabel or not)"""
754
- # return await call_smartlead_api("POST", "client/save", data=request.dict())
755
-
756
- # @app.get("/client", response_model=List[Dict[str, Any]], tags=["Clients"])
757
- # async def fetch_all_clients():
758
- # """Fetch all clients"""
759
- # return await call_smartlead_api("GET", "client")
760
-
761
- # # ============================================================================
762
- # # MESSAGE HISTORY AND REPLY ENDPOINTS
763
- # # ============================================================================
764
-
765
- # @app.get("/campaigns/{campaign_id}/leads/{lead_id}/message-history", response_model=Dict[str, Any], tags=["Messages"])
766
- # async def fetch_lead_message_history_based_on_campaign(campaign_id: int, lead_id: int):
767
- # """Fetch Lead Message History Based On Campaign"""
768
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/leads/{lead_id}/message-history")
769
-
770
- # @app.post("/campaigns/{campaign_id}/reply-email-thread", response_model=Dict[str, Any], tags=["Messages"])
771
- # async def reply_to_lead_from_master_inbox(campaign_id: int, request: MessageHistoryRequest):
772
- # """Reply To Lead From Master Inbox via API"""
773
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/reply-email-thread", data=request.dict())
774
-
775
- # # ============================================================================
776
- # # EMAIL ACCOUNT ENDPOINTS
777
- # # ============================================================================
778
-
779
- # @app.get("/email-accounts", response_model=List[EmailAccount], tags=["Email Accounts"])
780
- # async def list_email_accounts(offset: int = 0, limit: int = 100):
781
- # """List all email accounts with optional pagination"""
782
- # params = {"offset": offset, "limit": limit}
783
- # return await call_smartlead_api("GET", "email-accounts", params=params)
784
-
785
- # @app.post("/email-accounts/save", response_model=Any, tags=["Email Accounts"])
786
- # async def save_email_account(account: Dict[str, Any]):
787
- # """Create an Email Account"""
788
- # return await call_smartlead_api("POST", "email-accounts/save", data=account)
789
-
790
- # @app.post("/email-accounts/reconnect-failed-email-accounts", response_model=Dict[str, Any], tags=["Email Accounts"])
791
- # async def reconnect_failed_email_accounts(body: Optional[Dict] = {}):
792
- # """Reconnect failed email accounts"""
793
- # return await call_smartlead_api("POST", "email-accounts/reconnect-failed-email-accounts", data={})
794
-
795
- # @app.get("/email-accounts/{account_id}", response_model=EmailAccount, tags=["Email Accounts"])
796
- # async def get_email_account(account_id: int):
797
- # """Fetch Email Account By ID"""
798
- # return await call_smartlead_api("GET", f"email-accounts/{account_id}")
799
-
800
- # @app.post("/email-accounts/{account_id}", response_model=Any, tags=["Email Accounts"])
801
- # async def update_email_account(account_id: int, payload: Dict[str, Any]):
802
- # """Update Email Account"""
803
- # return await call_smartlead_api("POST", f"email-accounts/{account_id}", data=payload)
804
-
805
- # @app.post("/email-accounts/{account_id}/warmup", response_model=Any, tags=["Email Accounts"])
806
- # async def set_warmup(account_id: int, payload: WarmupSettingsRequest):
807
- # """Add/Update Warmup To Email Account"""
808
- # return await call_smartlead_api("POST", f"email-accounts/{account_id}/warmup", data=payload.dict(exclude_none=True))
809
-
810
- # @app.get("/email-accounts/{account_id}/warmup-stats", response_model=Any, tags=["Email Accounts"])
811
- # async def get_warmup_stats(account_id: int):
812
- # """Fetch Warmup Stats By Email Account ID"""
813
- # return await call_smartlead_api("GET", f"email-accounts/{account_id}/warmup-stats")
814
-
815
- # @app.get("/campaigns/{campaign_id}/email-accounts", response_model=List[EmailAccount], tags=["Email Accounts"])
816
- # async def list_campaign_email_accounts(campaign_id: int):
817
- # """List all email accounts per campaign"""
818
- # return await call_smartlead_api("GET", f"campaigns/{campaign_id}/email-accounts")
819
-
820
- # @app.post("/campaigns/{campaign_id}/email-accounts", response_model=Any, tags=["Email Accounts"])
821
- # async def add_campaign_email_accounts(campaign_id: int, payload: Dict[str, Any]):
822
- # """Add Email Account To A Campaign"""
823
- # return await call_smartlead_api("POST", f"campaigns/{campaign_id}/email-accounts", data=payload)
824
-
825
- # @app.delete("/campaigns/{campaign_id}/email-accounts", response_model=Any, tags=["Email Accounts"])
826
- # async def remove_campaign_email_accounts(campaign_id: int, payload: Dict[str, Any]):
827
- # """Remove Email Account From A Campaign"""
828
- # return await call_smartlead_api("DELETE", f"campaigns/{campaign_id}/email-accounts", data=payload)
829
-
830
- # # ============================================================================
831
- # # UTILITY ENDPOINTS
832
- # # ============================================================================
833
-
834
- # @app.get("/health", response_model=Dict[str, Any], tags=["Utilities"])
835
- # async def health_check():
836
- # """Health check endpoint to verify API connectivity"""
837
- # try:
838
- # campaigns = await call_smartlead_api("GET", "campaigns")
839
- # return {
840
- # "status": "healthy",
841
- # "message": "Smartlead API is accessible",
842
- # "campaigns_count": len(campaigns) if isinstance(campaigns, list) else 0,
843
- # "timestamp": datetime.now().isoformat()
844
- # }
845
- # except Exception as e:
846
- # return {
847
- # "status": "unhealthy",
848
- # "message": f"Smartlead API connection failed: {str(e)}",
849
- # "timestamp": datetime.now().isoformat()
850
- # }
851
-
852
- # @app.get("/api-info", response_model=Dict[str, Any], tags=["Utilities"])
853
- # async def api_info():
854
- # """Get information about the API and available endpoints"""
855
- # return {
856
- # "name": "Smartlead API - Complete Integration",
857
- # "version": "2.0.0",
858
- # "description": "Comprehensive FastAPI wrapper for Smartlead email automation platform",
859
- # "base_url": SMARTLEAD_BASE_URL,
860
- # "available_endpoints": [
861
- # "Campaign Management",
862
- # "Lead Management",
863
- # "Sequence Management",
864
- # "Webhook Management",
865
- # "Client Management",
866
- # "Message History & Reply",
867
- # "Analytics",
868
- # "Email Account Management",
869
- # "Job Management"
870
- # ],
871
- # "documentation": "Based on Smartlead API documentation",
872
- # "timestamp": datetime.now().isoformat()
873
- # }
874
-
875
- # # ============================================================================
876
- # # JOB MANAGEMENT ENDPOINTS
877
- # # ============================================================================
878
-
879
- # @app.get("/jobs", response_model=List[Dict[str, Any]], tags=["Jobs"])
880
- # async def get_available_jobs(limit: int = Query(10, ge=1, le=100, description="Number of jobs to return")):
881
- # """Get list of available jobs from database"""
882
- # return list_available_jobs(limit)
883
-
884
- # @app.get("/jobs/{job_id}", response_model=Job, tags=["Jobs"])
885
- # async def get_job_by_id(job_id: str):
886
- # """Get job details by ID from database"""
887
- # return fetch_job_by_id(job_id)
888
-
889
- # # ============================================================================
890
- # # AI SEQUENCE GENERATION FUNCTIONS
891
- # # ============================================================================
892
-
893
- # async def generate_welcome_closing_messages(lead_data: Dict[str, Any]) -> Dict[str, str]:
894
- # class structure(BaseModel):
895
- # welcome_message: str = Field(description="Welcome message for the candidate")
896
- # closing_message: str = Field(description="Closing message for the candidate")
897
-
898
- # """Generate personalized welcome and closing messages using LLM based on candidate details"""
899
-
900
- # if not LANGCHAIN_AVAILABLE:
901
- # return generate_template_welcome_closing_messages(lead_data)
902
-
903
- # try:
904
- # openai_api_key = os.getenv("OPENAI_API_KEY")
905
- # if not openai_api_key:
906
- # print("Warning: OPENAI_API_KEY not set. Using template messages.")
907
- # return generate_template_welcome_closing_messages(lead_data)
908
-
909
- # llm = ChatOpenAI(
910
- # model="gpt-4o-mini",
911
- # temperature=0.7,
912
- # openai_api_key=openai_api_key
913
- # )
914
- # str_llm = llm.with_structured_output(structure, method="function_calling")
915
-
916
- # # Extract relevant information from lead data
917
- # first_name = lead_data.get("first_name", "")
918
- # last_name = lead_data.get("last_name", "")
919
- # company_name = lead_data.get("company_name", "")
920
- # location = lead_data.get("location", "")
921
- # title = lead_data.get("custom_fields", {}).get("Title", "")
922
- # linkedin_profile = lead_data.get("linkedin_profile", "")
923
-
924
- # # Create a summary of the candidate's background
925
- # candidate_info = f"""
926
- # Name: {first_name}
927
- # Company: {company_name}
928
- # Location: {location}
929
- # Title: {title}
930
- # LinkedIn: {linkedin_profile}
931
- # """
932
-
933
- # system_prompt = """You are an expert recruiter who creates personalized welcome and closing messages for email campaigns.
934
-
935
- # Based on the candidate's information, generate:
936
- # 1. A personalized welcome message (2-3 sentences) starting with "Hi first_name then change the line using <br> tag and continue with a friendly introduction about their background/company/role.
937
- # 2. A personalized closing message (1-2 sentences)
938
-
939
- # Requirements:
940
- # - Professional but friendly tone
941
- # - Reference their specific background/company/role when possible
942
- # - Keep messages concise and engaging
943
- # - Make them feel valued and understood
944
-
945
- # IMPORTANT: Respond with ONLY valid JSON. No additional text."""
946
-
947
- # prompt_template = ChatPromptTemplate.from_messages([
948
- # ("system", system_prompt),
949
- # ("human", "Generate personalized messages for this candidate: {candidate_info}")
950
- # ])
951
-
952
- # messages = prompt_template.format_messages(candidate_info=candidate_info)
953
- # response = await str_llm.ainvoke(messages)
954
-
955
- # try:
956
-
957
- # return {
958
- # "welcome_message": response.welcome_message,
959
- # "closing_message": response.closing_message
960
- # }
961
-
962
- # except Exception as parse_error:
963
- # print(f"JSON parsing failed for welcome/closing messages: {parse_error}")
964
- # return generate_template_welcome_closing_messages(lead_data)
965
-
966
- # except Exception as e:
967
- # print(f"Error generating welcome/closing messages with LLM: {str(e)}")
968
- # return generate_template_welcome_closing_messages(lead_data)
969
-
970
- # def generate_template_welcome_closing_messages(lead_data: Dict[str, Any]) -> Dict[str, str]:
971
- # """Generate template-based welcome and closing messages as fallback"""
972
-
973
- # first_name = lead_data.get("first_name", "")
974
- # company_name = lead_data.get("company_name", "")
975
- # title = lead_data.get("custom_fields", {}).get("Title", "")
976
-
977
- # # Personalized welcome message
978
- # if first_name and company_name:
979
- # welcome_message = f"Hi {first_name}, I came across your profile and was impressed by your work at {company_name}."
980
- # elif first_name:
981
- # welcome_message = f"Hi {first_name}, I came across your profile and was impressed by your background."
982
- # elif company_name:
983
- # welcome_message = f"Hi there, I came across your profile and was impressed by your work at {company_name}."
984
- # else:
985
- # welcome_message = "Hi there, I came across your profile and was impressed by your background."
986
-
987
- # # Personalized closing message
988
- # if first_name:
989
- # closing_message = f"Looking forward to connecting with you, {first_name}!"
990
- # else:
991
- # closing_message = "Looking forward to connecting with you!"
992
-
993
- # return {
994
- # "welcome_message": welcome_message,
995
- # "closing_message": closing_message
996
- # }
997
-
998
- # async def generate_sequences_with_llm(job_description: str) -> List[CampaignSequence]:
999
- # class email_seq(BaseModel):
1000
- # subject: str = Field(description="Subject line for the email")
1001
- # body: str = Field(description="Body of the email")
1002
- # class structure(BaseModel):
1003
- # introduction: email_seq = Field(description="Email sequence for sequence 1 asking for consent and interest in the role")
1004
- # email_sequence_2: email_seq = Field(description="Email sequence for sequence 2 following up on updates and next steps")
1005
- # email_sequence_3: email_seq = Field(description="Email sequence for sequence 3 Another variant on following up on updates and next steps")
1006
-
1007
-
1008
-
1009
- # """Generate email sequences using LangChain and OpenAI based on job description"""
1010
-
1011
- # if not LANGCHAIN_AVAILABLE:
1012
- # return await generate_template_sequences(job_description)
1013
-
1014
- # try:
1015
- # openai_api_key = os.getenv("OPENAI_API_KEY")
1016
- # if not openai_api_key:
1017
- # print("Warning: OPENAI_API_KEY not set. Using template sequences.")
1018
- # return await generate_template_sequences(job_description)
1019
-
1020
- # llm = ChatOpenAI(
1021
- # model="gpt-4o-mini",
1022
- # temperature=0.7,
1023
- # openai_api_key=openai_api_key
1024
- # )
1025
- # str_llm = llm.with_structured_output(structure, method="function_calling")
1026
-
1027
- # system_prompt = """You are an expert email sequence template generator for recruitment campaigns.
1028
-
1029
- # Generate ONLY the subject lines and email body content for 3 professional email sequences.
1030
- # Write the email on behalf of Ali Taghikhani, CEO SRN.
1031
-
1032
- # Use the following placeholders in your templates, EXACTLY as writtenm, AlWAYS use double braces for placeholders:
1033
- # - `{{first_name}}`
1034
- # - `{{company_name}}`
1035
- # - `{{Title}}`
1036
- # - `{{Welcome_Message}}`
1037
- # - `{{Closing_Message}}`
1038
-
1039
- # Email Sequence Structure:
1040
- # 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.
1041
- # 2. OUTREACH (Day 3): Provide detailed job information. This is a follow-up, so it should not have a subject line.
1042
- # 3. FOLLOW-UP (Day 5): Another follow-up on updates and next steps. Also no subject line.
1043
-
1044
- # Requirements:
1045
- # - The first sequence must start with `{{Welcome_Message}}` and end with `{{Closing_Message}}`.
1046
- # - Always end the email with Best regards
1047
- # - The second and third sequences are follow-ups and should not have a subject line.
1048
- # - All emails should be HTML formatted with proper `<br>` tags.
1049
- # - Professional but friendly tone.
1050
- # - Include clear call-to-actions.
1051
- # - Focus on building consent and trust.
1052
-
1053
- # IMPORTANT: Respond with ONLY valid JSON. No additional text."""
1054
-
1055
- # prompt_template = ChatPromptTemplate.from_messages([
1056
- # ("system", system_prompt),
1057
- # ("human", "Generate email content for this job description: {job_description}")
1058
- # ])
1059
-
1060
- # messages = prompt_template.format_messages(job_description=job_description)
1061
- # response = await str_llm.ainvoke(messages)
1062
-
1063
- # try:
1064
- # # Handle the response structure correctly
1065
- # sequences = []
1066
-
1067
- # # Sequence 1: Introduction with A/B variants
1068
- # if hasattr(response, 'introduction') and response.introduction:
1069
- # # Check if body is a string or dict
1070
- # intro_body = response.introduction.body
1071
- # if isinstance(intro_body, dict):
1072
- # # If it's a dict, extract the content
1073
- # intro_body = str(intro_body)
1074
-
1075
- # sequences.append(CampaignSequence(
1076
- # seq_number=1,
1077
- # seq_delay_details=SeqDelayDetails(delay_in_days=1),
1078
- # seq_variants=[
1079
- # SeqVariant(
1080
- # subject=response.introduction.subject,
1081
- # email_body=intro_body,
1082
- # variant_label="A"
1083
- # )
1084
- # ]
1085
- # ))
1086
-
1087
- # # Sequence 2: Outreach
1088
- # if hasattr(response, 'email_sequence_2') and response.email_sequence_2:
1089
- # seq2_body = response.email_sequence_2.body
1090
- # if isinstance(seq2_body, dict):
1091
- # seq2_body = str(seq2_body)
1092
-
1093
- # sequences.append(CampaignSequence(
1094
- # seq_number=2,
1095
- # seq_delay_details=SeqDelayDetails(delay_in_days=3),
1096
- # subject="",
1097
- # email_body=seq2_body
1098
- # ))
1099
-
1100
- # # Sequence 3: Follow-up
1101
- # if hasattr(response, 'email_sequence_3') and response.email_sequence_3:
1102
- # seq3_body = response.email_sequence_3.body
1103
- # if isinstance(seq3_body, dict):
1104
- # seq3_body = str(seq3_body)
1105
-
1106
- # sequences.append(CampaignSequence(
1107
- # seq_number=3,
1108
- # seq_delay_details=SeqDelayDetails(delay_in_days=5),
1109
- # subject="",
1110
- # email_body=seq3_body
1111
- # ))
1112
-
1113
- # # Fill with templates if needed
1114
- # while len(sequences) < 3:
1115
- # if len(sequences) == 0:
1116
- # sequences.append(CampaignSequence(
1117
- # seq_number=1,
1118
- # seq_delay_details=SeqDelayDetails(delay_in_days=1),
1119
- # seq_variants=[
1120
- # SeqVariant(
1121
- # subject=f"Quick question about {job_description}",
1122
- # email_body=f"""<p>Hi there,<br><br>
1123
- # I came across your profile and noticed your experience in {job_description}.
1124
- # I'm reaching out because we have some exciting opportunities that might be a great fit for your background.<br><br>
1125
- # Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?<br><br>
1126
- # Would you be interested in hearing more about the roles we have available?<br><br>
1127
- # Best regards,<br>
1128
- # [Your Name]</p>""",
1129
- # variant_label="A"
1130
- # ),
1131
- # SeqVariant(
1132
- # subject=f"Interested in {job_description} opportunities?",
1133
- # email_body=f"""<p>Hello,<br><br>
1134
- # I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.<br><br>
1135
- # I'd love to connect and share some opportunities that align with your expertise.
1136
- # Are you currently open to exploring new roles in this space?<br><br>
1137
- # If so, I can send you specific details about the positions we have available.<br><br>
1138
- # Thanks,<br>
1139
- # [Your Name]</p>""",
1140
- # variant_label="B"
1141
- # )
1142
- # ]
1143
- # ))
1144
- # elif len(sequences) == 1:
1145
- # sequences.append(CampaignSequence(
1146
- # seq_number=2,
1147
- # seq_delay_details=SeqDelayDetails(delay_in_days=3),
1148
- # subject="",
1149
- # email_body=f"""<p>Hi,<br><br>
1150
- # Thanks for your interest! Here are more details about the {job_description} opportunities:<br><br>
1151
- # <strong>Role Details:</strong><br>
1152
- # • [Specific responsibilities]<br>
1153
- # • [Required skills and experience]<br>
1154
- # • [Team and company information]<br><br>
1155
- # <strong>Benefits:</strong><br>
1156
- # • [Compensation and benefits]<br>
1157
- # • [Growth opportunities]<br>
1158
- # • [Work environment]<br><br>
1159
- # Would you be interested in a quick call to discuss this role in more detail?<br><br>
1160
- # Best regards,<br>
1161
- # [Your Name]</p>"""
1162
- # ))
1163
- # elif len(sequences) == 2:
1164
- # sequences.append(CampaignSequence(
1165
- # seq_number=3,
1166
- # seq_delay_details=SeqDelayDetails(delay_in_days=5),
1167
- # subject="",
1168
- # email_body=f"""<p>Hi,<br><br>
1169
- # Just wanted to follow up on the {job_description} opportunity I shared.<br><br>
1170
- # Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.<br><br>
1171
- # If you're interested, I can help schedule next steps. If not, no worries at all!<br><br>
1172
- # Thanks for your time!<br>
1173
- # [Your Name]</p>"""
1174
- # ))
1175
-
1176
- # return sequences
1177
-
1178
- # except Exception as parse_error:
1179
- # print(f"JSON parsing failed: {parse_error}")
1180
- # return await generate_template_sequences(job_description)
1181
-
1182
- # except Exception as e:
1183
- # print(f"Error generating sequences with LLM: {str(e)}")
1184
- # return await generate_template_sequences(job_description)
1185
-
1186
- # def create_sequences_from_content(content: dict, job_description: str) -> List[CampaignSequence]:
1187
- # """Create CampaignSequence objects from parsed LLM content"""
1188
-
1189
- # sequences = []
1190
-
1191
- # # Sequence 1: Introduction with A/B variants
1192
- # if "sequence1_variant_a" in content and "sequence1_variant_b" in content:
1193
- # variants = []
1194
-
1195
- # if "sequence1_variant_a" in content:
1196
- # var_a = content["sequence1_variant_a"]
1197
- # variants.append(SeqVariant(
1198
- # subject=var_a.get("subject", f"Quick question about {job_description}"),
1199
- # email_body=var_a.get("body", ""),
1200
- # variant_label="A"
1201
- # ))
1202
-
1203
- # if "sequence1_variant_b" in content:
1204
- # var_b = content["sequence1_variant_b"]
1205
- # variants.append(SeqVariant(
1206
- # subject=var_b.get("subject", f"Interested in {job_description} opportunities?"),
1207
- # email_body=var_b.get("body", ""),
1208
- # variant_label="B"
1209
- # ))
1210
-
1211
- # sequences.append(CampaignSequence(
1212
- # seq_number=1,
1213
- # seq_delay_details=SeqDelayDetails(delay_in_days=1),
1214
- # seq_variants=variants
1215
- # ))
1216
-
1217
- # # Sequence 2: Outreach
1218
- # if "sequence2" in content:
1219
- # seq2_body = content["sequence2"].get("body", "")
1220
- # sequences.append(CampaignSequence(
1221
- # seq_number=2,
1222
- # seq_delay_details=SeqDelayDetails(delay_in_days=3),
1223
- # subject="",
1224
- # email_body=seq2_body
1225
- # ))
1226
-
1227
- # # Sequence 3: Follow-up
1228
- # if "sequence3" in content:
1229
- # seq3_body = content["sequence3"].get("body", "")
1230
- # sequences.append(CampaignSequence(
1231
- # seq_number=3,
1232
- # seq_delay_details=SeqDelayDetails(delay_in_days=5),
1233
- # subject="",
1234
- # email_body=seq3_body
1235
- # ))
1236
-
1237
- # # Fill with templates if needed
1238
- # while len(sequences) < 3:
1239
- # if len(sequences) == 0:
1240
- # sequences.append(CampaignSequence(
1241
- # seq_number=1,
1242
- # seq_delay_details=SeqDelayDetails(delay_in_days=1),
1243
- # seq_variants=[
1244
- # SeqVariant(
1245
- # subject=f"Quick question about {job_description}",
1246
- # email_body=f"""<p>Hi there,<br><br>
1247
- # I came across your profile and noticed your experience in {job_description}.
1248
- # I'm reaching out because we have some exciting opportunities that might be a great fit for your background.<br><br>
1249
- # Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?<br><br>
1250
- # Would you be interested in hearing more about the roles we have available?<br><br>
1251
- # Best regards,<br>
1252
- # [Your Name]</p>""",
1253
- # variant_label="A"
1254
- # ),
1255
- # SeqVariant(
1256
- # subject=f"Interested in {job_description} opportunities?",
1257
- # email_body=f"""<p>Hello,<br><br>
1258
- # I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.<br><br>
1259
- # I'd love to connect and share some opportunities that align with your expertise.
1260
- # Are you currently open to exploring new roles in this space?<br><br>
1261
- # If so, I can send you specific details about the positions we have available.<br><br>
1262
- # Thanks,<br>
1263
- # [Your Name]</p>""",
1264
- # variant_label="B"
1265
- # )
1266
- # ]
1267
- # ))
1268
- # elif len(sequences) == 1:
1269
- # sequences.append(CampaignSequence(
1270
- # seq_number=2,
1271
- # seq_delay_details=SeqDelayDetails(delay_in_days=3),
1272
- # subject="",
1273
- # email_body=f"""<p>Hi,<br><br>
1274
- # Thanks for your interest! Here are more details about the {job_description} opportunities:<br><br>
1275
- # <strong>Role Details:</strong><br>
1276
- # • [Specific responsibilities]<br>
1277
- # • [Required skills and experience]<br>
1278
- # • [Team and company information]<br><br>
1279
- # <strong>Benefits:</strong><br>
1280
- # • [Compensation and benefits]<br>
1281
- # • [Growth opportunities]<br>
1282
- # • [Work environment]<br><br>
1283
- # Would you be interested in a quick call to discuss this role in more detail?<br><br>
1284
- # Best regards,<br>
1285
- # [Your Name]</p>"""
1286
- # ))
1287
- # elif len(sequences) == 2:
1288
- # sequences.append(CampaignSequence(
1289
- # seq_number=3,
1290
- # seq_delay_details=SeqDelayDetails(delay_in_days=5),
1291
- # subject="",
1292
- # email_body=f"""<p>Hi,<br><br>
1293
- # Just wanted to follow up on the {job_description} opportunity I shared.<br><br>
1294
- # Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.<br><br>
1295
- # If you're interested, I can help schedule next steps. If not, no worries at all!<br><br>
1296
- # Thanks for your time!<br>
1297
- # [Your Name]</p>"""
1298
- # ))
1299
-
1300
- # return sequences
1301
-
1302
- # async def generate_template_sequences(job_description: str) -> List[CampaignSequence]:
1303
- # """Generate template-based sequences as fallback"""
1304
-
1305
- # sequences = [
1306
- # CampaignSequence(
1307
- # seq_number=1,
1308
- # seq_delay_details=SeqDelayDetails(delay_in_days=1),
1309
- # seq_variants=[
1310
- # SeqVariant(
1311
- # subject=f"Quick question about {job_description}",
1312
- # email_body=f"""<p>Hi there,<br><br>
1313
- # I came across your profile and noticed your experience in {job_description}.
1314
- # I'm reaching out because we have some exciting opportunities that might be a great fit for your background.<br><br>
1315
- # Before I share more details, I wanted to ask: Are you currently open to exploring new opportunities in this space?<br><br>
1316
- # Would you be interested in hearing more about the roles we have available?<br><br>
1317
- # Best regards,<br>
1318
- # [Your Name]</p>""",
1319
- # variant_label="A"
1320
- # ),
1321
- # SeqVariant(
1322
- # subject=f"Interested in {job_description} opportunities?",
1323
- # email_body=f"""<p>Hello,<br><br>
1324
- # I hope this message finds you well. I'm a recruiter specializing in {job_description} positions.<br><br>
1325
- # I'd love to connect and share some opportunities that align with your expertise.
1326
- # Are you currently open to exploring new roles in this space?<br><br>
1327
- # If so, I can send you specific details about the positions we have available.<br><br>
1328
- # Thanks,<br>
1329
- # [Your Name]</p>""",
1330
- # variant_label="B"
1331
- # )
1332
- # ]
1333
- # ),
1334
- # CampaignSequence(
1335
- # seq_number=2,
1336
- # seq_delay_details=SeqDelayDetails(delay_in_days=3),
1337
- # subject="",
1338
- # email_body=f"""<p>Hi,<br><br>
1339
- # Thanks for your interest! Here are more details about the {job_description} opportunities:<br><br>
1340
- # <strong>Role Details:</strong><br>
1341
- # • [Specific responsibilities]<br>
1342
- # • [Required skills and experience]<br>
1343
- # • [Team and company information]<br><br>
1344
- # <strong>Benefits:</strong><br>
1345
- # • [Compensation and benefits]<br>
1346
- # • [Growth opportunities]<br>
1347
- # • [Work environment]<br><br>
1348
- # Would you be interested in a quick call to discuss this role in more detail?<br><br>
1349
- # Best regards,<br>
1350
- # [Your Name]</p>"""
1351
- # ),
1352
- # CampaignSequence(
1353
- # seq_number=3,
1354
- # seq_delay_details=SeqDelayDetails(delay_in_days=5),
1355
- # subject="",
1356
- # email_body=f"""<p>Hi,<br><br>
1357
- # Just wanted to follow up on the {job_description} opportunity I shared.<br><br>
1358
- # Have you had a chance to review the information? I'd love to hear your thoughts and answer any questions.<br><br>
1359
- # If you're interested, I can help schedule next steps. If not, no worries at all!<br><br>
1360
- # Thanks for your time!<br>
1361
- # [Your Name]</p>"""
1362
- # )
1363
- # ]
1364
-
1365
- # return sequences
1366
-
1367
- # # ============================================================================
1368
- # # RATE LIMITING MIDDLEWARE
1369
- # # ============================================================================
1370
-
1371
- # class RateLimiter:
1372
- # def __init__(self, max_requests: int = 10, window_seconds: int = 2):
1373
- # self.max_requests = max_requests
1374
- # self.window_seconds = window_seconds
1375
- # self.requests = []
1376
-
1377
- # def is_allowed(self) -> bool:
1378
- # now = time.time()
1379
- # # Remove old requests outside the window
1380
- # self.requests = [req_time for req_time in self.requests if now - req_time < self.window_seconds]
1381
-
1382
- # if len(self.requests) >= self.max_requests:
1383
- # return False
1384
-
1385
- # self.requests.append(now)
1386
- # return True
1387
-
1388
- # # Global rate limiter instance
1389
- # rate_limiter = RateLimiter(max_requests=10, window_seconds=2)
1390
-
1391
- # @app.middleware("http")
1392
- # async def rate_limit_middleware(request: Request, call_next):
1393
- # """Rate limiting middleware to respect Smartlead's API limits"""
1394
- # if not rate_limiter.is_allowed():
1395
- # return JSONResponse(
1396
- # status_code=429,
1397
- # content={
1398
- # "error": "Rate limit exceeded",
1399
- # "message": "Too many requests. Please wait before making another request.",
1400
- # "retry_after": 2
1401
- # }
1402
- # )
1403
-
1404
- # response = await call_next(request)
1405
- # return response
1406
-
1407
- # # ============================================================================
1408
- # # ERROR HANDLING
1409
- # # ============================================================================
1410
-
1411
- # @app.exception_handler(HTTPException)
1412
- # async def http_exception_handler(request: Request, exc: HTTPException):
1413
- # """Custom HTTP exception handler"""
1414
- # return JSONResponse(
1415
- # status_code=exc.status_code,
1416
- # content={
1417
- # "error": True,
1418
- # "message": exc.detail,
1419
- # "status_code": exc.status_code,
1420
- # "timestamp": datetime.now().isoformat()
1421
- # }
1422
- # )
1423
-
1424
- # @app.exception_handler(Exception)
1425
- # async def general_exception_handler(request: Request, exc: Exception):
1426
- # """General exception handler"""
1427
- # return JSONResponse(
1428
- # status_code=500,
1429
- # content={
1430
- # "error": True,
1431
- # "message": "Internal server error",
1432
- # "detail": str(exc) if os.getenv("DEBUG", "false").lower() == "true" else "An unexpected error occurred",
1433
- # "timestamp": datetime.now().isoformat()
1434
- # }
1435
- # )
1436
-
1437
- # # ============================================================================
1438
- # # CUSTOM OPENAPI SCHEMA
1439
- # # ============================================================================
1440
-
1441
- # def custom_openapi():
1442
- # if app.openapi_schema:
1443
- # return app.openapi_schema
1444
-
1445
- # openapi_schema = get_openapi(
1446
- # title="Smartlead API - Complete Integration",
1447
- # version="2.0.0",
1448
- # description="""
1449
- # # Smartlead API - Complete Integration
1450
-
1451
- # A comprehensive FastAPI wrapper for the Smartlead email automation platform.
1452
-
1453
- # ## Features
1454
- # - **Campaign Management**: Create, update, and manage email campaigns
1455
- # - **Lead Management**: Add, update, and manage leads across campaigns with AI-powered personalization
1456
- # - **Sequence Management**: Create and manage email sequences with AI generation using job_id from database
1457
- # - **Webhook Management**: Set up webhooks for real-time notifications
1458
- # - **Analytics**: Get detailed campaign analytics and statistics
1459
- # - **Email Account Management**: Manage email accounts and warmup
1460
- # - **Client Management**: Handle client accounts and permissions
1461
- # - **Job Management**: Fetch job details from database for sequence generation
1462
-
1463
- # ## AI-Powered Personalization
1464
- # When adding leads to campaigns, the API automatically generates personalized welcome and closing messages using LLM (Language Model) based on candidate details. These messages are added to the custom_fields as:
1465
- # - `Welcome_Message`: Personalized greeting based on candidate's background
1466
- # - `Closing_Message`: Personalized closing statement
1467
-
1468
- # ## Sequence Generation with Job ID
1469
- # The sequence generation endpoint now supports using `job_id` to fetch job details from the database and automatically build comprehensive job descriptions for AI-powered sequence generation. This eliminates the need to manually provide job descriptions.
1470
-
1471
- # ## Lead Schema
1472
- # The lead schema supports the following structure:
1473
- # ```json
1474
- # {
1475
- # "lead_list": [
1476
- # {
1477
- # "first_name": "Cristiano",
1478
- # "last_name": "Ronaldo",
1479
- # "email": "[email protected]",
1480
- # "phone_number": "0239392029",
1481
- # "company_name": "Manchester United",
1482
- # "website": "mufc.com",
1483
- # "location": "London",
1484
- # "custom_fields": {
1485
- # "Title": "Regional Manager",
1486
- # "First_Line": "Loved your recent post about remote work on Linkedin"
1487
- # },
1488
- # "linkedin_profile": "http://www.linkedin.com/in/cristianoronaldo",
1489
- # "company_url": "mufc.com"
1490
- # }
1491
- # ],
1492
- # "settings": {
1493
- # "ignore_global_block_list": true,
1494
- # "ignore_unsubscribe_list": true,
1495
- # "ignore_duplicate_leads_in_other_campaign": false
1496
- # }
1497
- # }
1498
- # ```
1499
-
1500
- # ## Authentication
1501
- # All requests require a Smartlead API key passed as a query parameter: `?api_key=YOUR_API_KEY`
1502
-
1503
- # ## Rate Limits
1504
- # - 10 requests per 2 seconds (enforced automatically)
1505
-
1506
- # ## Base URL
1507
- # - Smartlead API: `https://server.smartlead.ai/api/v1`
1508
- # """,
1509
- # routes=app.routes,
1510
- # )
1511
-
1512
- # # Add custom tags
1513
- # openapi_schema["tags"] = [
1514
- # {"name": "Campaigns", "description": "Campaign management operations"},
1515
- # {"name": "Leads", "description": "Lead management operations"},
1516
- # {"name": "Sequences", "description": "Email sequence management"},
1517
- # {"name": "Webhooks", "description": "Webhook management"},
1518
- # {"name": "Clients", "description": "Client account management"},
1519
- # {"name": "Messages", "description": "Message history and reply operations"},
1520
- # {"name": "Analytics", "description": "Campaign analytics and statistics"},
1521
- # {"name": "Email Accounts", "description": "Email account management"},
1522
- # {"name": "Jobs", "description": "Job management operations"},
1523
- # {"name": "Utilities", "description": "Utility endpoints"}
1524
- # ]
1525
-
1526
- # app.openapi_schema = openapi_schema
1527
- # return app.openapi_schema
1528
-
1529
- # app.openapi = custom_openapi
1530
-
1531
- # # ============================================================================
1532
- # # MAIN APPLICATION ENTRY POINT
1533
- # # ============================================================================
1534
-
1535
- # if __name__ == "__main__":
1536
- # import uvicorn
1537
-
1538
- # print("�� Starting Smartlead API - Complete Integration")
1539
- # print(f"�� API Documentation: http://localhost:8000/docs")
1540
- # print(f"📖 ReDoc Documentation: http://localhost:8000/redoc")
1541
- # print(f"�� Smartlead Base URL: {SMARTLEAD_BASE_URL}")
1542
- # print(f"⚡ Rate Limit: 10 requests per 2 seconds")
1543
-
1544
- # uvicorn.run(
1545
- # "updated-final:app",
1546
- # host="0.0.0.0",
1547
- # port=8000,
1548
- # reload=True,
1549
- # log_level="info"
1550
- # )
1551
-
1552
-
1553
  import os
1554
  import re
1555
  import json
@@ -2576,7 +1024,7 @@ Requirements:
2576
 
2577
  3. **FIRST EMAIL STRUCTURE:** The first email's body MUST begin with `{{Welcome_Message}}` and end with `{{Closing_Message}}`.
2578
 
2579
- 4. **SIGNATURE:** End EVERY email body with `<br><br>Best regards`.
2580
 
2581
  5. **CONTENT REQUIREMENTS:** Each email must mention:
2582
  - Company name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import re
3
  import json
 
1024
 
1025
  3. **FIRST EMAIL STRUCTURE:** The first email's body MUST begin with `{{Welcome_Message}}` and end with `{{Closing_Message}}`.
1026
 
1027
+ 4. **SIGNATURE:** End EVERY email body with `<br><br>Best regards`. DO NOT WRITE NAME AND TITLE AS IT WILL BE ALREADY ADDED BY DEFAULT
1028
 
1029
  5. **CONTENT REQUIREMENTS:** Each email must mention:
1030
  - Company name