Files
GNX-mailEnterprise/emails/tasks.py
Iliyan Angelov c67067a2a4 Mail
2025-09-14 23:24:25 +03:00

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