update
This commit is contained in:
313
backEnd/contact/email_service.py
Normal file
313
backEnd/contact/email_service.py
Normal file
@@ -0,0 +1,313 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user