Files
GNX-WEB/backEnd/contact/email_service.py
Iliyan Angelov 136f75a859 update
2025-11-24 08:18:18 +02:00

307 lines
9.6 KiB
Python

"""
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