|
import asyncio
|
|
import aiosmtplib
|
|
from email.mime.text import MIMEText
|
|
from email.mime.multipart import MIMEMultipart
|
|
from typing import Optional
|
|
from .settings import settings
|
|
|
|
|
|
class EmailTask:
|
|
def __init__(
|
|
self,
|
|
to_email: str,
|
|
subject: str,
|
|
body: str,
|
|
html: Optional[str] = None,
|
|
**kwargs,
|
|
):
|
|
self.to_email = to_email
|
|
self.subject = subject
|
|
self.body = body
|
|
self.html = html
|
|
self.kwargs = kwargs
|
|
|
|
|
|
class EmailService:
|
|
def __init__(self):
|
|
self.queue = asyncio.Queue()
|
|
self.worker_task: Optional[asyncio.Task] = None
|
|
self.running = False
|
|
|
|
async def start(self):
|
|
"""Start the email worker"""
|
|
if self.running:
|
|
return
|
|
self.running = True
|
|
self.worker_task = asyncio.create_task(self._worker())
|
|
|
|
async def stop(self):
|
|
"""Stop the email worker"""
|
|
if not self.running:
|
|
return
|
|
self.running = False
|
|
if self.worker_task:
|
|
self.worker_task.cancel()
|
|
try:
|
|
await self.worker_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
async def send_email(
|
|
self,
|
|
to_email: str,
|
|
subject: str,
|
|
body: str,
|
|
html: Optional[str] = None,
|
|
**kwargs,
|
|
):
|
|
"""Queue an email for sending"""
|
|
task = EmailTask(to_email, subject, body, html, **kwargs)
|
|
await self.queue.put(task)
|
|
|
|
async def _worker(self):
|
|
"""Email worker coroutine"""
|
|
while self.running:
|
|
try:
|
|
# Wait for a task with timeout to allow checking running flag
|
|
task = await asyncio.wait_for(self.queue.get(), timeout=1.0)
|
|
await self._send_email_task(task)
|
|
self.queue.task_done()
|
|
except asyncio.TimeoutError:
|
|
continue
|
|
except Exception as e:
|
|
# Log error, but continue processing
|
|
print(f"Email worker error: {e}")
|
|
continue
|
|
|
|
async def _send_email_task(self, task: EmailTask):
|
|
"""Send a single email task"""
|
|
if (
|
|
not settings.SMTP_HOST
|
|
or not settings.SMTP_USERNAME
|
|
or not settings.SMTP_PASSWORD
|
|
):
|
|
print("SMTP not configured, skipping email send")
|
|
return
|
|
|
|
msg = MIMEMultipart("alternative")
|
|
msg["From"] = settings.SMTP_SENDER_EMAIL
|
|
msg["To"] = task.to_email
|
|
msg["Subject"] = task.subject
|
|
|
|
# Add text part
|
|
text_part = MIMEText(task.body, "plain")
|
|
msg.attach(text_part)
|
|
|
|
# Add HTML part if provided
|
|
if task.html:
|
|
html_part = MIMEText(task.html, "html")
|
|
msg.attach(html_part)
|
|
|
|
try:
|
|
# Use implicit TLS for port 465 or when SMTP_USE_TLS is True
|
|
if settings.SMTP_PORT == 465 or settings.SMTP_USE_TLS:
|
|
async with aiosmtplib.SMTP(
|
|
hostname=settings.SMTP_HOST,
|
|
port=settings.SMTP_PORT,
|
|
username=settings.SMTP_USERNAME,
|
|
password=settings.SMTP_PASSWORD,
|
|
use_tls=True,
|
|
) as smtp:
|
|
await smtp.send_message(msg)
|
|
print(f"Email sent to {task.to_email}")
|
|
else:
|
|
# Use STARTTLS for other ports
|
|
async with aiosmtplib.SMTP(
|
|
hostname=settings.SMTP_HOST,
|
|
port=settings.SMTP_PORT,
|
|
) as smtp:
|
|
try:
|
|
await smtp.starttls()
|
|
except Exception as tls_error:
|
|
if (
|
|
"already using" in str(tls_error).lower()
|
|
or "tls" in str(tls_error).lower()
|
|
):
|
|
# Connection is already using TLS, proceed without starttls
|
|
pass
|
|
else:
|
|
raise
|
|
await smtp.login(settings.SMTP_USERNAME, settings.SMTP_PASSWORD)
|
|
await smtp.send_message(msg)
|
|
print(f"Email sent to {task.to_email}")
|
|
except Exception as e:
|
|
print(f"Failed to send email to {task.to_email}: {e}")
|
|
raise # Re-raise to let caller handle
|
|
|
|
|
|
# Global email service instance
|
|
email_service = EmailService()
|
|
|
|
|
|
# Convenience functions
|
|
async def send_email(
|
|
to_email: str, subject: str, body: str, html: Optional[str] = None, **kwargs
|
|
):
|
|
"""Send an email asynchronously"""
|
|
await email_service.send_email(to_email, subject, body, html, **kwargs)
|
|
|
|
|
|
def queue_email(
|
|
to_email: str, subject: str, body: str, html: Optional[str] = None, **kwargs
|
|
):
|
|
"""Queue an email for sending (fire and forget)"""
|
|
asyncio.create_task(
|
|
email_service.send_email(to_email, subject, body, html, **kwargs)
|
|
)
|