Добавлена опция сохранения копии отправленных писем в IMAP Sent
- Добавлены поля IMAP в модель SmtpSettings и миграция колонок - Реализовано сохранение копии MIME-сообщения через imaplib после SMTP/EWS/Outlook отправки - Обновлен routes.py: чтение и сохранение IMAP-настроек из формы - Обновлен шаблон admin/email_settings.html: блок настроек IMAP с подсказками
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import smtplib
|
||||
import time
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.image import MIMEImage
|
||||
@@ -13,26 +14,16 @@ from app.utils.paths import event_qr_folder
|
||||
OUTLOOK_CONTENT_ID_PROP = "http://schemas.microsoft.com/mapi/proptag/0x3712001F"
|
||||
|
||||
|
||||
def _send_smtp_email(to_address, subject, html_body, sender_name, attachments):
|
||||
"""Отправляет письмо через SMTP с inline-вложениями."""
|
||||
def _build_mime_message(to_address, subject, html_body, sender_name, attachments):
|
||||
"""Собирает MIME-сообщение с inline-вложениями."""
|
||||
config = current_app.config
|
||||
|
||||
server = config.get("MAIL_SERVER", "")
|
||||
port = config.get("MAIL_PORT", 465)
|
||||
username = config.get("MAIL_USERNAME", "")
|
||||
password = config.get("MAIL_PASSWORD", "")
|
||||
use_ssl = config.get("MAIL_USE_SSL", True)
|
||||
sender = config.get("MAIL_DEFAULT_SENDER") or username
|
||||
|
||||
if not username or not password:
|
||||
raise ValueError("MAIL_USERNAME и MAIL_PASSWORD должны быть указаны в конфигурации")
|
||||
if not server or not port:
|
||||
raise ValueError("MAIL_SERVER и MAIL_PORT должны быть указаны в конфигурации")
|
||||
sender = config.get("MAIL_DEFAULT_SENDER") or config.get("MAIL_USERNAME", "")
|
||||
|
||||
msg = MIMEMultipart("related")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = formataddr((sender_name, sender))
|
||||
msg["To"] = to_address
|
||||
msg["Date"] = email.utils.formatdate(localtime=True)
|
||||
msg.attach(MIMEText(html_body, "html", "utf-8"))
|
||||
|
||||
for att in attachments:
|
||||
@@ -46,6 +37,26 @@ def _send_smtp_email(to_address, subject, html_body, sender_name, attachments):
|
||||
img.add_header("Content-Disposition", "inline", filename=filename)
|
||||
msg.attach(img)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def _send_smtp_email(to_address, subject, html_body, sender_name, attachments):
|
||||
"""Отправляет письмо через SMTP с inline-вложениями."""
|
||||
config = current_app.config
|
||||
|
||||
server = config.get("MAIL_SERVER", "")
|
||||
port = config.get("MAIL_PORT", 465)
|
||||
username = config.get("MAIL_USERNAME", "")
|
||||
password = config.get("MAIL_PASSWORD", "")
|
||||
use_ssl = config.get("MAIL_USE_SSL", True)
|
||||
|
||||
if not username or not password:
|
||||
raise ValueError("MAIL_USERNAME и MAIL_PASSWORD должны быть указаны в конфигурации")
|
||||
if not server or not port:
|
||||
raise ValueError("MAIL_SERVER и MAIL_PORT должны быть указаны в конфигурации")
|
||||
|
||||
msg = _build_mime_message(to_address, subject, html_body, sender_name, attachments)
|
||||
|
||||
if use_ssl:
|
||||
with smtplib.SMTP_SSL(server, port) as smtp:
|
||||
smtp.login(username, password)
|
||||
@@ -56,6 +67,8 @@ def _send_smtp_email(to_address, subject, html_body, sender_name, attachments):
|
||||
smtp.login(username, password)
|
||||
smtp.send_message(msg)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def _send_outlook_email(to_address, subject, html_body, sender_name, attachments):
|
||||
"""Отправляет письмо через локальный Outlook (MAPI/COM)."""
|
||||
@@ -132,6 +145,49 @@ def _send_ews_email(to_address, subject, html_body, sender_name, attachments):
|
||||
raise RuntimeError(f"Ошибка отправки через EWS: {e}") from e
|
||||
|
||||
|
||||
def _save_imap_copy(to_address, subject, html_body, sender_name, attachments):
|
||||
"""Сохраняет копию отправленного письма в IMAP-папку (обычно Sent)."""
|
||||
import imaplib
|
||||
|
||||
config = current_app.config
|
||||
if not config.get("IMAP_SAVE_COPY"):
|
||||
return
|
||||
|
||||
server = config.get("IMAP_SERVER", "")
|
||||
port = config.get("IMAP_PORT", 993)
|
||||
username = config.get("IMAP_USERNAME", "")
|
||||
password = config.get("IMAP_PASSWORD", "")
|
||||
folder = config.get("IMAP_FOLDER", "Sent")
|
||||
use_ssl = config.get("IMAP_USE_SSL", True)
|
||||
|
||||
if not server or not username or not password:
|
||||
raise ValueError("Для сохранения копии в IMAP должны быть указаны сервер, логин и пароль")
|
||||
|
||||
msg = _build_mime_message(to_address, subject, html_body, sender_name, attachments)
|
||||
msg_bytes = msg.as_bytes()
|
||||
date_str = imaplib.Time2Internaldate(time.time())
|
||||
|
||||
try:
|
||||
if use_ssl:
|
||||
mail = imaplib.IMAP4_SSL(server, port)
|
||||
else:
|
||||
mail = imaplib.IMAP4(server, port)
|
||||
try:
|
||||
mail.starttls()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
mail.login(username, password)
|
||||
mail.select(folder)
|
||||
result, data = mail.append(folder, "", date_str, msg_bytes)
|
||||
if result != "OK":
|
||||
raise RuntimeError(f"IMAP append failed: {data}")
|
||||
mail.close()
|
||||
mail.logout()
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Ошибка сохранения копии в IMAP: {e}") from e
|
||||
|
||||
|
||||
def _send_email(to_address, subject, html_body, sender_name="InviteMe", attachments=None):
|
||||
"""Маршрутизирует отправку через SMTP, EWS или локальный Outlook."""
|
||||
attachments = attachments or []
|
||||
@@ -142,6 +198,10 @@ def _send_email(to_address, subject, html_body, sender_name="InviteMe", attachme
|
||||
else:
|
||||
_send_smtp_email(to_address, subject, html_body, sender_name, attachments)
|
||||
|
||||
# Сохраняем копию в IMAP, если включено
|
||||
if current_app.config.get("IMAP_SAVE_COPY"):
|
||||
_save_imap_copy(to_address, subject, html_body, sender_name, attachments)
|
||||
|
||||
|
||||
def send_invitation(guest, event_name=None, additional_text=""):
|
||||
"""Отправляет приглашение гостю на email с QR-кодом и билетом во вложении."""
|
||||
|
||||
Reference in New Issue
Block a user