kaauh_ats/recruitment/email_service.py

388 lines
14 KiB
Python

"""
Email service for sending notifications related to agency messaging.
"""
from django.core.mail import send_mail, EmailMultiAlternatives
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.html import strip_tags
import logging
logger = logging.getLogger(__name__)
class EmailService:
"""
Service class for handling email notifications
"""
def send_email(self, recipient_email, subject, body, html_body=None):
"""
Send email using Django's send_mail function
Args:
recipient_email: Email address to send to
subject: Email subject
body: Plain text email body
html_body: HTML email body (optional)
Returns:
dict: Result with success status and error message if failed
"""
try:
send_mail(
subject=subject,
message=body,
from_email=getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa'),
recipient_list=[recipient_email],
html_message=html_body,
fail_silently=False,
)
logger.info(f"Email sent successfully to {recipient_email}")
return {'success': True}
except Exception as e:
error_msg = f"Failed to send email to {recipient_email}: {str(e)}"
logger.error(error_msg)
return {'success': False, 'error': error_msg}
def send_agency_welcome_email(agency, access_link=None):
"""
Send welcome email to a new agency with portal access information.
Args:
agency: HiringAgency instance
access_link: AgencyAccessLink instance (optional)
Returns:
bool: True if email was sent successfully, False otherwise
"""
try:
if not agency.email:
logger.warning(f"No email found for agency {agency.id}")
return False
context = {
'agency': agency,
'access_link': access_link,
'portal_url': getattr(settings, 'AGENCY_PORTAL_URL', 'https://kaauh.edu.sa/portal/'),
}
# Render email templates
html_message = render_to_string('recruitment/emails/agency_welcome.html', context)
plain_message = strip_tags(html_message)
# Send email
send_mail(
subject='Welcome to KAAUH Recruitment Portal',
message=plain_message,
from_email=getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa'),
recipient_list=[agency.email],
html_message=html_message,
fail_silently=False,
)
logger.info(f"Welcome email sent to agency {agency.email}")
return True
except Exception as e:
logger.error(f"Failed to send agency welcome email: {str(e)}")
return False
def send_assignment_notification_email(assignment, message_type='created'):
"""
Send email notification about assignment changes.
Args:
assignment: AgencyJobAssignment instance
message_type: Type of notification ('created', 'updated', 'deadline_extended')
Returns:
bool: True if email was sent successfully, False otherwise
"""
try:
if not assignment.agency.email:
logger.warning(f"No email found for agency {assignment.agency.id}")
return False
context = {
'assignment': assignment,
'agency': assignment.agency,
'job': assignment.job,
'message_type': message_type,
'portal_url': getattr(settings, 'AGENCY_PORTAL_URL', 'https://kaauh.edu.sa/portal/'),
}
# Render email templates
html_message = render_to_string('recruitment/emails/assignment_notification.html', context)
plain_message = strip_tags(html_message)
# Determine subject based on message type
subjects = {
'created': f'New Job Assignment: {assignment.job.title}',
'updated': f'Assignment Updated: {assignment.job.title}',
'deadline_extended': f'Deadline Extended: {assignment.job.title}',
}
subject = subjects.get(message_type, f'Assignment Notification: {assignment.job.title}')
# Send email
send_mail(
subject=subject,
message=plain_message,
from_email=getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa'),
recipient_list=[assignment.agency.email],
html_message=html_message,
fail_silently=False,
)
logger.info(f"Assignment notification email sent to {assignment.agency.email} for {message_type}")
return True
except Exception as e:
logger.error(f"Failed to send assignment notification email: {str(e)}")
return False
def send_interview_invitation_email(candidate, job, meeting_details=None, recipient_list=None):
"""
Send interview invitation email using HTML template.
Args:
candidate: Candidate instance
job: Job instance
meeting_details: Dictionary with meeting information (optional)
recipient_list: List of additional email addresses (optional)
Returns:
dict: Result with success status and error message if failed
"""
try:
# Prepare recipient list
recipients = []
if candidate.hiring_source == "Agency":
try:
recipients.append(candidate.hiring_agency.email)
except :
pass
else:
recipients.append(candidate.email)
if recipient_list:
recipients.extend(recipient_list)
if not recipients:
return {'success': False, 'error': 'No recipient email addresses provided'}
# Prepare context for template
context = {
'candidate_name': candidate.full_name or candidate.name,
'candidate_email': candidate.email,
'candidate_phone': candidate.phone or '',
'job_title': job.title,
'department': getattr(job, 'department', ''),
'company_name': getattr(settings, 'COMPANY_NAME', 'Norah University'),
}
# Add meeting details if provided
if meeting_details:
context.update({
'meeting_topic': meeting_details.get('topic', f'Interview for {job.title}'),
'meeting_date_time': meeting_details.get('date_time', ''),
'meeting_duration': meeting_details.get('duration', '60 minutes'),
'join_url': meeting_details.get('join_url', ''),
})
# Render HTML template
html_message = render_to_string('emails/interview_invitation.html', context)
plain_message = strip_tags(html_message)
# Create email with both HTML and plain text versions
email = EmailMultiAlternatives(
subject=f'Interview Invitation: {job.title}',
body=plain_message,
from_email=getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa'),
to=recipients,
)
email.attach_alternative(html_message, "text/html")
# Send email
email.send(fail_silently=False)
logger.info(f"Interview invitation email sent successfully to {', '.join(recipients)}")
return {
'success': True,
'recipients_count': len(recipients),
'message': f'Interview invitation sent successfully to {len(recipients)} recipient(s)'
}
except Exception as e:
error_msg = f"Failed to send interview invitation email: {str(e)}"
logger.error(error_msg, exc_info=True)
return {'success': False, 'error': error_msg}
from .models import Candidate
def send_bulk_email(subject, message, recipient_list, request=None, attachments=None, async_task_=False):
"""
Send bulk email to multiple recipients with HTML support and attachments,
supporting synchronous or asynchronous dispatch.
"""
# Define messages (Placeholders)
all_candidate_emails = []
candidate_through_agency_emails=[]
participant_emails = []
agency_emails = []
left_candidate_emails = []
if not recipient_list:
return {'success': False, 'error': 'No recipients provided'}
for email in recipient_list:
email = email.strip().lower() # Clean input email
if email:
candidate = Candidate.objects.filter(email=email).first()
if candidate:
all_candidate_emails.append(email)
else:
participant_emails.append(email)
for email in all_candidate_emails:
candidate = Candidate.objects.filter(email=email).first()
if candidate:
if candidate.hiring_source == 'Agency' and hasattr(candidate, 'hiring_agency') and candidate.hiring_agency:
agency = candidate.hiring_agency
candidate_through_agency_emails.append(email)
if agency and agency.email:
agency_emails.append(agency.email)
else:
left_candidate_emails.append(email)
else:
left_candidate_emails.append(email)
# Determine unique recipients
unique_left_candidates = list(set(left_candidate_emails)) # Convert to list for async task
unique_agencies = list(agency_emails)
unique_participants = list(set(participant_emails))
total_recipients = len(unique_left_candidates) + len(unique_agencies) + len(unique_participants)
if total_recipients == 0:
return {'success': False, 'error': 'No valid email addresses found after categorization'}
# --- 3. Handle ASYNC Dispatch ---
if async_task_:
try:
from django_q.tasks import async_task
# Simple, serializable attachment format assumed: list of (filename, content, content_type) tuples
processed_attachments = attachments if attachments else []
task_ids = []
# Queue Left Candidates
if unique_left_candidates:
task_id = async_task(
'recruitment.tasks.send_bulk_email_task',
subject,
message,
recipient_list,
processed_attachments, # Pass serializable data
hook='recruitment.tasks.email_success_hook' # Example hook
)
task_ids.append(task_id)
logger.info(f" email queued. ID: {task_id}")
return {
'success': True,
'async': True,
'task_ids': task_ids,
'message': f'Emails queued for background sending to {total_recipients} recipient(s)'
}
except ImportError:
logger.error("Async execution requested, but django_q or required modules not found. Defaulting to sync.")
async_task_ = False # Fallback to sync
except Exception as e:
logger.error(f"Failed to queue async tasks: {str(e)}", exc_info=True)
return {'success': False, 'error': f"Failed to queue async tasks: {str(e)}"}
# --- 4. Handle SYNCHRONOUS Send (If async_task_=False or fallback) ---
try:
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
is_html = '<' in message and '>' in message
successful_sends = 0
# Helper Function for Sync Send
def send_individual_email(recipient, body_message):
nonlocal successful_sends
if is_html:
plain_message = strip_tags(body_message)
email_obj = EmailMultiAlternatives(subject=subject, body=plain_message, from_email=from_email, to=[recipient])
email_obj.attach_alternative(body_message, "text/html")
else:
email_obj = EmailMultiAlternatives(subject=subject, body=body_message, from_email=from_email, to=[recipient])
# Attachment Logic
if attachments:
for attachment in attachments:
if hasattr(attachment, 'read'):
filename = getattr(attachment, 'name', 'attachment')
content = attachment.read()
content_type = getattr(attachment, 'content_type', 'application/octet-stream')
email_obj.attach(filename, content, content_type)
elif isinstance(attachment, tuple) and len(attachment) == 3:
filename, content, content_type = attachment
email_obj.attach(filename, content, content_type)
try:
# FIX: Added the critical .send() call
email_obj.send(fail_silently=False)
successful_sends += 1
except Exception as e:
logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True)
# Send Emails
for email in unique_left_candidates:
candidate_name=Candidate.objects.filter(email=email).first().first_name
candidate_message = f"Hi, {candidate_name}"+"\n"+message
send_individual_email(email, candidate_message)
i=0
for email in unique_agencies:
candidate_name=Candidate.objects.filter(email=candidate_through_agency_emails[i]).first().first_name
agency_message = f"Hi, {candidate_name}"+"\n"+message
send_individual_email(email, agency_message)
for email in unique_participants:
participant_message = "Hello Participant! This is a general notification for you."
send_individual_email(email, participant_message)
logger.info(f"Bulk email processing complete. Sent successfully to {successful_sends} out of {total_recipients} unique recipients.")
return {
'success': True,
'recipients_count': successful_sends,
'message': f'Email processing complete. {successful_sends} email(s) were sent successfully to {total_recipients} unique intended recipients.'
}
except Exception as e:
error_msg = f"Failed to process bulk email send request: {str(e)}"
logger.error(error_msg, exc_info=True)
return {'success': False, 'error': error_msg}