307 lines
9.6 KiB
Python
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
|