""" Email service for contact form notifications. Production-ready with retry logic and comprehensive error handling. """ from django.core.mail import EmailMultiAlternatives, get_connection from django.template.loader import render_to_string from django.conf import settings from django.utils.html import strip_tags from django.core.mail.backends.smtp import EmailBackend import logging import time from typing import Optional, List logger = logging.getLogger(__name__) def _send_email_with_retry(email_message, max_retries: int = 3, delay: float = 1.0) -> bool: """ Send email with retry logic for production reliability. Args: email_message: EmailMultiAlternatives instance max_retries: Maximum number of retry attempts delay: Delay between retries in seconds Returns: bool: True if email was sent successfully, False otherwise """ for attempt in range(max_retries + 1): try: # Test connection before sending connection = get_connection() connection.open() connection.close() # Send the email email_message.send() return True except Exception as e: logger.warning(f"Email send attempt {attempt + 1} failed: {str(e)}") if attempt < max_retries: time.sleep(delay * (2 ** attempt)) # Exponential backoff continue else: logger.error(f"Failed to send email after {max_retries + 1} attempts: {str(e)}") return False return False def _create_email_connection() -> Optional[EmailBackend]: """ Create a robust email connection with production settings. Returns: EmailBackend instance or None if connection fails """ try: connection = get_connection( host=settings.EMAIL_HOST, port=settings.EMAIL_PORT, username=settings.EMAIL_HOST_USER, password=settings.EMAIL_HOST_PASSWORD, use_tls=settings.EMAIL_USE_TLS, use_ssl=settings.EMAIL_USE_SSL, timeout=getattr(settings, 'EMAIL_TIMEOUT', 30), connection_timeout=getattr(settings, 'EMAIL_CONNECTION_TIMEOUT', 10), read_timeout=getattr(settings, 'EMAIL_READ_TIMEOUT', 10), ) # Test the connection connection.open() connection.close() return connection except Exception as e: logger.error(f"Failed to create email connection: {str(e)}") return None def send_contact_submission_notification(submission): """ Send email notification for new contact form submission. Args: submission: ContactSubmission instance Returns: bool: True if email was sent successfully, False otherwise """ try: # Get company email from settings company_email = getattr(settings, 'COMPANY_EMAIL', None) if not company_email: logger.warning("COMPANY_EMAIL not configured in settings") return False # Prepare email context context = { 'submission': submission, } # Render email templates html_content = render_to_string( 'contact/contact_submission_email.html', context ) text_content = render_to_string( 'contact/contact_submission_email.txt', context ) # Create email subject with priority indicator priority_emoji = { 'urgent': '🚨', 'high': '⚠️', 'medium': '📋', 'low': '📝' }.get(submission.priority, '📋') subject = f"{priority_emoji} New Contact Form Submission - {submission.company} (#{submission.id})" # Create email message email = EmailMultiAlternatives( subject=subject, body=text_content, from_email=settings.DEFAULT_FROM_EMAIL, to=[company_email], reply_to=[submission.email] # Allow direct reply to customer ) # Add headers for better email handling email.extra_headers = { 'X-Priority': '1' if submission.priority in ['urgent', 'high'] else '3', 'X-MSMail-Priority': 'High' if submission.priority in ['urgent', 'high'] else 'Normal', } # Attach HTML version email.attach_alternative(html_content, "text/html") # Send email with retry logic success = _send_email_with_retry(email) if success: logger.info(f"Contact submission notification sent for submission #{submission.id}") else: logger.error(f"Failed to send contact submission notification for submission #{submission.id} after retries") return success except Exception as e: logger.error(f"Failed to send contact submission notification for submission #{submission.id}: {str(e)}") return False def send_contact_submission_confirmation(submission): """ Send confirmation email to the customer who submitted the form. Args: submission: ContactSubmission instance Returns: bool: True if email was sent successfully, False otherwise """ try: # Prepare email context context = { 'submission': submission, } # Create simple confirmation email subject = "Thank you for contacting GNX Software Solutions" # Simple text email for confirmation message = f""" Dear {submission.full_name}, Thank you for reaching out to GNX Software Solutions! We have received your inquiry about {submission.get_project_type_display() if submission.project_type else 'your project'} and will review it carefully. Here are the details of your submission: - Submission ID: #{submission.id} - Company: {submission.company} - Project Type: {submission.get_project_type_display() if submission.project_type else 'Not specified'} - Timeline: {submission.get_timeline_display() if submission.timeline else 'Not specified'} Our team will contact you within 24 hours to discuss your project requirements and how we can help you achieve your goals. If you have any urgent questions, please don't hesitate to contact us directly. Best regards, The GNX Team --- GNX Software Solutions Email: {settings.DEFAULT_FROM_EMAIL} """ # Create email message email = EmailMultiAlternatives( subject=subject, body=message, from_email=settings.DEFAULT_FROM_EMAIL, to=[submission.email] ) # Send email with retry logic success = _send_email_with_retry(email) if success: logger.info(f"Contact submission confirmation sent to {submission.email} for submission #{submission.id}") else: logger.error(f"Failed to send contact submission confirmation for submission #{submission.id} after retries") return success except Exception as e: logger.error(f"Failed to send contact submission confirmation for submission #{submission.id}: {str(e)}") return False def check_email_health() -> dict: """ Check email service health for production monitoring. Returns: dict: Health status information """ health_status = { 'email_service': 'unknown', 'connection_test': False, 'configuration_valid': False, 'error_message': None } try: # Check configuration required_settings = [ 'EMAIL_HOST', 'EMAIL_PORT', 'EMAIL_HOST_USER', 'EMAIL_HOST_PASSWORD', 'DEFAULT_FROM_EMAIL', 'COMPANY_EMAIL' ] missing_settings = [] for setting in required_settings: if not getattr(settings, setting, None): missing_settings.append(setting) if missing_settings: health_status['error_message'] = f"Missing email settings: {', '.join(missing_settings)}" return health_status health_status['configuration_valid'] = True # Test connection connection = _create_email_connection() if connection: health_status['connection_test'] = True health_status['email_service'] = 'healthy' else: health_status['email_service'] = 'unhealthy' health_status['error_message'] = 'Failed to establish email connection' except Exception as e: health_status['email_service'] = 'error' health_status['error_message'] = str(e) return health_status def send_test_email(to_email: str) -> bool: """ Send a test email to verify email configuration. Args: to_email: Email address to send test email to Returns: bool: True if test email was sent successfully """ try: subject = "GNX Email Service Test" message = f""" This is a test email from the GNX contact form system. If you receive this email, the email service is working correctly. Timestamp: {time.strftime("%Y-%m-%d %H:%M:%S UTC")} """ email = EmailMultiAlternatives( subject=subject, body=message, from_email=settings.DEFAULT_FROM_EMAIL, to=[to_email] ) success = _send_email_with_retry(email) if success: logger.info(f"Test email sent successfully to {to_email}") else: logger.error(f"Failed to send test email to {to_email}") return success except Exception as e: logger.error(f"Error sending test email to {to_email}: {str(e)}") return False