from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
import time
import os
import tempfile
from fpdf import FPDF
def _build_smtp_client():
smtp = smtplib.SMTP(
"mail.invario-software.de",
587,
timeout=10,
)
smtp.ehlo()
if True: # Always use TLS for security
smtp.starttls()
smtp.ehlo()
smtp.login("no-reply@invario-software.de", "#,EATwIn,68")
return smtp
def send(email: list | str, subject: str, text_body: str, html_body: str = None) -> bool:
"""Sends the email with both Plain Text and HTML support."""
if isinstance(email, str):
email = [email]
TEXT_SIGNATURE = (
"\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 = """
|
Mit freundlichen Grüßen,
Ihr Invario Team
Invario UG
Am Sportplatz 10
83052 Bruckmühl
|
"""
text_content = f"{text_body}{TEXT_SIGNATURE}"
if html_body:
html_content = f"{html_body}{HTML_SIGNATURE}
"
else:
# Fallback if only text is provided
html_safe_text = text_body.replace('\n', '
')
html_content = f"{html_safe_text}
{HTML_SIGNATURE}"
mails_per_second = 10
interval = 1.0 / mails_per_second
smtp = None
try:
smtp = _build_smtp_client()
for i, recipient in enumerate(email):
start_time = time.time()
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = "Invario Team "
msg["To"] = str(recipient)
msg.attach(MIMEText(text_content, "plain"))
msg.attach(MIMEText(html_content, "html"))
smtp.sendmail(
from_addr="no-reply@invario-software.de",
to_addrs=[recipient],
msg=msg.as_string()
)
elapsed_time = time.time() - start_time
sleep_time = interval - elapsed_time
if sleep_time > 0 and i < len(email) - 1:
time.sleep(sleep_time)
return True
except Exception as e:
print(f"Debug: Fehler beim Senden der E-Mail: {e}")
return False
finally:
if smtp:
try:
smtp.quit()
except Exception:
pass
def send_register_token(email: str, token: str) -> bool:
"""Sends a professionally styled registration token to the user."""
subject = "Aktion erforderlich: Ihr Bestätigungscode für Invario"
text_note = (
"Herzlich willkommen bei Invario!\n\n"
"Um die Einrichtung Ihres Kontos abzuschließen und Ihre E-Mail-Adresse zu bestätigen, "
"geben Sie bitte den folgenden Sicherheitscode ein:\n\n"
f"Code: {token}\n\n"
"Dieser Code ist aus Sicherheitsgründen nur für begrenzte Zeit gültig. "
"Falls Sie diese Registrierung nicht angefordert haben, können Sie diese E-Mail einfach ignorieren."
)
html_note = f"""
Willkommen bei Invario!
Vielen Dank für Ihre Registrierung. Um die Einrichtung Ihres Kontos abzuschließen und Ihre E-Mail-Adresse zu verifizieren, verwenden Sie bitte den folgenden Bestätigungscode:
{token}
Dieser Code ist aus Sicherheitsgründen nur für eine begrenzte Zeit gültig.
Falls Sie kein Konto bei uns erstellt haben, können Sie diese E-Mail sicher ignorieren.
"""
return send(email, subject, text_body=text_note, html_body=html_note)
def send_password_reset_token(email: str, token: str) -> bool:
"""Sends a professionally styled password reset token to the user."""
subject = "Aktion erforderlich: Ihr Passwort-Zurücksetzungs-Code für Invario"
text_note = (
"Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt.\n\n"
"Um Ihr Passwort zurückzusetzen, geben Sie bitte den folgenden Sicherheitscode ein:\n\n"
f"Code: {token}\n\n"
"Dieser Code ist aus Sicherheitsgründen nur für begrenzte Zeit gültig. "
"Falls Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail einfach ignorieren."
)
html_note = f"""
Passwort zurücksetzen
Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt. Um fortzufahren, verwenden Sie bitte den folgenden Bestätigungscode:
{token}
Dieser Code ist aus Sicherheitsgründen nur für eine begrenzte Zeit gültig.
Falls Sie kein Zurücksetzen des Passworts angefordert haben, können Sie diese E-Mail sicher ignorieren.
"""
return send(email, subject, text_body=text_note, html_body=html_note)
def generate_main_contract_pdf(
school_name: str,
address: str,
price: str,
date: str,
software_name: str,
output_path: str
):
"""Generiert das komplette PDF des Hauptvertrages (HV) mit allen Paragraphen und dynamischen Feldern."""
pdf = FPDF()
pdf.add_page()
pdf.set_auto_page_break(auto=True, margin=15)
# Schriftart festlegen
pdf.set_font("Helvetica", size=10)
# --- TITEL ---
pdf.set_font("Helvetica", style="B", size=13)
pdf.cell(0, 6, "Vertrag über die Nutzung der Software-Lizenz", ln=True, align="C")
pdf.cell(0, 6, f"{software_name}", ln=True, align="C")
pdf.ln(6)
# --- PARTEIEN ---
pdf.set_font("Helvetica", size=10)
parties_text = (
"Zwischen\n\n"
"Invario UG\nAm Sportplatz 10, 83052 Bruckmühl\n"
"— Anbieter / Lizenzgeber —\n\n"
"und\n\n"
f"{school_name}\n{address}\n"
"— Auftraggeber / Schule —"
)
pdf.multi_cell(0, 5, parties_text)
pdf.ln(6)
# --- 1. VERTRAGSGEGENSTAND ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "1. Vertragsgegenstand", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
f"(1) Der Anbieter stellt dem Auftraggeber die Software „{software_name}“ (nachfolgend „Software“) als webbasierte Anwendung im Wege des Software-as-a-Service (SaaS) zur Verfügung. Die Software wird auf der vom Anbieter genutzten Infrastruktur betrieben.\n"
"(2) Dieser Hauptvertrag wird durch folgende Dokumente ergänzt, die als integrale Bestandteile dieses Vertrages gelten:\n"
"a) Anlage 1: Allgemeine Geschäftsbedingungen (AGB) und Lizenzbedingungen (EULA) des Anbieters\n"
"b) Anlage 2: Auftragsverarbeitungsvertrag (AVV) gemäß Art. 28 DS-GVO nebst technischen und organisatorischen Maßnahmen (TOM)"
)
pdf.ln(4)
# --- 2. VERTRAGSBEGINN ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "2. Vertragsbeginn", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
"(1) Dieser Vertrag kommt entweder mit dem Datum der beidseitigen Unterzeichnung dieses Dokumentes oder durch die Nutzung der integrierten digitalen Bestellfunktion im Invario-Portal durch einen hierzu autorisierten Vertreter des Auftraggebers zustande.\n"
"(2) Die Vertragslaufzeit beginnt mit der Bereitstellung der Zugangsdaten durch den Anbieter."
)
pdf.ln(4)
# --- 3. LAUFZEIT UND KÜNDIGUNG ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "3. Laufzeit und Kündigung", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
"(1) Der Vertrag wird zunächst für die Dauer von zwölf (12) Monaten geschlossen (Mindestlaufzeit).\n"
"(2) Er verlängert sich automatisch um jeweils ein weiteres Jahr, sofern er nicht von einer Partei mit einer Frist von drei (3) Monaten zum Ende der jeweiligen Laufzeit gekündigt wird.\n"
"(3) Die Kündigung kann in Textform (z. B. per E-Mail) oder über die dafür vorgesehenen Funktionen im Invario-Portal erfolgen. Das Recht zur außerordentlichen Kündigung aus wichtigem Grund bleibt unberührt."
)
pdf.ln(4)
# --- 4. VERGÜTUNG UND BEZAHLUNG ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "4. Vergütung und Bezahlung", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
f"(1) Für die Nutzung der Software zahlt der Auftraggeber eine jährliche Lizenzgebühr in Höhe von {price} EUR (zzgl. ggf. anfallender gesetzlicher Umsatzsteuer).\n"
"(2) Die Vergütung ist jeweils für ein Vertragsjahr im Voraus fällig.\n"
"(3) Der Rechnungsbetrag ist innerhalb von 30 Tagen nach Erhalt der Rechnung ohne Abzug zur Zahlung fällig."
)
pdf.ln(4)
# --- 5. NUTZUNG UND SUPPORT ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "5. Nutzung und Support", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
"(1) Der Umfang der Lizenzeinräumung sowie die Nutzungsbeschränkungen richten sich nach den Bestimmungen der anliegenden EULA (Anlage 1).\n"
"(2) Der technische Support wird innerhalb der in den AGB (Anlage 1) definierten Servicezeiten über das integrierte Ticket-System oder per E-Mail erbracht."
)
pdf.ln(4)
# --- 6. DATENSCHUTZ UND DATENSICHERHEIT ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "6. Datenschutz und Datensicherheit", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
"(1) Die Parteien verpflichten sich zur Einhaltung der datenschutzrechtlichen Bestimmungen. Die Details der Verarbeitung der personenbezogenen Daten werden im anliegenden AVV (Anlage 2) verbindlich geregelt.\n"
"(2) Der Anbieter sichert zu, dass die Speicherung und Verarbeitung der Daten des Auftraggebers ausschließlich in Rechenzentren mit Standort innerhalb der Bundesrepublik Deutschland erfolgt."
)
pdf.ln(4)
# --- 7. HAFTUNG ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "7. Haftung", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
"(1) Die Haftung des Anbieters richtet sich nach den Regelungen in Ziffer 7 der Allgemeinen Geschäftsbedingungen (Anlage 1).\n"
"(2) Für Unterbrechungen der Verfügbarkeit durch höhere Gewalt, Störungen im Bereich außerhalb des Übergabepunktes (z. B. Internet-Backbone) sowie für unvorhersehbare Sicherheitslücken, die nach dem aktuellen Stand der Technik zum Zeitpunkt des Auftretens noch nicht abwehrbar waren (Zero-Day-Exploits), haftet the Anbieter nur nach Maßgabe der Haftungsregelungen in den AGB, es sei denn, ihn trifft ein Verschulden an der verzögerten Behebung. Gesetzliche Datenschutzpflichten und Meldepflichten bleiben unberührt."
)
pdf.ln(4)
# --- 8. SCHLUSSBESTIMMUNGEN ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "8. Schlussbestimmungen", ln=True)
pdf.set_font("Helvetica", size=10)
pdf.multi_cell(0, 5,
"(1) Änderungen und Ergänzungen dieses Vertrages bedürfen der Textform.\n"
"(2) Sollten einzelne Bestimmungen dieses Vertrages unwirksam oder undurchführbar sein oder werden, bleibt die Wirksamkeit der übrigen Bestimmungen hiervon unberührt. Anstelle der unwirksamen oder undurchführbaren Bestimmung gelten die gesetzlichen Vorschriften. Sofern es sich nicht um Allgemeine Geschäftsbedingungen handelt, verpflichten sich die Parteien, eine wirksame Regelung zu vereinbaren, die dem wirtschaftlichen Zweck der unwirksamen oder undurchführbaren Bestimmung möglichst nahekommt.\n"
"(3) Es gilt das Recht der Bundesrepublik Deutschland. Ausschließlicher Gerichtsstand für alle Streitigkeiten aus oder im Zusammenhang mit diesem Vertrag ist, soweit gesetzlich zulässig, der Sitz des Anbieters."
)
pdf.ln(6)
# --- UNTERSCHRIFTEN ---
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 6, "Unterschriften und Einverständnis", ln=True)
pdf.set_font("Helvetica", size=9)
pdf.multi_cell(0, 4, "Mit der Unterzeichnung bestätigen die Parteien den Abschluss des Hauptvertrages inklusive der beigefügten Anlagen (AGB, EULA und AVV).")
pdf.ln(8)
# Unterschriften-Blöcke nebeneinander
pdf.set_font("Helvetica", size=9)
pdf.cell(95, 5, "Für den Auftraggeber", ln=False)
pdf.cell(95, 5, "Für den Anbieter", ln=True)
pdf.ln(8)
pdf.cell(95, 5, f"{date} ____________________________________", ln=False)
pdf.cell(95, 5, f"{date} ________________ ____________________", ln=True)
pdf.cell(95, 5, "(Verantwortlicher)", ln=False)
pdf.cell(95, 5, "(Simon Brieger)", ln=True)
# PDF speichern
pdf.output(output_path)
return output_path
def send_accreditation_email(
recipient: str,
domain: str,
username: str,
password: str,
school_name: str,
address: str,
price: str,
date: str,
software_name: str,
invoice_pdf_path: str = None
) -> bool:
"""Sends an accreditation email with login credentials and generated contract PDF."""
# 1. Temporären Pfad für das generierte PDF festlegen
contract_filename = f"Vertrag_{school_name.replace(' ', '_')}.pdf"
contract_pdf_path = os.path.join(tempfile.gettempdir(), contract_filename)
# 2. Hauptvertrag als PDF generieren
generate_main_contract_pdf(
school_name=school_name,
address=address,
price=price,
date=date,
software_name=software_name,
output_path=contract_pdf_path
)
# 3. E-Mail Inhalt vorbereiten
subject = "Ihre Zugangsdaten und Unterlagen für Invario"
text_note = (
f"Sehr geehrte/r {username},\n\n"
"Vielen Dank für Ihre Akkreditierung bei Invario. "
"Im Anhang finden Sie Ihre Rechnung und den Vertrag.\n\n"
f"Ihre Zugangsdaten:\n"
f"Domain: https://{domain}\n"
f"Benutzername: {username}\n"
f"Passwort: {password}\n\n"
"Bitte bewahren Sie diese Informationen sicher auf."
)
html_note = f"""
Willkommen bei Invario!
Vielen Dank für Ihre Akkreditierung. Im Anhang finden Sie Ihre Rechnung und den Vertrag.
Ihre Zugangsdaten:
Bitte bewahren Sie diese Informationen sicher auf.
"""
# 4. Anhänge zusammenstellen
attachments = [contract_pdf_path]
if invoice_pdf_path and os.path.exists(invoice_pdf_path):
attachments.append(invoice_pdf_path)
# 5. E-Mail versenden
result = send(recipient, subject, text_body=text_note, html_body=html_note, attachments=attachments)
# 6. Temporäre PDF-Datei nach dem Versand wieder löschen
try:
os.remove(contract_pdf_path)
except OSError:
pass
return result