ak0601 commited on
Commit
23f4c61
·
verified ·
1 Parent(s): ef94105

Update app_hug.py

Browse files
Files changed (1) hide show
  1. app_hug.py +607 -19
app_hug.py CHANGED
@@ -1,3 +1,548 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI, HTTPException, BackgroundTasks
2
  from pydantic import BaseModel, EmailStr, Field
3
  from typing import Optional, Tuple
@@ -166,14 +711,18 @@ def authenticate_gmail(email: str, create_if_missing: bool = False):
166
  except Exception:
167
  return None
168
 
169
- def create_email_message(sender: str, to: str, subject: str, message_text: str, reply_to: Optional[str] = None):
170
- message = MIMEText(message_text)
171
- message["to"] = to
172
- message["from"] = sender
173
- message["subject"] = subject
 
 
 
 
 
174
  if reply_to:
175
- message["reply-to"] = reply_to
176
- message["Cc"] = reply_to
177
  raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
178
  return {"raw": raw_message}
179
 
@@ -334,6 +883,33 @@ def generate_recruitment_message_with_subject(
334
  except Exception as e:
335
  raise HTTPException(status_code=500, detail=f"Error generating message: {str(e)}")
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  # ------------------------------------------
338
  # FastAPI Endpoints
339
  # ------------------------------------------
@@ -363,14 +939,20 @@ async def send_message(request: SendMessageRequest):
363
  message="",
364
  error="Gmail authentication failed"
365
  )
 
 
 
 
 
366
  # Create the email
367
  email_message = create_email_message(
368
- sender=request.sender_email,
369
- to=request.recipient_email,
370
- subject=request.subject,
371
- message_text=request.email_body,
372
- reply_to=request.reply_to_email
373
- )
 
374
  # Send the email
375
  email_sent = send_gmail_message(service, "me", email_message)
376
  if email_sent:
@@ -425,13 +1007,19 @@ async def generate_message(request: GenerateMessageRequest, background_tasks: Ba
425
  )
426
  service = authenticate_gmail(request.sender_email)
427
  if service:
 
 
 
 
 
428
  email_message = create_email_message(
429
- sender=request.sender_email,
430
- to=request.recipient_email,
431
- subject=email_subject,
432
- message_text=generated_message,
433
- reply_to=request.reply_to_email
434
- )
 
435
  email_sent = send_gmail_message(service, "me", email_message)
436
  if not email_sent:
437
  return MessageResponse(
 
1
+ # from fastapi import FastAPI, HTTPException, BackgroundTasks
2
+ # from pydantic import BaseModel, EmailStr, Field
3
+ # from typing import Optional, Tuple
4
+ # from enum import Enum
5
+ # import os
6
+ # import base64
7
+ # import pickle
8
+ # import pandas as pd
9
+ # from dotenv import load_dotenv
10
+ # from langchain_openai import ChatOpenAI
11
+ # from langchain.schema import HumanMessage, SystemMessage
12
+ # from email.mime.text import MIMEText
13
+ # from google.auth.transport.requests import Request
14
+ # from google.oauth2.credentials import Credentials
15
+ # from google_auth_oauthlib.flow import InstalledAppFlow
16
+ # from googleapiclient.discovery import build
17
+ # from googleapiclient.errors import HttpError
18
+ # from datetime import datetime
19
+ # import json
20
+
21
+ # # Load environment variables (not needed on Hugging Face, but harmless)
22
+ # load_dotenv()
23
+
24
+ # # ------------------------------------------
25
+ # # Helper: Write GOOGLE_CREDENTIALS_JSON to file if needed
26
+ # # ------------------------------------------
27
+ # def ensure_credentials_file():
28
+ # credentials_env = os.getenv("GOOGLE_CREDENTIALS_JSON")
29
+ # credentials_path = "credentials_SYNAPSE.json"
30
+ # if not os.path.exists(credentials_path):
31
+ # if not credentials_env:
32
+ # raise Exception("GOOGLE_CREDENTIALS_JSON not found in environment variables.")
33
+ # try:
34
+ # parsed_json = json.loads(credentials_env)
35
+ # except json.JSONDecodeError:
36
+ # raise Exception("Invalid JSON in GOOGLE_CREDENTIALS_JSON")
37
+ # with open(credentials_path, "w") as f:
38
+ # json.dump(parsed_json, f, indent=2)
39
+ # return credentials_path
40
+
41
+ # # ------------------------------------------
42
+ # # FastAPI app
43
+ # # ------------------------------------------
44
+ # app = FastAPI(title="Recruitment Message Generator API", version="1.0.0")
45
+
46
+ # SCOPES = ["https://www.googleapis.com/auth/gmail.send"]
47
+ # openai_api_key = os.getenv("OPENAI_API_KEY")
48
+
49
+ # # ------------------------------------------
50
+ # # Enums and Models
51
+ # # ------------------------------------------
52
+ # class MessageType(str, Enum):
53
+ # OUTREACH = "outreach"
54
+ # INTRODUCTORY = "introductory"
55
+ # FOLLOWUP = "followup"
56
+
57
+ # class GenerateMessageRequest(BaseModel):
58
+ # job_evaluation: str
59
+ # sender_email: EmailStr
60
+ # reply_to_email: Optional[EmailStr] = Field(None, description="Recruiter's email for reply-to header")
61
+ # recipient_email: EmailStr
62
+ # candidate_name: str
63
+ # current_role: str
64
+ # current_company: str
65
+ # company_name: str
66
+ # role: str
67
+ # recruiter_name: str
68
+ # organisation: str
69
+ # message_type: MessageType
70
+ # send_email: bool = False
71
+
72
+ # class FeedbackRequest(BaseModel):
73
+ # message: str
74
+ # feedback: str
75
+
76
+ # class AuthenticateRequest(BaseModel):
77
+ # email: EmailStr
78
+
79
+ # class AuthenticateResponse(BaseModel):
80
+ # success: bool
81
+ # message: str
82
+ # error: Optional[str] = None
83
+
84
+ # class MessageResponse(BaseModel):
85
+ # success: bool
86
+ # message: str
87
+ # email_sent: bool = False
88
+ # email_subject: Optional[str] = None
89
+ # error: Optional[str] = None
90
+
91
+ # class SendMessageRequest(BaseModel):
92
+ # subject: str
93
+ # email_body: str
94
+ # sender_email: EmailStr
95
+ # recipient_email: EmailStr
96
+ # reply_to_email: Optional[EmailStr] = None
97
+
98
+ # # ------------------------------------------
99
+ # # Gmail Helper Functions
100
+ # # ------------------------------------------
101
+ # def get_token_file_path(email: str) -> str:
102
+ # tokens_dir = "gmail_tokens"
103
+ # if not os.path.exists(tokens_dir):
104
+ # os.makedirs(tokens_dir)
105
+ # safe_email = email.replace("@", "_at_").replace(".", "_dot_")
106
+ # return os.path.join(tokens_dir, f"token_{safe_email}.pickle")
107
+
108
+ # def check_user_token_exists(email: str) -> bool:
109
+ # token_file = get_token_file_path(email)
110
+ # return os.path.exists(token_file)
111
+
112
+ # def load_user_credentials(email: str):
113
+ # token_file = get_token_file_path(email)
114
+ # if os.path.exists(token_file):
115
+ # try:
116
+ # with open(token_file, 'rb') as token:
117
+ # creds = pickle.load(token)
118
+ # return creds
119
+ # except Exception:
120
+ # if os.path.exists(token_file):
121
+ # os.remove(token_file)
122
+ # return None
123
+
124
+ # def save_user_credentials(email: str, creds):
125
+ # token_file = get_token_file_path(email)
126
+ # with open(token_file, 'wb') as token:
127
+ # pickle.dump(creds, token)
128
+
129
+ # def create_new_credentials(email: str):
130
+ # credentials_path = ensure_credentials_file()
131
+ # flow = InstalledAppFlow.from_client_secrets_file(
132
+ # credentials_path, SCOPES
133
+ # )
134
+ # creds = flow.run_local_server(port=0)
135
+ # save_user_credentials(email, creds)
136
+ # return creds
137
+
138
+ # def authenticate_gmail(email: str, create_if_missing: bool = False):
139
+ # creds = load_user_credentials(email)
140
+ # if creds:
141
+ # if creds.expired and creds.refresh_token:
142
+ # try:
143
+ # creds.refresh(Request())
144
+ # save_user_credentials(email, creds)
145
+ # except Exception:
146
+ # if create_if_missing:
147
+ # try:
148
+ # creds = create_new_credentials(email)
149
+ # except:
150
+ # return None
151
+ # else:
152
+ # return None
153
+ # elif not creds.valid:
154
+ # creds = None
155
+ # if not creds:
156
+ # if create_if_missing:
157
+ # try:
158
+ # creds = create_new_credentials(email)
159
+ # except:
160
+ # return None
161
+ # else:
162
+ # return None
163
+ # try:
164
+ # service = build("gmail", "v1", credentials=creds)
165
+ # return service
166
+ # except Exception:
167
+ # return None
168
+
169
+ # def create_email_message(sender: str, to: str, subject: str, message_text: str, reply_to: Optional[str] = None):
170
+ # message = MIMEText(message_text)
171
+ # message["to"] = to
172
+ # message["from"] = sender
173
+ # message["subject"] = subject
174
+ # if reply_to:
175
+ # message["reply-to"] = reply_to
176
+ # message["Cc"] = reply_to
177
+ # raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
178
+ # return {"raw": raw_message}
179
+
180
+ # def send_gmail_message(service, user_id: str, message: dict):
181
+ # try:
182
+ # result = service.users().messages().send(userId=user_id, body=message).execute()
183
+ # return result is not None
184
+ # except HttpError:
185
+ # return False
186
+
187
+ # # ------------------------------------------
188
+ # # LLM (OpenAI) Message Generation Helpers
189
+ # # ------------------------------------------
190
+ # def refine_message_with_feedback(
191
+ # original_message: str,
192
+ # feedback: str,
193
+ # ) -> Tuple[str, str]:
194
+ # api_key = os.getenv("OPENAI_API_KEY")
195
+ # llm = ChatOpenAI(
196
+ # model="gpt-4o-mini",
197
+ # temperature=0.7,
198
+ # max_tokens=600,
199
+ # openai_api_key=api_key
200
+ # )
201
+ # prompt = f"""
202
+ # Please refine the following recruitment message based on the provided feedback:
203
+
204
+ # ORIGINAL MESSAGE:
205
+ # {original_message}
206
+
207
+ # FEEDBACK:
208
+ # {feedback}
209
+
210
+ # Please provide your response in the following format:
211
+ # SUBJECT: [Your subject line here]
212
+
213
+ # BODY:
214
+ # [Your refined email body content here]
215
+ # Keep the same tone and intent as the original message, but incorporate the feedback to improve it.
216
+ # """
217
+ # try:
218
+ # messages = [
219
+ # SystemMessage(content="You are a professional recruitment message writer. Refine the given message based on feedback while maintaining professionalism and the original intent."),
220
+ # HumanMessage(content=prompt)
221
+ # ]
222
+ # response = llm.invoke(messages)
223
+ # content = response.content.strip()
224
+ # subject_line = ""
225
+ # body_content = ""
226
+ # lines = content.split('\n')
227
+ # body_found = False
228
+ # body_lines = []
229
+ # for line in lines:
230
+ # if line.strip().startswith('SUBJECT:'):
231
+ # subject_line = line.replace('SUBJECT:', '').strip()
232
+ # elif line.strip().startswith('BODY:'):
233
+ # body_found = True
234
+ # elif body_found and line.strip():
235
+ # body_lines.append(line)
236
+ # body_content = '\n'.join(body_lines).strip()
237
+ # if not subject_line:
238
+ # subject_line = "Recruitment Opportunity - Updated"
239
+ # if not body_content:
240
+ # body_content = content
241
+ # return subject_line, body_content
242
+ # except Exception as e:
243
+ # raise HTTPException(status_code=500, detail=f"Error refining message: {str(e)}")
244
+
245
+ # def generate_recruitment_message_with_subject(
246
+ # msg_type: str,
247
+ # company: str,
248
+ # role_title: str,
249
+ # recruiter: str,
250
+ # org: str,
251
+ # candidate: str,
252
+ # current_pos: str,
253
+ # evaluation: str,
254
+ # feedback: Optional[str] = None
255
+ # ) -> Tuple[str, str]:
256
+ # api_key = os.getenv("OPENAI_API_KEY")
257
+ # llm = ChatOpenAI(
258
+ # model="gpt-4o-mini",
259
+ # temperature=0.7,
260
+ # max_tokens=600,
261
+ # openai_api_key=api_key
262
+ # )
263
+ # base_prompt = f"""
264
+ # Generate a professional recruitment {msg_type} with the following details:
265
+ # - Company hiring: {company}
266
+ # - Role: {role_title}
267
+ # - Recruiter: {recruiter} from {org}
268
+ # - Candidate: {candidate}
269
+ # - Candidate's current position: {current_pos}
270
+ # - Evaluation: {evaluation}
271
+ # """
272
+ # if msg_type == "outreach":
273
+ # prompt = base_prompt + """
274
+ # Create an initial outreach message that:
275
+ # - Introduces the recruiter and organization
276
+ # - Mentions the specific role and company
277
+ # - Expresses interest in discussing the opportunity
278
+ # - Keeps it short and to the point.
279
+ # - Do not include any placeholders like [Candidate Name] or [Role Title] in the email.
280
+ # """
281
+ # elif msg_type == "introductory":
282
+ # prompt = base_prompt + """
283
+ # Create an introductory message that:
284
+ # - Thanks the candidate for their initial response
285
+ # - Provides more details about the role and company
286
+ # - Explains why this opportunity aligns with their background
287
+ # - Suggests next steps (like a call or meeting)
288
+ # - Maintains a warm, professional tone
289
+ # """
290
+ # else: # followup
291
+ # prompt = base_prompt + """
292
+ # Create a follow-up message that:
293
+ # - References previous communication
294
+ # - Reiterates interest in the candidate
295
+ # - Addresses any potential concerns
296
+ # - Provides additional compelling reasons to consider the role
297
+ # - Includes a clear call to action
298
+ # """
299
+ # if feedback:
300
+ # prompt += f"\n\nPlease modify the message based on this feedback: {feedback}"
301
+ # prompt += """
302
+
303
+ # Please provide your response in the following format:
304
+ # SUBJECT: [Your subject line here]
305
+
306
+ # BODY:
307
+ # [Your email body content here]
308
+ # """
309
+ # try:
310
+ # messages = [
311
+ # SystemMessage(content="You are a professional recruitment message writer. Generate both an email subject line and body content. Follow the exact format requested."),
312
+ # HumanMessage(content=prompt)
313
+ # ]
314
+ # response = llm.invoke(messages)
315
+ # content = response.content.strip()
316
+ # subject_line = ""
317
+ # body_content = ""
318
+ # lines = content.split('\n')
319
+ # body_found = False
320
+ # body_lines = []
321
+ # for line in lines:
322
+ # if line.strip().startswith('SUBJECT:'):
323
+ # subject_line = line.replace('SUBJECT:', '').strip()
324
+ # elif line.strip().startswith('BODY:'):
325
+ # body_found = True
326
+ # elif body_found and line.strip():
327
+ # body_lines.append(line)
328
+ # body_content = '\n'.join(body_lines).strip()
329
+ # if not subject_line:
330
+ # subject_line = f"Opportunity at {company} - {role_title}"
331
+ # if not body_content:
332
+ # body_content = content
333
+ # return subject_line, body_content
334
+ # except Exception as e:
335
+ # raise HTTPException(status_code=500, detail=f"Error generating message: {str(e)}")
336
+
337
+ # # ------------------------------------------
338
+ # # FastAPI Endpoints
339
+ # # ------------------------------------------
340
+ # @app.get("/")
341
+ # async def root():
342
+ # return {
343
+ # "message": "Recruitment Message Generator API",
344
+ # "version": "1.0.0",
345
+ # "endpoints": [
346
+ # "/generate-message",
347
+ # "/refine-message",
348
+ # "/authenticate",
349
+ # "/send-message",
350
+ # "/docs"
351
+
352
+ # ]
353
+ # }
354
+
355
+ # @app.post("/send-message", response_model=MessageResponse)
356
+ # async def send_message(request: SendMessageRequest):
357
+ # try:
358
+ # # Authenticate sender
359
+ # service = authenticate_gmail(request.sender_email)
360
+ # if not service:
361
+ # return MessageResponse(
362
+ # success=False,
363
+ # message="",
364
+ # error="Gmail authentication failed"
365
+ # )
366
+ # # Create the email
367
+ # email_message = create_email_message(
368
+ # sender=request.sender_email,
369
+ # to=request.recipient_email,
370
+ # subject=request.subject,
371
+ # message_text=request.email_body,
372
+ # reply_to=request.reply_to_email
373
+ # )
374
+ # # Send the email
375
+ # email_sent = send_gmail_message(service, "me", email_message)
376
+ # if email_sent:
377
+ # return MessageResponse(
378
+ # success=True,
379
+ # message="Email sent successfully.",
380
+ # email_sent=True,
381
+ # email_subject=request.subject
382
+ # )
383
+ # else:
384
+ # return MessageResponse(
385
+ # success=False,
386
+ # message="",
387
+ # email_sent=False,
388
+ # email_subject=request.subject,
389
+ # error="Failed to send via Gmail API"
390
+ # )
391
+ # except Exception as e:
392
+ # return MessageResponse(
393
+ # success=False,
394
+ # message="",
395
+ # error=str(e)
396
+ # )
397
+
398
+ # @app.post("/generate-message", response_model=MessageResponse)
399
+ # async def generate_message(request: GenerateMessageRequest, background_tasks: BackgroundTasks):
400
+ # try:
401
+ # current_position = f"{request.current_role} at {request.current_company}"
402
+ # email_subject, generated_message = generate_recruitment_message_with_subject(
403
+ # msg_type=request.message_type.value.replace('followup', 'follow-up'),
404
+ # company=request.company_name,
405
+ # role_title=request.role,
406
+ # recruiter=request.recruiter_name,
407
+ # org=request.organisation,
408
+ # candidate=request.candidate_name,
409
+ # current_pos=current_position,
410
+ # evaluation=request.job_evaluation
411
+ # )
412
+ # email_sent = False
413
+ # if request.send_email:
414
+ # registered_users = []
415
+ # if os.path.exists("registered_users.csv"):
416
+ # df = pd.read_csv("registered_users.csv")
417
+ # registered_users = df['email'].tolist() if 'email' in df.columns else []
418
+ # if request.sender_email.lower() not in [user.lower() for user in registered_users]:
419
+ # return MessageResponse(
420
+ # success=True,
421
+ # message=generated_message,
422
+ # email_sent=False,
423
+ # email_subject=email_subject,
424
+ # error="Email not sent: Sender email is not registered"
425
+ # )
426
+ # service = authenticate_gmail(request.sender_email)
427
+ # if service:
428
+ # email_message = create_email_message(
429
+ # sender=request.sender_email,
430
+ # to=request.recipient_email,
431
+ # subject=email_subject,
432
+ # message_text=generated_message,
433
+ # reply_to=request.reply_to_email
434
+ # )
435
+ # email_sent = send_gmail_message(service, "me", email_message)
436
+ # if not email_sent:
437
+ # return MessageResponse(
438
+ # success=True,
439
+ # message=generated_message,
440
+ # email_sent=False,
441
+ # email_subject=email_subject,
442
+ # error="Email not sent: Failed to send via Gmail API"
443
+ # )
444
+ # else:
445
+ # return MessageResponse(
446
+ # success=True,
447
+ # message=generated_message,
448
+ # email_sent=False,
449
+ # email_subject=email_subject,
450
+ # error="Email not sent: Gmail authentication failed"
451
+ # )
452
+ # return MessageResponse(
453
+ # success=True,
454
+ # message=generated_message,
455
+ # email_sent=email_sent,
456
+ # email_subject=email_subject
457
+ # )
458
+ # except Exception as e:
459
+ # return MessageResponse(
460
+ # success=False,
461
+ # message="",
462
+ # error=str(e)
463
+ # )
464
+
465
+ # @app.post("/refine-message", response_model=MessageResponse)
466
+ # async def refine_message(request: FeedbackRequest):
467
+ # try:
468
+ # email_subject, refined_message = refine_message_with_feedback(
469
+ # original_message=request.message,
470
+ # feedback=request.feedback
471
+ # )
472
+ # return MessageResponse(
473
+ # success=True,
474
+ # message=refined_message,
475
+ # email_sent=False,
476
+ # email_subject=email_subject
477
+ # )
478
+ # except Exception as e:
479
+ # return MessageResponse(
480
+ # success=False,
481
+ # message="",
482
+ # error=str(e)
483
+ # )
484
+
485
+ # @app.post("/authenticate", response_model=AuthenticateResponse)
486
+ # async def authenticate_user(request: AuthenticateRequest):
487
+ # try:
488
+ # if check_user_token_exists(request.email):
489
+ # service = authenticate_gmail(request.email, create_if_missing=False)
490
+ # if service:
491
+ # return AuthenticateResponse(
492
+ # success=True,
493
+ # message="User already authenticated and token is valid"
494
+ # )
495
+ # else:
496
+ # service = authenticate_gmail(request.email, create_if_missing=True)
497
+ # if service:
498
+ # return AuthenticateResponse(
499
+ # success=True,
500
+ # message="Token refreshed successfully"
501
+ # )
502
+ # else:
503
+ # return AuthenticateResponse(
504
+ # success=False,
505
+ # message="Failed to refresh token",
506
+ # error="Could not refresh existing token. Please check credentials.json"
507
+ # )
508
+ # else:
509
+ # try:
510
+ # creds = create_new_credentials(request.email)
511
+ # if creds:
512
+ # return AuthenticateResponse(
513
+ # success=True,
514
+ # message="Authentication successful. Token created and saved."
515
+ # )
516
+ # else:
517
+ # return AuthenticateResponse(
518
+ # success=False,
519
+ # message="Authentication failed",
520
+ # error="Failed to create credentials"
521
+ # )
522
+ # except Exception as e:
523
+ # return AuthenticateResponse(
524
+ # success=False,
525
+ # message="Authentication failed",
526
+ # error=str(e)
527
+ # )
528
+ # except Exception as e:
529
+ # return AuthenticateResponse(
530
+ # success=False,
531
+ # message="Authentication error",
532
+ # error=str(e)
533
+ # )
534
+
535
+ # @app.get("/health")
536
+ # async def health_check():
537
+ # return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
538
+
539
+ # if __name__ == "__main__":
540
+ # import uvicorn
541
+ # uvicorn.run(app, host="0.0.0.0", port=8000)
542
+
543
+
544
+
545
+
546
  from fastapi import FastAPI, HTTPException, BackgroundTasks
547
  from pydantic import BaseModel, EmailStr, Field
548
  from typing import Optional, Tuple
 
711
  except Exception:
712
  return None
713
 
714
+ from email.mime.text import MIMEText
715
+
716
+ def create_email_message(sender: str, to: str, subject: str, message_text: str, reply_to: Optional[str] = None, is_html: bool = False):
717
+ if is_html:
718
+ message = MIMEText(message_text, "html")
719
+ else:
720
+ message = MIMEText(message_text, "plain")
721
+ message["To"] = to
722
+ message["From"] = sender
723
+ message["Subject"] = subject
724
  if reply_to:
725
+ message["Reply-To"] = reply_to
 
726
  raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
727
  return {"raw": raw_message}
728
 
 
883
  except Exception as e:
884
  raise HTTPException(status_code=500, detail=f"Error generating message: {str(e)}")
885
 
886
+
887
+ def format_email_html(body: str, sender_name: Optional[str]=None, sender_org: Optional[str]=None):
888
+ """Wrap plain text body in an HTML template for better appearance."""
889
+ # Convert consecutive line breaks into HTML paragraphs
890
+ import re
891
+ # Smart paragraphing
892
+ body = re.sub(r"\n\s*\n", "</p><p>", body.strip()) # Double newlines => new paragraph
893
+ body = re.sub(r"\n", "<br>", body) # Single newlines => <br>
894
+ # Optional signature
895
+ signature = ""
896
+ if sender_name or sender_org:
897
+ signature = "<br><br>Best regards,<br>"
898
+ if sender_name:
899
+ signature += f"{sender_name}<br>"
900
+ if sender_org:
901
+ signature += f"{sender_org}"
902
+ html = f"""
903
+ <html>
904
+ <body style="font-family: Arial, sans-serif; color: #222; line-height: 1.7; max-width: 540px;">
905
+ <p>{body}</p>
906
+ {signature}
907
+ </body>
908
+ </html>
909
+ """
910
+ return html
911
+
912
+
913
  # ------------------------------------------
914
  # FastAPI Endpoints
915
  # ------------------------------------------
 
939
  message="",
940
  error="Gmail authentication failed"
941
  )
942
+
943
+
944
+ formatted_html = format_email_html(
945
+ request.email_body
946
+ )
947
  # Create the email
948
  email_message = create_email_message(
949
+ sender=request.sender_email,
950
+ to=request.recipient_email,
951
+ subject=request.subject,
952
+ message_text=formatted_html,
953
+ reply_to=request.reply_to_email,
954
+ is_html=True
955
+ )
956
  # Send the email
957
  email_sent = send_gmail_message(service, "me", email_message)
958
  if email_sent:
 
1007
  )
1008
  service = authenticate_gmail(request.sender_email)
1009
  if service:
1010
+ formatted_html = format_email_html(
1011
+ generated_message,
1012
+ sender_name=request.recruiter_name,
1013
+ sender_org=request.organisation
1014
+ )
1015
  email_message = create_email_message(
1016
+ sender=request.sender_email,
1017
+ to=request.recipient_email,
1018
+ subject=email_subject,
1019
+ message_text=formatted_html,
1020
+ reply_to=request.reply_to_email,
1021
+ is_html=True
1022
+ )
1023
  email_sent = send_gmail_message(service, "me", email_message)
1024
  if not email_sent:
1025
  return MessageResponse(