Feature: Gesendete Mails via IMAP in Sent-Ordner ablegen
This commit is contained in:
parent
b9ee67b8dd
commit
31fae63658
1 changed files with 49 additions and 7 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
"""BAN YARO — Mailing (Admin)"""
|
"""BAN YARO — Mailing (Admin)"""
|
||||||
|
|
||||||
|
import imaplib
|
||||||
import os
|
import os
|
||||||
import smtplib
|
import smtplib
|
||||||
import ssl
|
import ssl
|
||||||
|
|
@ -19,6 +20,8 @@ router = APIRouter()
|
||||||
|
|
||||||
_SMTP_HOST = os.getenv("SMTP_HOST", "mail.your-server.de")
|
_SMTP_HOST = os.getenv("SMTP_HOST", "mail.your-server.de")
|
||||||
_SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
|
_SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
|
||||||
|
_IMAP_HOST = os.getenv("IMAP_HOST", "mail.your-server.de")
|
||||||
|
_IMAP_PORT = int(os.getenv("IMAP_PORT", "993"))
|
||||||
|
|
||||||
_ACCOUNTS = {
|
_ACCOUNTS = {
|
||||||
"partner": {
|
"partner": {
|
||||||
|
|
@ -35,23 +38,62 @@ _ACCOUNTS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Mögliche Namen für den Sent-Ordner (Hetzner/Dovecot)
|
||||||
|
_SENT_CANDIDATES = ["Sent", "Sent Messages", "Sent Items", "INBOX.Sent", "Gesendete Objekte"]
|
||||||
|
|
||||||
def _send_smtp(to: str, subject: str, body: str, account: str = "partner"):
|
|
||||||
|
def _imap_save_sent(msg_bytes: bytes, account: str):
|
||||||
acc = _ACCOUNTS.get(account) or _ACCOUNTS["partner"]
|
acc = _ACCOUNTS.get(account) or _ACCOUNTS["partner"]
|
||||||
if not acc["user"] or not acc["pass"]:
|
if not acc["user"] or not acc["pass"]:
|
||||||
raise RuntimeError(f"SMTP-Account '{account}' nicht konfiguriert.")
|
return
|
||||||
|
try:
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
with imaplib.IMAP4_SSL(_IMAP_HOST, _IMAP_PORT, ssl_context=ctx) as imap:
|
||||||
|
imap.login(acc["user"], acc["pass"])
|
||||||
|
# Sent-Ordner finden
|
||||||
|
folder = None
|
||||||
|
_, folders = imap.list()
|
||||||
|
available = [f.decode() for f in (folders or [])]
|
||||||
|
for candidate in _SENT_CANDIDATES:
|
||||||
|
if any(candidate.lower() in f.lower() for f in available):
|
||||||
|
folder = candidate
|
||||||
|
break
|
||||||
|
if not folder:
|
||||||
|
folder = "Sent" # Fallback: anlegen lassen
|
||||||
|
imap.append(
|
||||||
|
folder,
|
||||||
|
r"\Seen",
|
||||||
|
imaplib.Time2Internaldate(datetime.now().timestamp()),
|
||||||
|
msg_bytes,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass # Nicht blockieren wenn IMAP fehlschlägt
|
||||||
|
|
||||||
|
|
||||||
|
def _build_message(to: str, subject: str, body: str, account: str) -> MIMEMultipart:
|
||||||
|
acc = _ACCOUNTS.get(account) or _ACCOUNTS["partner"]
|
||||||
msg = MIMEMultipart("alternative")
|
msg = MIMEMultipart("alternative")
|
||||||
msg["Subject"] = subject
|
msg["Subject"] = subject
|
||||||
msg["From"] = formataddr((acc["name"], acc["from"]))
|
msg["From"] = formataddr((acc["name"], acc["from"]))
|
||||||
msg["To"] = to
|
msg["To"] = to
|
||||||
msg["Reply-To"] = acc["from"]
|
msg["Reply-To"] = acc["from"]
|
||||||
msg.attach(MIMEText(body, "plain", "utf-8"))
|
msg.attach(MIMEText(body, "plain", "utf-8"))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def _send_smtp(to: str, subject: str, body: str, account: str = "partner"):
|
||||||
|
acc = _ACCOUNTS.get(account) or _ACCOUNTS["partner"]
|
||||||
|
if not acc["user"] or not acc["pass"]:
|
||||||
|
raise RuntimeError(f"SMTP-Account '{account}' nicht konfiguriert.")
|
||||||
|
msg = _build_message(to, subject, body, account)
|
||||||
|
msg_bytes = msg.as_bytes()
|
||||||
ctx = ssl.create_default_context()
|
ctx = ssl.create_default_context()
|
||||||
with smtplib.SMTP(_SMTP_HOST, _SMTP_PORT, timeout=15) as s:
|
with smtplib.SMTP(_SMTP_HOST, _SMTP_PORT, timeout=15) as s:
|
||||||
s.ehlo()
|
s.ehlo()
|
||||||
s.starttls(context=ctx)
|
s.starttls(context=ctx)
|
||||||
s.login(acc["user"], acc["pass"])
|
s.login(acc["user"], acc["pass"])
|
||||||
s.sendmail(acc["from"], [to], msg.as_bytes())
|
s.sendmail(acc["from"], [to], msg_bytes)
|
||||||
|
_imap_save_sent(msg_bytes, account)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue