300 lines
12 KiB
Python
300 lines
12 KiB
Python
from celery import shared_task
|
|
from django.core.mail import EmailMultiAlternatives
|
|
from django.template.loader import render_to_string
|
|
from django.conf import settings
|
|
from django.utils import timezone
|
|
import smtplib
|
|
import imaplib
|
|
import email
|
|
from email.mime.text import MIMEText
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.mime.base import MIMEBase
|
|
from email import encoders
|
|
import logging
|
|
from .models import Email, EmailFolder, EmailAttachment
|
|
from accounts.models import User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@shared_task
|
|
def send_email_task(email_id):
|
|
"""Send email asynchronously."""
|
|
try:
|
|
email_obj = Email.objects.get(id=email_id)
|
|
user = email_obj.user
|
|
|
|
# Create email message
|
|
msg = MIMEMultipart('alternative')
|
|
msg['From'] = email_obj.from_email
|
|
msg['To'] = ', '.join(email_obj.to_emails)
|
|
msg['Subject'] = email_obj.subject
|
|
|
|
if email_obj.cc_emails:
|
|
msg['Cc'] = ', '.join(email_obj.cc_emails)
|
|
|
|
if email_obj.reply_to:
|
|
msg['Reply-To'] = email_obj.reply_to
|
|
|
|
if email_obj.in_reply_to:
|
|
msg['In-Reply-To'] = email_obj.in_reply_to
|
|
|
|
if email_obj.references:
|
|
msg['References'] = email_obj.references
|
|
|
|
# Add body
|
|
if email_obj.body_text:
|
|
text_part = MIMEText(email_obj.body_text, 'plain')
|
|
msg.attach(text_part)
|
|
|
|
if email_obj.body_html:
|
|
html_part = MIMEText(email_obj.body_html, 'html')
|
|
msg.attach(html_part)
|
|
|
|
# Add attachments
|
|
for attachment in email_obj.attachments.all():
|
|
with open(attachment.file.path, 'rb') as f:
|
|
part = MIMEBase('application', 'octet-stream')
|
|
part.set_payload(f.read())
|
|
encoders.encode_base64(part)
|
|
part.add_header(
|
|
'Content-Disposition',
|
|
f'attachment; filename= {attachment.filename}'
|
|
)
|
|
msg.attach(part)
|
|
|
|
# Send email
|
|
if user.smtp_host and user.smtp_username:
|
|
# Use user's SMTP settings
|
|
server = smtplib.SMTP(user.smtp_host, user.smtp_port)
|
|
if user.smtp_use_tls:
|
|
server.starttls()
|
|
server.login(user.smtp_username, user.get_smtp_password())
|
|
|
|
recipients = email_obj.to_emails + email_obj.cc_emails + email_obj.bcc_emails
|
|
server.send_message(msg, to_addrs=recipients)
|
|
server.quit()
|
|
else:
|
|
# Use Django's email backend
|
|
django_email = EmailMultiAlternatives(
|
|
subject=email_obj.subject,
|
|
body=email_obj.body_text,
|
|
from_email=email_obj.from_email,
|
|
to=email_obj.to_emails,
|
|
cc=email_obj.cc_emails,
|
|
bcc=email_obj.bcc_emails,
|
|
reply_to=[email_obj.reply_to] if email_obj.reply_to else None,
|
|
)
|
|
|
|
if email_obj.body_html:
|
|
django_email.attach_alternative(email_obj.body_html, "text/html")
|
|
|
|
# Add attachments
|
|
for attachment in email_obj.attachments.all():
|
|
django_email.attach_file(attachment.file.path)
|
|
|
|
django_email.send()
|
|
|
|
# Update email status
|
|
email_obj.status = 'sent'
|
|
email_obj.sent_at = timezone.now()
|
|
email_obj.save()
|
|
|
|
logger.info(f"Email {email_id} sent successfully")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to send email {email_id}: {str(e)}")
|
|
# Update email status to failed
|
|
try:
|
|
email_obj = Email.objects.get(id=email_id)
|
|
email_obj.status = 'failed'
|
|
email_obj.save()
|
|
except Email.DoesNotExist:
|
|
pass
|
|
|
|
|
|
@shared_task
|
|
def fetch_emails_task(user_id):
|
|
"""Fetch emails from IMAP server asynchronously."""
|
|
try:
|
|
user = User.objects.get(id=user_id)
|
|
|
|
if not user.imap_host or not user.imap_username:
|
|
logger.error(f"IMAP settings not configured for user {user_id}")
|
|
return
|
|
|
|
# Connect to IMAP server
|
|
if user.imap_use_ssl:
|
|
server = imaplib.IMAP4_SSL(user.imap_host, user.imap_port)
|
|
else:
|
|
server = imaplib.IMAP4(user.imap_host, user.imap_port)
|
|
|
|
server.login(user.imap_username, user.get_imap_password())
|
|
|
|
# Select inbox
|
|
server.select('INBOX')
|
|
|
|
# Search for unseen emails
|
|
status, messages = server.search(None, 'UNSEEN')
|
|
|
|
if status == 'OK':
|
|
email_ids = messages[0].split()
|
|
|
|
# Get or create inbox folder
|
|
inbox_folder, created = EmailFolder.objects.get_or_create(
|
|
user=user,
|
|
folder_type='inbox',
|
|
defaults={'name': 'Inbox', 'is_system': True}
|
|
)
|
|
|
|
for email_id in email_ids:
|
|
try:
|
|
# Fetch email
|
|
status, msg_data = server.fetch(email_id, '(RFC822)')
|
|
|
|
if status == 'OK':
|
|
raw_email = msg_data[0][1]
|
|
email_message = email.message_from_bytes(raw_email)
|
|
|
|
# Parse email
|
|
subject = email_message.get('Subject', '')
|
|
from_email = email_message.get('From', '')
|
|
to_emails = email_message.get('To', '').split(',')
|
|
cc_emails = email_message.get('Cc', '').split(',') if email_message.get('Cc') else []
|
|
message_id = email_message.get('Message-ID', '')
|
|
in_reply_to = email_message.get('In-Reply-To', '')
|
|
references = email_message.get('References', '')
|
|
|
|
# Get email body
|
|
body_text = ''
|
|
body_html = ''
|
|
|
|
if email_message.is_multipart():
|
|
for part in email_message.walk():
|
|
content_type = part.get_content_type()
|
|
content_disposition = str(part.get('Content-Disposition'))
|
|
|
|
if content_type == 'text/plain' and 'attachment' not in content_disposition:
|
|
body_text = part.get_payload(decode=True).decode()
|
|
elif content_type == 'text/html' and 'attachment' not in content_disposition:
|
|
body_html = part.get_payload(decode=True).decode()
|
|
else:
|
|
content_type = email_message.get_content_type()
|
|
if content_type == 'text/plain':
|
|
body_text = email_message.get_payload(decode=True).decode()
|
|
elif content_type == 'text/html':
|
|
body_html = email_message.get_payload(decode=True).decode()
|
|
|
|
# Create email object
|
|
email_obj = Email.objects.create(
|
|
user=user,
|
|
folder=inbox_folder,
|
|
subject=subject,
|
|
from_email=from_email,
|
|
to_emails=[email.strip() for email in to_emails if email.strip()],
|
|
cc_emails=[email.strip() for email in cc_emails if email.strip()],
|
|
body_text=body_text,
|
|
body_html=body_html,
|
|
message_id=message_id,
|
|
in_reply_to=in_reply_to,
|
|
references=references,
|
|
status='received',
|
|
received_at=timezone.now(),
|
|
size=len(raw_email)
|
|
)
|
|
|
|
# Process attachments
|
|
if email_message.is_multipart():
|
|
for part in email_message.walk():
|
|
content_disposition = str(part.get('Content-Disposition'))
|
|
if 'attachment' in content_disposition:
|
|
filename = part.get_filename()
|
|
if filename:
|
|
# Save attachment
|
|
attachment = EmailAttachment.objects.create(
|
|
email=email_obj,
|
|
filename=filename,
|
|
content_type=part.get_content_type(),
|
|
size=len(part.get_payload(decode=True)),
|
|
file=part.get_payload(decode=True)
|
|
)
|
|
|
|
logger.info(f"Fetched email {email_obj.id} for user {user_id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing email {email_id}: {str(e)}")
|
|
continue
|
|
|
|
server.logout()
|
|
logger.info(f"Email fetch completed for user {user_id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to fetch emails for user {user_id}: {str(e)}")
|
|
|
|
|
|
@shared_task
|
|
def cleanup_old_emails():
|
|
"""Clean up old emails from trash folder."""
|
|
try:
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
|
|
# Delete emails in trash folder older than 30 days
|
|
cutoff_date = timezone.now() - timedelta(days=30)
|
|
trash_emails = Email.objects.filter(
|
|
folder__folder_type='trash',
|
|
created_at__lt=cutoff_date
|
|
)
|
|
|
|
count = trash_emails.count()
|
|
trash_emails.delete()
|
|
|
|
logger.info(f"Cleaned up {count} old emails from trash")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to cleanup old emails: {str(e)}")
|
|
|
|
|
|
@shared_task
|
|
def process_email_rules():
|
|
"""Process email rules for all users."""
|
|
try:
|
|
from .models import EmailRule
|
|
|
|
rules = EmailRule.objects.filter(is_active=True)
|
|
|
|
for rule in rules:
|
|
try:
|
|
# Apply rule to matching emails
|
|
emails = Email.objects.filter(user=rule.user)
|
|
|
|
# Apply condition
|
|
if rule.condition_field == 'from':
|
|
emails = emails.filter(from_email__icontains=rule.condition_value)
|
|
elif rule.condition_field == 'to':
|
|
emails = emails.filter(to_emails__icontains=rule.condition_value)
|
|
elif rule.condition_field == 'subject':
|
|
emails = emails.filter(subject__icontains=rule.condition_value)
|
|
elif rule.condition_field == 'body':
|
|
emails = emails.filter(body_text__icontains=rule.condition_value)
|
|
|
|
# Apply action
|
|
if rule.action == 'move':
|
|
folder = EmailFolder.objects.get(user=rule.user, name=rule.action_value)
|
|
emails.update(folder=folder)
|
|
elif rule.action == 'mark_read':
|
|
emails.update(is_read=True)
|
|
elif rule.action == 'mark_important':
|
|
emails.update(is_important=True)
|
|
elif rule.action == 'delete':
|
|
emails.delete()
|
|
|
|
logger.info(f"Processed rule {rule.id} for user {rule.user.id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing rule {rule.id}: {str(e)}")
|
|
continue
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to process email rules: {str(e)}")
|