Files
GNX-WEB/backEnd/contact/email_service.py
Iliyan Angelov 366f28677a update
2025-11-24 03:52:08 +02:00

314 lines
9.8 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,
}
# 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