feat: EmailDeliveryService + professional DSR email templates

- EmailDeliveryService: load template → find published version →
  render {{variables}} → send via SMTP → audit log. Fallback to
  inline HTML when no published template exists.
- Migration 117: Professional HTML/text content for all 5 DSR
  templates (receipt, completion, rejection, identity, extension)
  with branded styling and proper Art. references
- DSRArt11Service now uses EmailDeliveryService with dsr_rejection
  template instead of hardcoded HTML

[migration-approved]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-03 23:38:32 +02:00
parent 060f351da7
commit eb4ea8bc42
3 changed files with 305 additions and 26 deletions
@@ -75,33 +75,26 @@ class DSRArt11Service:
if not dsr.requester_email:
return
try:
from compliance.services.smtp_sender import send_email
send_email(
from compliance.services.email_delivery_service import EmailDeliveryService
delivery = EmailDeliveryService(self._db)
variables = {
"requester_name": dsr.requester_name or "Antragsteller/in",
"reference_number": dsr.request_number or "",
"rejection_reason": "Identifikation nicht moeglich — Art. 11 Abs. 1 DSGVO",
"legal_basis": "Art. 11 Abs. 1 DSGVO",
"sender_name": "Datenschutzbeauftragter",
}
# Use published dsr_rejection template, fallback to inline
delivery.send(
tenant_id=str(dsr.tenant_id),
template_type="dsr_rejection",
recipient=dsr.requester_email,
subject=f"Zu Ihrer Anfrage {dsr.request_number} — Art. 11 DSGVO",
body_html=f"""
<div style="font-family:system-ui,sans-serif;max-width:600px;margin:0 auto;">
<div style="background:#6b7280;color:white;padding:20px 24px;border-radius:12px 12px 0 0;">
<h1 style="margin:0;font-size:20px;">Mitteilung zu Ihrer Anfrage</h1>
</div>
<div style="background:white;padding:24px;border:1px solid #e5e7eb;border-top:none;border-radius:0 0 12px 12px;">
<p>Sehr geehrte/r Antragsteller/in,</p>
<p>wir haben Ihre Anfrage ({dsr.request_number}) gemaess Art. 15 DSGVO
erhalten und geprueft.</p>
<p>Leider koennen wir die bei uns gespeicherten Daten (anonymisierte
Cookies, IP-Hashes) <strong>keiner identifizierbaren Person zuordnen</strong>.</p>
<p>Gemaess <strong>Art. 11 Abs. 1 DSGVO</strong> sind wir nicht verpflichtet,
zusaetzliche Informationen zu erheben, um Sie zu identifizieren.
Eine Auskunftserteilung ist daher nicht moeglich.</p>
<p>Sollten Sie ueber ein Kundenkonto bei uns verfuegen, koennen Sie
die Anfrage unter Angabe Ihrer Kundennummer erneut einreichen.</p>
<p style="margin-top:24px;color:#6b7280;font-size:12px;">
Mit freundlichen Gruessen<br>
<strong>Datenschutzbeauftragter</strong>
</p>
</div>
</div>
""",
variables=variables,
fallback_subject=f"Zu Ihrer Anfrage {dsr.request_number} — Art. 11 DSGVO",
fallback_html=f"""<p>Sehr geehrte/r {dsr.requester_name or 'Antragsteller/in'},</p>
<p>wir koennen die bei uns gespeicherten Daten keiner identifizierbaren Person zuordnen.
Gemaess Art. 11 Abs. 1 DSGVO ist eine Auskunftserteilung nicht moeglich.</p>
<p>Mit freundlichen Gruessen<br/>Datenschutzbeauftragter</p>""",
)
except Exception as e:
logger.warning("Art. 11 notification failed: %s", e)