feat: echo submitted message back in confirmation email

Task #18 — Send a copy of the submitted message back to the contact in the confirmation email.

Changes made to artifacts/api-server/src/routes/contact.ts:

- Updated buildConfirmationEmail() signature from (name: string) to
  ({ name, subject, message }) so it can accept and render the original submission.

- Added a visually distinct "Ihre Nachricht" quoted block to the HTML template:
  - Light gray background (#f8fafc) matching the existing bgField colour
  - Subtle 3px left border (#e2e8f0) styled as a blockquote
  - Rounded right corners (border-radius: 0 6px 6px 0)
  - ALL-CAPS label "IHRE NACHRICHT" in the same style as field labels elsewhere
  - Optional subject line rendered in semi-bold if provided
  - Message body with white-space:pre-wrap to preserve line breaks

- All user-supplied values (subject, message) passed through the existing
  escapeHtml() helper — XSS-safe.

- Updated the plain-text fallback: adds a "—" delimited quoted section
  with optional "Betreff:" line before the message body.

- Updated the call site to pass { name, subject, message } to
  buildConfirmationEmail().

No new dependencies. Pre-existing TS error (SendContactMessageBody missing
from @workspace/api-zod) is unrelated to this task and unchanged.

Replit-Task-Id: 63279220-bb35-4721-bbd6-86182301a697
This commit is contained in:
joachimhummel
2026-05-15 17:14:35 +00:00
parent 565cdf975c
commit d34902cd5f

View File

@@ -67,13 +67,19 @@ router.post("/contact", contactRateLimit, async (req, res) => {
"", "",
"vielen Dank für Ihre Nachricht! Ich habe Ihre Anfrage erhalten und melde mich in der Regel innerhalb von 12 Werktagen bei Ihnen.", "vielen Dank für Ihre Nachricht! Ich habe Ihre Anfrage erhalten und melde mich in der Regel innerhalb von 12 Werktagen bei Ihnen.",
"", "",
"Zur Erinnerung, hier ist Ihre Nachricht:",
"—",
...(subject ? [`Betreff: ${subject}`, ""] : []),
message,
"—",
"",
"Mit freundlichen Grüßen", "Mit freundlichen Grüßen",
"Joachim Hummel", "Joachim Hummel",
"", "",
"—", "—",
"jh@unixweb.de", "jh@unixweb.de",
].join("\n"), ].join("\n"),
htmlContent: buildConfirmationEmail(name), htmlContent: buildConfirmationEmail({ name, subject, message }),
}); });
req.log.info({ to: email }, "Confirmation email sent to sender via Brevo"); req.log.info({ to: email }, "Confirmation email sent to sender via Brevo");
@@ -255,8 +261,18 @@ function buildNotificationEmail({
</html>`; </html>`;
} }
function buildConfirmationEmail(name: string): string { function buildConfirmationEmail({
name,
subject,
message,
}: {
name: string;
subject?: string;
message: string;
}): string {
const safeName = escapeHtml(name); const safeName = escapeHtml(name);
const safeSubject = subject ? escapeHtml(subject) : null;
const safeMessage = escapeHtml(message);
const brandBlue = "#3f4ff4"; const brandBlue = "#3f4ff4";
const textDark = "#0f172a"; const textDark = "#0f172a";
const textMid = "#334155"; const textMid = "#334155";
@@ -265,6 +281,7 @@ function buildConfirmationEmail(name: string): string {
const borderColor = "#e2e8f0"; const borderColor = "#e2e8f0";
const bgPage = "#f1f5f9"; const bgPage = "#f1f5f9";
const bgCard = "#ffffff"; const bgCard = "#ffffff";
const bgQuote = "#f8fafc";
return `<!DOCTYPE html> return `<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml"> <html lang="de" xmlns="http://www.w3.org/1999/xhtml">
@@ -301,7 +318,24 @@ function buildConfirmationEmail(name: string): string {
<p style="margin:0 0 6px;font-size:22px;font-weight:700;color:${textDark};line-height:1.3;">Vielen Dank, ${safeName}!</p> <p style="margin:0 0 6px;font-size:22px;font-weight:700;color:${textDark};line-height:1.3;">Vielen Dank, ${safeName}!</p>
<p style="margin:0 0 20px;font-size:13px;font-weight:500;color:${brandBlue};letter-spacing:0.3px;">Ihre Anfrage ist eingegangen.</p> <p style="margin:0 0 20px;font-size:13px;font-weight:500;color:${brandBlue};letter-spacing:0.3px;">Ihre Anfrage ist eingegangen.</p>
<p style="margin:0 0 16px;font-size:15px;line-height:1.75;color:${textMid};">Schön, dass Sie sich gemeldet haben. Ich habe Ihre Nachricht erhalten und werde mich in der Regel innerhalb von <strong style="color:${textDark};">12 Werktagen</strong> bei Ihnen melden.</p> <p style="margin:0 0 16px;font-size:15px;line-height:1.75;color:${textMid};">Schön, dass Sie sich gemeldet haben. Ich habe Ihre Nachricht erhalten und werde mich in der Regel innerhalb von <strong style="color:${textDark};">12 Werktagen</strong> bei Ihnen melden.</p>
<p style="margin:0 0 32px;font-size:15px;line-height:1.75;color:${textMid};">Bis dahin können Sie gerne mein Portfolio besuchen oder mir direkt eine E-Mail schicken.</p> <p style="margin:0 0 24px;font-size:15px;line-height:1.75;color:${textMid};">Bis dahin können Sie gerne mein Portfolio besuchen oder mir direkt eine E-Mail schicken.</p>
<!-- ── Quoted message ── -->
<table width="100%" cellpadding="0" cellspacing="0" border="0" role="presentation" style="margin-bottom:32px;">
<tr>
<td style="padding-left:4px;">
<table width="100%" cellpadding="0" cellspacing="0" border="0" role="presentation" style="background-color:${bgQuote};border-left:3px solid ${borderColor};border-radius:0 6px 6px 0;">
<tr>
<td style="padding:16px 20px;">
<p style="margin:0 0 10px;font-size:11px;font-weight:600;color:${textLight};letter-spacing:1px;text-transform:uppercase;">Ihre Nachricht</p>
${safeSubject ? `<p style="margin:0 0 8px;font-size:13px;font-weight:600;color:${textMuted};">Betreff: ${safeSubject}</p>` : ""}
<p style="margin:0;font-size:14px;line-height:1.75;color:${textMid};white-space:pre-wrap;">${safeMessage}</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- CTA button --> <!-- CTA button -->
<table cellpadding="0" cellspacing="0" border="0" role="presentation"> <table cellpadding="0" cellspacing="0" border="0" role="presentation">