changes to the email sending

This commit is contained in:
2026-09-17 19:36:48 +02:00
parent 7b2a6d7bc1
commit 320acb5256
2 changed files with 67 additions and 58 deletions
+4 -4
View File
@@ -48,7 +48,7 @@ import Web.modules.log.audit_log as al
import push_notifications as pn import push_notifications as pn
import Web.modules.inventarsystem.pdf_export as pdf_export import Web.modules.inventarsystem.pdf_export as pdf_export
import Web.modules.inventarsystem.excel_export as excel_export import Web.modules.inventarsystem.excel_export as excel_export
from Web.modules.emailservice.email import send, send_pdf from Web.modules.emailservice.email import send
import datetime import datetime
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from bson.objectid import ObjectId, InvalidId from bson.objectid import ObjectId, InvalidId
@@ -5543,7 +5543,7 @@ def student_card_single_barcode_download(card_id):
if not cfg.MODULES.is_enabled('mail'): if not cfg.MODULES.is_enabled('mail'):
flash('Das E-Mail-Add-on ist deaktiviert. Der Ausweis kann weiterhin als PDF heruntergeladen werden.', 'warning') flash('Das E-Mail-Add-on ist deaktiviert. Der Ausweis kann weiterhin als PDF heruntergeladen werden.', 'warning')
return redirect(url_for('student_cards_admin')) return redirect(url_for('student_cards_admin'))
sent = send_pdf( sent = send(
recipient_email, recipient_email,
f'Bibliotheksausweis {card["AusweisId"]}', f'Bibliotheksausweis {card["AusweisId"]}',
f'Anbei erhalten Sie den Bibliotheksausweis für {card.get("SchülerName", "")} als PDF.', f'Anbei erhalten Sie den Bibliotheksausweis für {card.get("SchülerName", "")} als PDF.',
@@ -9153,7 +9153,7 @@ def admin_audit_export_pdf_official():
flash('Das E-Mail-Add-on ist deaktiviert. Der Auditbericht kann weiterhin als PDF heruntergeladen werden.', 'warning') flash('Das E-Mail-Add-on ist deaktiviert. Der Auditbericht kann weiterhin als PDF heruntergeladen werden.', 'warning')
return redirect(url_for('admin_audit_dashboard')) return redirect(url_for('admin_audit_dashboard'))
filename = f'audit-official-report-{datetime.datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y%m%d-%H%M%S")}.pdf' filename = f'audit-official-report-{datetime.datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y%m%d-%H%M%S")}.pdf'
sent = send_pdf( sent = send(
recipient_email, recipient_email,
'Amtlicher Auditbericht', 'Amtlicher Auditbericht',
'Anbei erhalten Sie den aktuellen amtlichen Auditbericht des Inventarsystems als PDF.', 'Anbei erhalten Sie den aktuellen amtlichen Auditbericht des Inventarsystems als PDF.',
@@ -9549,7 +9549,7 @@ def admin_create_invoice(borrow_id):
if not cfg.MODULES.is_enabled('mail'): if not cfg.MODULES.is_enabled('mail'):
flash('Das E-Mail-Add-on ist deaktiviert. Die PDF-Rechnung kann weiterhin heruntergeladen werden.', 'warning') flash('Das E-Mail-Add-on ist deaktiviert. Die PDF-Rechnung kann weiterhin heruntergeladen werden.', 'warning')
return redirect(url_for('admin_borrowings')) return redirect(url_for('admin_borrowings'))
sent = send_pdf( sent = send(
recipient_email, recipient_email,
f'Rechnung {invoice_number} - {item_name}', f'Rechnung {invoice_number} - {item_name}',
f'Anbei erhalten Sie die Rechnung {invoice_number} zum Element {item_name}.', f'Anbei erhalten Sie die Rechnung {invoice_number} zum Element {item_name}.',
+61 -52
View File
@@ -3,7 +3,7 @@ from email.mime.text import MIMEText
from email.mime.application import MIMEApplication from email.mime.application import MIMEApplication
import smtplib import smtplib
import time import time
import os
import Web.modules.database.settings as cfg import Web.modules.database.settings as cfg
@@ -28,70 +28,89 @@ def _normalize_recipients(email: list | str) -> list[str]:
return [str(recipient).strip() for recipient in (email or []) if str(recipient).strip()] return [str(recipient).strip() for recipient in (email or []) if str(recipient).strip()]
def _send_message(email: list | str, subject: str, note: str, sender: str, attachment=None) -> bool: def send(
"""Send a plain/HTML message, optionally with one PDF attachment.""" email: list | str,
if not cfg.MODULES.is_enabled("mail"): subject: str,
print("Debug: Module not enabled") text_body: str,
return False html_body: str = None,
attachments: list = None
) -> bool:
"""Sends the email with Plain Text, HTML, and optional File Attachments support."""
recipients = _normalize_recipients(email) if isinstance(email, str):
if not recipients: email = [email]
return False
body_message = note if attachments is None:
attachments = []
HTML_SIGNATURE = f""" TEXT_SIGNATURE = (
<table cellpadding="0" cellspacing="0" border="0" style="font-family: Arial, Helvetica, sans-serif; font-size: 13px; color: #333333; line-height: 1.5;"> "\n\n--\n"
"Mit freundlichen Grüßen\n"
"Ihr Invario Team\n\n"
"Invario UG\n"
"Am Sportplatz 10\n"
"83052 Bruckmühl"
)
HTML_SIGNATURE = """
<br><br>
<table cellpadding="0" cellspacing="0" border="0" style="font-family: Arial, Helvetica, sans-serif; font-size: 13px; color: #555555; line-height: 1.5; border-top: 1px solid #eaebed; padding-top: 15px; width: 100%;">
<tr> <tr>
<td> <td>
<p style="margin:0 0 12px 0;">Mit freundlichen Grüßen</p> <p style="margin:0 0 5px 0;">Mit freundlichen Grüßen,</p>
<p style="margin:0;"><strong style="font-size:16px;">Automatisierter Email Verteiler für die Schule: {cfg.get_school_info().get("name")}</strong><br></p><br> <p style="margin:0;"><strong style="font-size:15px; color: #333333;">Ihr Invario Team</strong></p><br>
<p style="margin:12px 0 0 0;"><strong>Invario UG</strong><br>Am Sportplatz 10<br>83052 Bruckmühl</p> <p style="margin:0 0 0 0; font-size: 12px; color: #888888;">
<strong>Invario UG</strong><br>
Am Sportplatz 10<br>
83052 Bruckmühl
</p>
</td> </td>
</tr> </tr>
</table> </table>
""" """
text_content = f"{body_message}\n\nMit freundlichen Grüßen\n{sender}\n" text_content = f"{text_body}{TEXT_SIGNATURE}"
html_content = f""" if html_body:
<html> html_content = f"<html><body style='background-color: #f9f9f9; padding: 20px;'><div style='background-color: #ffffff; padding: 30px; border-radius: 8px; max-width: 600px; margin: 0 auto; box-shadow: 0px 2px 5px rgba(0,0,0,0.05);'>{html_body}{HTML_SIGNATURE}</div></body></html>"
<body> else:
<p>{body_message}</p> # Fallback if only text is provided
<br> html_safe_text = text_body.replace('\n', '<br>')
{HTML_SIGNATURE} html_content = f"<html><body><p style='font-family: Arial, sans-serif; color: #333333;'>{html_safe_text}</p>{HTML_SIGNATURE}</body></html>"
</body>
</html>
"""
mails_per_second = 10 mails_per_second = 10
interval = 1.0 / mails_per_second interval = 1.0 / mails_per_second
smtp = None smtp = None
try: try:
smtp = _build_smtp_client() smtp = _build_smtp_client()
for i, recipient in enumerate(recipients): for i, recipient in enumerate(email):
start_time = time.time() start_time = time.time()
msg = MIMEMultipart("mixed" if attachment else "alternative") # Root message structure for email with attachments
msg["Subject"] = str(subject) msg = MIMEMultipart("mixed")
from_address = "no-reply@invario-software.de" msg["Subject"] = subject
msg["From"] = f"{sender} <{from_address}>" msg["From"] = "Invario Team <no-reply@invario-software.de>"
msg["To"] = str(recipient) msg["To"] = str(recipient)
body = MIMEMultipart("alternative") if attachment else msg # Sub-container for Plain Text and HTML bodies
body.attach(MIMEText(text_content, "plain")) msg_body = MIMEMultipart("alternative")
body.attach(MIMEText(html_content, "html")) msg_body.attach(MIMEText(text_content, "plain"))
if attachment: msg_body.attach(MIMEText(html_content, "html"))
msg.attach(body) msg.attach(msg_body)
attachment_payload, filename = attachment
pdf_part = MIMEApplication(attachment_payload, _subtype="pdf") # Attach files if provided
pdf_part.add_header("Content-Disposition", "attachment", filename=filename) for file_path in attachments:
msg.attach(pdf_part) if file_path and os.path.isfile(file_path):
filename = os.path.basename(file_path)
with open(file_path, "rb") as file:
part = MIMEApplication(file.read(), Name=filename)
part['Content-Disposition'] = f'attachment; filename="{filename}"'
msg.attach(part)
smtp.sendmail( smtp.sendmail(
from_addr=from_address, from_addr="no-reply@invario-software.de",
to_addrs=[recipient], to_addrs=[recipient],
msg=msg.as_string() msg=msg.as_string()
) )
@@ -99,7 +118,7 @@ def _send_message(email: list | str, subject: str, note: str, sender: str, attac
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
sleep_time = interval - elapsed_time sleep_time = interval - elapsed_time
if sleep_time > 0 and i < len(recipients) - 1: if sleep_time > 0 and i < len(email) - 1:
time.sleep(sleep_time) time.sleep(sleep_time)
return True return True
@@ -112,13 +131,3 @@ def _send_message(email: list | str, subject: str, note: str, sender: str, attac
smtp.quit() smtp.quit()
except Exception: except Exception:
pass pass
def send(email: list | str, subject: str, note: str, sender: str) -> bool:
"""Send an HTML/plain email without an attachment."""
return _send_message(email, subject, note, sender)
def send_pdf(email: list | str, subject: str, note: str, sender: str, pdf_bytes: bytes, filename: str) -> bool:
"""Send an email with a PDF attachment."""
return _send_message(email, subject, note, sender, attachment=(pdf_bytes, filename))