From 623d452d7d0763b637ce4a32c4ac736e605cdda1b7e01b9c0054c76b8b7a4ed1 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 30 Jun 2026 19:13:50 -0400 Subject: [PATCH] =?UTF-8?q?Bot=20commit:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D0=B0=D0=B9=D0=BB=20email=5Ftask.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- email_task.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 email_task.py diff --git a/email_task.py b/email_task.py new file mode 100644 index 0000000..5318c68 --- /dev/null +++ b/email_task.py @@ -0,0 +1,114 @@ +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from celery import Celery +from config import * +from templates import templates +import logging +from typing import Optional +import asyncio +from email.utils import formatdate, make_msgid + + +def build_unsubscribe_url(email: str, token: str, base_url: str) -> str: + return f"{base_url}/api/v1/email/unsubscribe?token={token}&email={email}" + + +async def create_email_message( + to_email: str, + subject: str, + html_content: str, + user_id: int = None, +): + msg = MIMEMultipart("alternative") + + msg['From'] = f"{FROM_NAME} <{FROM_EMAIL}>" + msg['To'] = to_email + msg['Subject'] = subject + msg['Message-ID'] = make_msgid(domain=FROM_EMAIL.split('@')[1]) + msg['Date'] = formatdate(localtime=True) + msg['X-Priority'] = "3" + msg['Importance'] = "normal" + msg['X-Auto-Response-Suppress'] = "All" + + # token = await create_unsubscribe_token(to_email, user_id) + unsubscribe_url = build_unsubscribe_url(to_email, None, FULL_DOMAIN) + if to_email != FROM_EMAIL: + msg['List-Unsubscribe'] = f"<{unsubscribe_url}>" + msg['List-Unsubscribe-Post'] = "List-Unsubscribe=One-Click" + + msg.attach(MIMEText(html_content, 'html')) + + return msg + +def connect(email_server, email_port): + server = smtplib.SMTP(email_server, email_port) + server.starttls(context=ctx) + return server + + + +def send_email( + to_email: str, + html_content: str, + subject: str, + user_id: Optional[str] = None, +) -> bool: + msg =asyncio.run(create_email_message( + to_email=to_email, + subject=subject, + html_content=html_content, + user_id=user_id, + )) + + + try: + with connect(EMAIL_SMTP_SERVER, EMAIL_SMTP_PORT) as server: + server.login(FROM_EMAIL, EMAIL_PASS) + server.send_message(msg) + # server.close() + + logging.info(f"Email sent successfully to: {to_email} | Subject: {subject}") + return True + + except Exception as e: + logging.error( + f"Error sending email to: {to_email}, " + f"Subject: {subject}, Error: {str(e)}" + ) + return False + + +celery_app_email = Celery('emails', broker=f'amqps://admin:{RABBITMQ_PASS}@{RABBITMQ_HOST}/email', backend=f'redis://{REDIS_HOST}:{6379 if PROD else 30003}/0', task_track_started=True ) + + +@celery_app_email.task( + bind=True, + rate_limit='30/m', + name='email_task.send_email', + retry_backoff=True, + retry_backoff_max=600, + retry_jitter=True, + max_retries=3, + soft_time_limit=30, + time_limit=45, +) +def notify(self, to_email: str, data: dict, subject: str = EMAIL_SUBJECT, template: str = 'main.html'): + logging.info(f"Отправка email на {to_email} (попытка {self.request.retries + 1})") + try: + html_content = templates.get_template(f"email/{template}").render({ + "subject": subject, + "data": data + }) + + ok = send_email(to_email, html_content, subject=subject) + if ok: + return ok + else: + raise self.retry(exc=exc) + + except Exception as exc: + logging.warning( + f"Ошибка при отправке email на {to_email} (попытка {self.request.retries + 1}): {exc}" + ) + raise self.retry(exc=exc) \ No newline at end of file