""" Background email tasks for Django-Q integration. """ import logging from typing import Dict, Any from django_q.tasks import async_task from .services.email_service import UnifiedEmailService from .dto.email_dto import EmailConfig, BulkEmailConfig, EmailTemplate, EmailResult from .email_templates import EmailTemplates logger = logging.getLogger(__name__) def send_email_task(email_config_dict: Dict[str, Any]) -> Dict[str, Any]: """ Background task for sending individual emails. Args: email_config_dict: Dictionary representation of EmailConfig Returns: Dict with task result """ try: # Reconstruct EmailConfig from dictionary config = EmailConfig( to_email=email_config_dict["to_email"], subject=email_config_dict["subject"], template_name=email_config_dict.get("template_name"), context=email_config_dict.get("context", {}), html_content=email_config_dict.get("html_content"), attachments=email_config_dict.get("attachments", []), priority=EmailPriority(email_config_dict.get("priority", "normal")), cc_emails=email_config_dict.get("cc_emails", []), bcc_emails=email_config_dict.get("bcc_emails", []), reply_to=email_config_dict.get("reply_to"), ) # Add sender and job objects if IDs provided if email_config_dict.get("sender_id"): from django.contrib.auth import get_user_model User = get_user_model() try: config.sender = User.objects.get(id=email_config_dict["sender_id"]) except User.DoesNotExist: logger.warning( f"Sender user {email_config_dict['sender_id']} not found" ) if email_config_dict.get("job_id"): from .models import JobPosting try: config.job = JobPosting.objects.get(id=email_config_dict["job_id"]) except JobPosting.DoesNotExist: logger.warning(f"Job {email_config_dict['job_id']} not found") # Send email using unified service service = UnifiedEmailService() result = service.send_email(config) return { "success": result.success, "message": result.message, "recipient_count": result.recipient_count, "error_details": result.error_details, } except Exception as e: error_msg = f"Background email task failed: {str(e)}" logger.error(error_msg, exc_info=True) return {"success": False, "message": error_msg, "error_details": str(e)} def send_bulk_email_task(*args, **kwargs) -> Dict[str, Any]: """ Background task for sending bulk emails. Supports both old parameter format and new BulkEmailConfig format for backward compatibility. Args: *args: Variable positional arguments (old format) **kwargs: Variable keyword arguments (old format) Returns: Dict with task result """ try: config = None # Handle both old format and new BulkEmailConfig format if len(args) == 1 and isinstance(args[0], dict): # New format: BulkEmailConfig dictionary bulk_config_dict = args[0] config = BulkEmailConfig( subject=bulk_config_dict["subject"], template_name=bulk_config_dict.get("template_name"), recipients_data=bulk_config_dict["recipients_data"], attachments=bulk_config_dict.get("attachments", []), priority=EmailPriority(bulk_config_dict.get("priority", "normal")), async_send=False, # Force sync processing in background ) # Add sender and job objects if IDs provided if bulk_config_dict.get("sender_id"): from django.contrib.auth import get_user_model User = get_user_model() try: config.sender = User.objects.get(id=bulk_config_dict["sender_id"]) except User.DoesNotExist: logger.warning( f"Sender user {bulk_config_dict['sender_id']} not found" ) if bulk_config_dict.get("job_id"): from .models import JobPosting try: config.job = JobPosting.objects.get(id=bulk_config_dict["job_id"]) except JobPosting.DoesNotExist: logger.warning(f"Job {bulk_config_dict['job_id']} not found") else: # Old format: individual parameters subject = kwargs.get("subject") customized_sends = kwargs.get("customized_sends", []) attachments = kwargs.get("attachments") sender_user_id = kwargs.get("sender_user_id") job_id = kwargs.get("job_id") if not subject or not customized_sends: return {"success": False, "message": "Missing required parameters"} # Convert old format to BulkEmailConfig recipients_data = [] for send_data in customized_sends: if isinstance(send_data, dict): recipients_data.append( { "email": send_data.get("email"), "name": send_data.get( "name", send_data.get("email", "").split("@")[0] if "@" in send_data.get("email", "") else send_data.get("email", ""), ), "personalization": send_data.get("personalization", {}), } ) else: # Handle legacy format where customized_sends might be list of emails recipients_data.append( { "email": send_data, "name": send_data.split("@")[0] if "@" in send_data else send_data, } ) config = BulkEmailConfig( subject=subject, recipients_data=recipients_data, attachments=attachments or [], priority=EmailPriority.NORMAL, async_send=False, # Force sync processing in background ) # Handle old format with sender_user_id and job_id if sender_user_id: from django.contrib.auth import get_user_model User = get_user_model() try: config.sender = User.objects.get(id=sender_user_id) except User.DoesNotExist: logger.warning(f"Sender user {sender_user_id} not found") if job_id: from .models import JobPosting try: config.job = JobPosting.objects.get(id=job_id) except JobPosting.DoesNotExist: logger.warning(f"Job {job_id} not found") # Send bulk emails using unified service service = UnifiedEmailService() result = service.send_bulk_emails(config) return { "success": result.success, "message": result.message, "recipient_count": result.recipient_count, "error_details": result.error_details, } except Exception as e: error_msg = f"Background bulk email task failed: {str(e)}" logger.error(error_msg, exc_info=True) return {"success": False, "message": error_msg, "error_details": str(e)} def send_interview_email_task(interview_data: Dict[str, Any]) -> Dict[str, Any]: """ Background task specifically for interview invitation emails. Args: interview_data: Dictionary with interview details Returns: Dict with task result """ try: from .models import ScheduledInterview from .dto.email_dto import EmailConfig, EmailTemplate, EmailPriority # Get interview object interview_id = interview_data.get("interview_id") if not interview_id: raise ValueError("interview_id is required") try: interview = ScheduledInterview.objects.get(id=interview_id) except ScheduledInterview.DoesNotExist: raise ValueError(f"Interview {interview_id} not found") # Build email configuration service = UnifiedEmailService() context = service.template_manager.build_interview_context( interview.candidate, interview.job, { "topic": f"Interview for {interview.job.title}", "date_time": interview.interview_date, "duration": "60 minutes", "join_url": interview.zoom_meeting.join_url if interview.zoom_meeting else "", "meeting_id": interview.zoom_meeting.meeting_id if interview.zoom_meeting else "", }, ) config = EmailConfig( to_email=interview.candidate.email, subject=service.template_manager.get_subject_line( EmailTemplate.INTERVIEW_INVITATION_ALT, context ), template_name=EmailTemplate.INTERVIEW_INVITATION_ALT.value, context=context, priority=EmailPriority.HIGH, ) # Send email result = service.send_email(config) return { "success": result.success, "message": result.message, "recipient_count": result.recipient_count, "error_details": result.error_details, "interview_id": interview_id, } except Exception as e: error_msg = f"Interview email task failed: {str(e)}" logger.error(error_msg, exc_info=True) return { "success": False, "message": error_msg, "error_details": str(e), "interview_id": interview_data.get("interview_id"), } def email_success_hook(task): """ Success hook for email tasks. Args: task: Django-Q task object """ if task.success: logger.info(f"Email task {task.id} completed successfully: {task.result}") else: logger.error(f"Email task {task.id} failed: {task.result}") def email_failure_hook(task): """ Failure hook for email tasks. Args: task: Django-Q task object """ logger.error(f"Email task {task.id} failed after retries: {task.result}") # Additional failure handling can be added here # e.g., send notification to admin, log to external system, etc.