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