""" 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, 'logo_url': f'{settings.SITE_URL}/images/logo.png', 'site_url': settings.SITE_URL, } # 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, 'logo_url': f'{settings.SITE_URL}/images/logo.png', 'site_url': settings.SITE_URL, } # Create confirmation email subject = "Thank you for contacting GNX Software Solutions" # Render HTML email template html_content = render_to_string( 'contact/contact_submission_confirmation.html', context ) # Render text email template text_content = render_to_string( 'contact/contact_submission_confirmation.txt', context ) # Create email message email = EmailMultiAlternatives( subject=subject, body=text_content, from_email=settings.DEFAULT_FROM_EMAIL, to=[submission.email] ) # 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 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