see comments

This commit is contained in:
Rajpreet0 2026-08-14 13:56:23 +02:00
parent b340d7ee03
commit 0a06e47c82
8 changed files with 497 additions and 257 deletions

View file

@ -1,90 +1,177 @@
<!DOCTYPE html>
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E-Mail-Änderung bestätigen</title>
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5; padding: 40px 0;">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 14px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<!-- Header -->
<tr>
<td style="background-color: #1E293B; padding: 40px; text-align: center;">
<img src="https://kumasy.de/logo-dark.png" alt="Kumasy" width="160" style="display: block; margin: 0 auto 16px; max-width: 160px; height: auto;" />
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 600;">
E-Mail-Änderung bestätigen
</h1>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px;">
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;">
Hallo,
</p>
<p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;">
für dein Kumasy-Konto wurde eine Änderung der E-Mail-Adresse angefordert. Bitte bestätige die neue Adresse, damit die Änderung wirksam wird.
</p>
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f8f9fa; border: none; border-radius: 10px; margin: 0 0 30px;">
<tr>
<td style="padding: 24px;">
<p style="margin: 0 0 4px; color: #999999; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px;">Neue E-Mail-Adresse</p>
<p style="margin: 0; color: #333333; font-size: 15px; font-weight: 600;">{{ .NewEmail }}</p>
</td>
</tr>
</table>
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 20px;">
<tr>
<td align="center">
<a href="{{ .ConfirmationURL }}"
style="display: inline-block; background-color: #F4C00C; color: #1E293B; text-decoration: none; padding: 16px 40px; border-radius: 999px; font-size: 16px; font-weight: 600;">
E-Mail-Änderung bestätigen
</a>
</td>
</tr>
</table>
<p style="margin: 20px 0 0; color: #666666; font-size: 14px; line-height: 1.6; text-align: center;">
Oder kopiere diesen Link in deinen Browser:
</p>
<p style="margin: 8px 0 0; color: #1E293B; font-size: 14px; word-break: break-all; text-align: center;">
{{ .ConfirmationURL }}
</p>
<div style="border-top: 1px solid #e0e0e0; padding-top: 20px; margin-top: 30px;">
<p style="margin: 0; color: #999999; font-size: 13px; line-height: 1.6;">
Falls du diese Änderung nicht angefordert hast, kannst du diese E-Mail ignorieren – deine aktuelle E-Mail-Adresse bleibt dann unverändert.
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>E-Mail-Änderung bestätigen</title>
</head>
<body
style="
margin: 0;
padding: 0;
font-family:
-apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;,
Arial, sans-serif;
background-color: #f5f5f5;
"
>
<table
width="100%"
cellpadding="0"
cellspacing="0"
style="background-color: #f5f5f5; padding: 40px 0"
>
<tr>
<td align="center">
<table
width="600"
cellpadding="0"
cellspacing="0"
style="
background-color: #ffffff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
"
>
<!-- Header -->
<tr>
<td style="background-color: #1e293b; padding: 40px; text-align: center">
<p
style="
margin: 0 0 12px;
color: #ffffff;
font-size: 15px;
font-weight: 700;
letter-spacing: 0.5px;
text-transform: uppercase;
"
>
Kumasy
</p>
</div>
</td>
</tr>
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 600">
E-Mail-Änderung bestätigen
</h1>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background-color: #f8f9fa; padding: 30px; text-align: center; border-top: 1px solid #e0e0e0;">
<p style="margin: 0 0 10px; color: #666666; font-size: 14px;">
Kumasy
</p>
<p style="margin: 0 0 10px; color: #999999; font-size: 12px;">
Diese E-Mail wurde automatisch generiert. Bitte antworte nicht auf diese E-Mail.
</p>
<p style="margin: 0; color: #999999; font-size: 11px;">
Powered by Kumasy
</p>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px">
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6">
Hallo,
</p>
</table>
</td>
</tr>
</table>
</body>
<p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6">
für dein Kumasy-Konto wurde eine Änderung der E-Mail-Adresse angefordert. Bitte
bestätige die neue Adresse, damit die Änderung wirksam wird.
</p>
<table
width="100%"
cellpadding="0"
cellspacing="0"
style="
background-color: #f8f9fa;
border: none;
border-radius: 10px;
margin: 0 0 30px;
"
>
<tr>
<td style="padding: 24px">
<p
style="
margin: 0 0 4px;
color: #999999;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.5px;
"
>
Neue E-Mail-Adresse
</p>
<p style="margin: 0; color: #333333; font-size: 15px; font-weight: 600">
{{ .NewEmail }}
</p>
</td>
</tr>
</table>
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 20px">
<tr>
<td align="center">
<a
href="{{ .ConfirmationURL }}"
style="
display: inline-block;
background-color: #f4c00c;
color: #1e293b;
text-decoration: none;
padding: 16px 40px;
border-radius: 999px;
font-size: 16px;
font-weight: 600;
"
>
E-Mail-Änderung bestätigen
</a>
</td>
</tr>
</table>
<p
style="
margin: 20px 0 0;
color: #666666;
font-size: 14px;
line-height: 1.6;
text-align: center;
"
>
Oder kopiere diesen Link in deinen Browser:
</p>
<p
style="
margin: 8px 0 0;
color: #1e293b;
font-size: 14px;
word-break: break-all;
text-align: center;
"
>
{{ .ConfirmationURL }}
</p>
<div style="border-top: 1px solid #e0e0e0; padding-top: 20px; margin-top: 30px">
<p style="margin: 0; color: #999999; font-size: 13px; line-height: 1.6">
Falls du diese Änderung nicht angefordert hast, kannst du diese E-Mail
ignorieren – deine aktuelle E-Mail-Adresse bleibt dann unverändert.
</p>
</div>
</td>
</tr>
<!-- Footer -->
<tr>
<td
style="
background-color: #f8f9fa;
padding: 30px;
text-align: center;
border-top: 1px solid #e0e0e0;
"
>
<p style="margin: 0 0 10px; color: #666666; font-size: 14px">Kumasy</p>
<p style="margin: 0 0 10px; color: #999999; font-size: 12px">
Diese E-Mail wurde automatisch generiert. Bitte antworte nicht auf diese E-Mail.
</p>
<p style="margin: 0; color: #999999; font-size: 11px">Powered by Kumasy</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View file

@ -1,81 +1,146 @@
<!DOCTYPE html>
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bestätige deine E-Mail-Adresse</title>
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5; padding: 40px 0;">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 14px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<!-- Header -->
<tr>
<td style="background-color: #1E293B; padding: 40px; text-align: center;">
<img src="https://kumasy.de/logo-dark.png" alt="Kumasy" width="160" style="display: block; margin: 0 auto 16px; max-width: 160px; height: auto;" />
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 600;">
Willkommen bei Kumasy!
</h1>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px;">
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;">
Hallo,
</p>
<p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;">
vielen Dank für deine Registrierung bei Kumasy. Bitte bestätige deine E-Mail-Adresse, um dein Konto zu aktivieren.
</p>
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 20px;">
<tr>
<td align="center">
<a href="{{ .ConfirmationURL }}"
style="display: inline-block; background-color: #F4C00C; color: #1E293B; text-decoration: none; padding: 16px 40px; border-radius: 999px; font-size: 16px; font-weight: 600;">
E-Mail-Adresse bestätigen
</a>
</td>
</tr>
</table>
<p style="margin: 20px 0 0; color: #666666; font-size: 14px; line-height: 1.6; text-align: center;">
Oder kopiere diesen Link in deinen Browser:
</p>
<p style="margin: 8px 0 0; color: #1E293B; font-size: 14px; word-break: break-all; text-align: center;">
{{ .ConfirmationURL }}
</p>
<div style="border-top: 1px solid #e0e0e0; padding-top: 20px; margin-top: 30px;">
<p style="margin: 0; color: #999999; font-size: 13px; line-height: 1.6;">
Falls du dich nicht bei Kumasy registriert hast, kannst du diese E-Mail ignorieren.
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bestätige deine E-Mail-Adresse</title>
</head>
<body
style="
margin: 0;
padding: 0;
font-family:
-apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;,
Arial, sans-serif;
background-color: #f5f5f5;
"
>
<table
width="100%"
cellpadding="0"
cellspacing="0"
style="background-color: #f5f5f5; padding: 40px 0"
>
<tr>
<td align="center">
<table
width="600"
cellpadding="0"
cellspacing="0"
style="
background-color: #ffffff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
"
>
<!-- Header -->
<tr>
<td style="background-color: #1e293b; padding: 40px; text-align: center">
<p
style="
margin: 0 0 12px;
color: #ffffff;
font-size: 15px;
font-weight: 700;
letter-spacing: 0.5px;
text-transform: uppercase;
"
>
Kumasy
</p>
</div>
</td>
</tr>
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 600">
Willkommen bei Kumasy!
</h1>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background-color: #f8f9fa; padding: 30px; text-align: center; border-top: 1px solid #e0e0e0;">
<p style="margin: 0 0 10px; color: #666666; font-size: 14px;">
Kumasy
</p>
<p style="margin: 0 0 10px; color: #999999; font-size: 12px;">
Diese E-Mail wurde automatisch generiert. Bitte antworte nicht auf diese E-Mail.
</p>
<p style="margin: 0; color: #999999; font-size: 11px;">
Powered by Kumasy
</p>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px">
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6">
Hallo,
</p>
</table>
</td>
</tr>
</table>
</body>
<p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6">
vielen Dank für deine Registrierung bei Kumasy. Bitte bestätige deine
E-Mail-Adresse, um dein Konto zu aktivieren.
</p>
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 20px">
<tr>
<td align="center">
<a
href="{{ .ConfirmationURL }}"
style="
display: inline-block;
background-color: #f4c00c;
color: #1e293b;
text-decoration: none;
padding: 16px 40px;
border-radius: 999px;
font-size: 16px;
font-weight: 600;
"
>
E-Mail-Adresse bestätigen
</a>
</td>
</tr>
</table>
<p
style="
margin: 20px 0 0;
color: #666666;
font-size: 14px;
line-height: 1.6;
text-align: center;
"
>
Oder kopiere diesen Link in deinen Browser:
</p>
<p
style="
margin: 8px 0 0;
color: #1e293b;
font-size: 14px;
word-break: break-all;
text-align: center;
"
>
{{ .ConfirmationURL }}
</p>
<div style="border-top: 1px solid #e0e0e0; padding-top: 20px; margin-top: 30px">
<p style="margin: 0; color: #999999; font-size: 13px; line-height: 1.6">
Falls du dich nicht bei Kumasy registriert hast, kannst du diese E-Mail
ignorieren.
</p>
</div>
</td>
</tr>
<!-- Footer -->
<tr>
<td
style="
background-color: #f8f9fa;
padding: 30px;
text-align: center;
border-top: 1px solid #e0e0e0;
"
>
<p style="margin: 0 0 10px; color: #666666; font-size: 14px">Kumasy</p>
<p style="margin: 0 0 10px; color: #999999; font-size: 12px">
Diese E-Mail wurde automatisch generiert. Bitte antworte nicht auf diese E-Mail.
</p>
<p style="margin: 0; color: #999999; font-size: 11px">Powered by Kumasy</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View file

@ -1,84 +1,150 @@
<!DOCTYPE html>
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Passwort zurücksetzen</title>
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5; padding: 40px 0;">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 14px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<!-- Header -->
<tr>
<td style="background-color: #1E293B; padding: 40px; text-align: center;">
<img src="https://kumasy.de/logo-dark.png" alt="Kumasy" width="160" style="display: block; margin: 0 auto 16px; max-width: 160px; height: auto;" />
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 600;">
Passwort zurücksetzen
</h1>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px;">
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;">
Hallo,
</p>
<p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;">
wir haben eine Anfrage erhalten, das Passwort für dein Kumasy-Konto zurückzusetzen. Klicke auf den Button unten, um ein neues Passwort zu vergeben.
</p>
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 20px;">
<tr>
<td align="center">
<a href="{{ .ConfirmationURL }}"
style="display: inline-block; background-color: #F4C00C; color: #1E293B; text-decoration: none; padding: 16px 40px; border-radius: 999px; font-size: 16px; font-weight: 600;">
Neues Passwort vergeben
</a>
</td>
</tr>
</table>
<p style="margin: 20px 0 0; color: #666666; font-size: 14px; line-height: 1.6; text-align: center;">
Oder kopiere diesen Link in deinen Browser:
</p>
<p style="margin: 8px 0 0; color: #1E293B; font-size: 14px; word-break: break-all; text-align: center;">
{{ .ConfirmationURL }}
</p>
<div style="border-top: 1px solid #e0e0e0; padding-top: 20px; margin-top: 30px;">
<p style="margin: 0 0 10px; color: #999999; font-size: 13px; line-height: 1.6;">
<strong>Wichtig:</strong> Dieser Link ist aus Sicherheitsgründen zeitlich begrenzt gültig.
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Passwort zurücksetzen</title>
</head>
<body
style="
margin: 0;
padding: 0;
font-family:
-apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;,
Arial, sans-serif;
background-color: #f5f5f5;
"
>
<table
width="100%"
cellpadding="0"
cellspacing="0"
style="background-color: #f5f5f5; padding: 40px 0"
>
<tr>
<td align="center">
<table
width="600"
cellpadding="0"
cellspacing="0"
style="
background-color: #ffffff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
"
>
<!-- Header -->
<tr>
<td style="background-color: #1e293b; padding: 40px; text-align: center">
<p
style="
margin: 0 0 12px;
color: #ffffff;
font-size: 15px;
font-weight: 700;
letter-spacing: 0.5px;
text-transform: uppercase;
"
>
Kumasy
</p>
<p style="margin: 0; color: #999999; font-size: 13px; line-height: 1.6;">
Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail ignorieren – dein Passwort bleibt unverändert.
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 600">
Passwort zurücksetzen
</h1>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px">
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6">
Hallo,
</p>
</div>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background-color: #f8f9fa; padding: 30px; text-align: center; border-top: 1px solid #e0e0e0;">
<p style="margin: 0 0 10px; color: #666666; font-size: 14px;">
Kumasy
</p>
<p style="margin: 0 0 10px; color: #999999; font-size: 12px;">
Diese E-Mail wurde automatisch generiert. Bitte antworte nicht auf diese E-Mail.
</p>
<p style="margin: 0; color: #999999; font-size: 11px;">
Powered by Kumasy
</p>
</td>
</tr>
<p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6">
wir haben eine Anfrage erhalten, das Passwort für dein Kumasy-Konto
zurückzusetzen. Klicke auf den Button unten, um ein neues Passwort zu vergeben.
</p>
</table>
</td>
</tr>
</table>
</body>
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 20px">
<tr>
<td align="center">
<a
href="{{ .ConfirmationURL }}"
style="
display: inline-block;
background-color: #f4c00c;
color: #1e293b;
text-decoration: none;
padding: 16px 40px;
border-radius: 999px;
font-size: 16px;
font-weight: 600;
"
>
Neues Passwort vergeben
</a>
</td>
</tr>
</table>
<p
style="
margin: 20px 0 0;
color: #666666;
font-size: 14px;
line-height: 1.6;
text-align: center;
"
>
Oder kopiere diesen Link in deinen Browser:
</p>
<p
style="
margin: 8px 0 0;
color: #1e293b;
font-size: 14px;
word-break: break-all;
text-align: center;
"
>
{{ .ConfirmationURL }}
</p>
<div style="border-top: 1px solid #e0e0e0; padding-top: 20px; margin-top: 30px">
<p style="margin: 0 0 10px; color: #999999; font-size: 13px; line-height: 1.6">
<strong>Wichtig:</strong> Dieser Link ist aus Sicherheitsgründen zeitlich
begrenzt gültig.
</p>
<p style="margin: 0; color: #999999; font-size: 13px; line-height: 1.6">
Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail ignorieren
– dein Passwort bleibt unverändert.
</p>
</div>
</td>
</tr>
<!-- Footer -->
<tr>
<td
style="
background-color: #f8f9fa;
padding: 30px;
text-align: center;
border-top: 1px solid #e0e0e0;
"
>
<p style="margin: 0 0 10px; color: #666666; font-size: 14px">Kumasy</p>
<p style="margin: 0 0 10px; color: #999999; font-size: 12px">
Diese E-Mail wurde automatisch generiert. Bitte antworte nicht auf diese E-Mail.
</p>
<p style="margin: 0; color: #999999; font-size: 11px">Powered by Kumasy</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View file

@ -4,7 +4,7 @@
* Jede Funktion gibt einen HTML-String-Fragment zurück (kein vollständiges Dokument).
*/
import { escapeHtml, sanitizeUrl } from "./utils";
import { escapeHtml, sanitizeColor, sanitizeUrl } from "./utils";
import { EMAIL_TOKENS, KUMASY_BRANDING, type EmailBranding } from "./tokens";
interface InfoBoxRow {
@ -55,13 +55,18 @@ export function renderButton({
}): string {
const safeLabel = escapeHtml(label);
const safeHref = sanitizeUrl(href);
const safeAccentColor = sanitizeColor(branding.accentColor, KUMASY_BRANDING.accentColor);
const safeAccentTextColor = sanitizeColor(
branding.accentTextColor,
KUMASY_BRANDING.accentTextColor
);
return `
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 20px;">
<tr>
<td align="center">
<a href="${safeHref}"
style="display: inline-block; background-color: ${branding.accentColor}; color: ${branding.accentTextColor}; text-decoration: none; padding: 16px 40px; border-radius: ${EMAIL_TOKENS.radius.button}; font-size: 16px; font-weight: 600;">
style="display: inline-block; background-color: ${safeAccentColor}; color: ${safeAccentTextColor}; text-decoration: none; padding: 16px 40px; border-radius: ${EMAIL_TOKENS.radius.button}; font-size: 16px; font-weight: 600;">
${safeLabel}
</a>
</td>

View file

@ -5,7 +5,7 @@
* kein <style>-Block, kein Flexbox/Grid, keine CSS-Gradients).
*/
import { escapeHtml } from "./utils";
import { escapeHtml, sanitizeColor, sanitizeUrl } from "./utils";
import { EMAIL_TOKENS, KUMASY_BRANDING, type EmailBranding } from "./tokens";
export interface RenderEmailLayoutParams {
@ -29,9 +29,10 @@ export function renderEmailLayout({
const safeTitle = escapeHtml(title);
const safeCompanyName = escapeHtml(branding.companyName);
const safeLogoAlt = escapeHtml(branding.logoAlt);
const safePrimaryColor = sanitizeColor(branding.primaryColor, KUMASY_BRANDING.primaryColor);
const headerLogoHtml = branding.logoUrl
? `<img src="${branding.logoUrl}" alt="${safeLogoAlt}" width="160" style="display: block; margin: 0 auto 16px; max-width: 160px; height: auto;" />`
? `<img src="${sanitizeUrl(branding.logoUrl)}" alt="${safeLogoAlt}" width="160" style="display: block; margin: 0 auto 16px; max-width: 160px; height: auto;" />`
: `<p style="margin: 0 0 12px; color: #ffffff; font-size: 15px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase;">${safeCompanyName}</p>`;
return `
@ -52,7 +53,7 @@ export function renderEmailLayout({
<!-- Header -->
<tr>
<td style="background-color: ${branding.primaryColor}; padding: 40px; text-align: center;">
<td style="background-color: ${safePrimaryColor}; padding: 40px; text-align: center;">
${headerLogoHtml}
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 600;">
${headerHeading}

View file

@ -5,7 +5,7 @@
*/
import { escapeHtml, singleLine } from "./utils";
import { renderDivider, renderInfoBox } from "./components";
import { renderInfoBox } from "./components";
import { renderEmailLayout } from "./layout";
import { KUMASY_BRANDING, type EmailBranding } from "./tokens";
@ -58,12 +58,6 @@ export function generateOnboardingCompleteEmail({
<p style="margin: 0; color: #666666; font-size: 14px; line-height: 1.6;">
Der Mitarbeiter kann sich ab sofort mit seinen Zugangsdaten anmelden.
</p>
${renderDivider(`
<p style="margin: 0; color: #999999; font-size: 13px; line-height: 1.6;">
Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht auf diese E-Mail.
</p>
`)}
`;
const html = renderEmailLayout({

View file

@ -22,12 +22,14 @@ export interface EmailBranding {
export const KUMASY_BRANDING: EmailBranding = {
companyName: "Kumasy",
// Statischer public/-Pfad auf der Production-Domain — bewusst NICHT über den
// Next.js /_next/image-Optimizer oder eine Vercel-Preview-Branch-URL, da
// E-Mail-Client-Bildproxys (z.B. Gmail) mit dynamisch generierten
// Optimizer-URLs und mit nicht-öffentlichen Preview-Deployments unzuverlässig
// umgehen.
logoUrl: "https://kumasy.de/logo-dark.png",
// TODO: auf eine stabile, statische Logo-URL setzen, sobald eine aktive
// Production-Domain feststeht (kumasy.de ist aktuell eine geparkte
// Hostinger-Domain, liefert kein Bild aus). Bis dahin Text-Wordmark-Fallback.
// Bewusst NICHT über den Next.js /_next/image-Optimizer oder eine
// Vercel-Preview-Branch-URL, da E-Mail-Client-Bildproxys (z.B. Gmail) mit
// dynamisch generierten Optimizer-URLs und nicht-öffentlichen
// Preview-Deployments unzuverlässig umgehen.
logoUrl: null,
logoAlt: "Kumasy",
primaryColor: "#1E293B",
accentColor: "#F4C00C",

View file

@ -40,3 +40,23 @@ export function sanitizeUrl(value: string): string {
export function singleLine(value: string): string {
return value.replace(/[\r\n]+/g, " ");
}
/**
* Validiert einen Farbwert für die Verwendung in einem style-Attribut.
* Erlaubt nur #hex (3/4/6/8-stellig), rgb()/rgba() und CSS-Keyword-Farben
* (Buchstaben only) – verhindert, dass ein nicht vertrauenswürdiger
* Branding-Wert (z.B. künftig manager-gesteuert) das style-Attribut
* verlässt und beliebiges Markup/CSS injiziert.
* Fällt bei ungültigem Wert auf den übergebenen Fallback zurück.
*/
export function sanitizeColor(value: string, fallback: string): string {
const isHex = /^#[0-9a-fA-F]{3,8}$/.test(value);
const isRgb = /^rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*(,\s*[\d.]+\s*)?\)$/.test(value);
const isKeyword = /^[a-zA-Z]+$/.test(value);
if (isHex || isRgb || isKeyword) {
return value;
}
return fallback;
}