feat: polished on-brand HTML confirmation email (task #15)
Replaced the plain 4-line HTML confirmation email with a fully styled, table-based HTML email that matches Joachim Hummel's portfolio branding. Changes: - artifacts/api-server/src/routes/contact.ts - Added buildConfirmationEmail(name: string): string helper function - Replaced inline htmlContent template literal with a call to the new helper - Extended textContent plain-text fallback with email/separator footer line Email design details: - Full DOCTYPE + <html lang="de"> structure for broad client compatibility - All styles are inline (no <style> blocks) — compatible with Gmail, Outlook, Apple Mail - Table-based layout throughout (no CSS grid/flexbox, no <div> dividers) for Outlook - Divider uses a zero-height table cell with border-bottom instead of a <div> - Brand blue header (#3f4ff4 ≈ hsl(234 89% 60%)) with frosted "JH" monogram badge - White card body on slate-100 page background matching portfolio palette - Personalised greeting using the sender's name (XSS-safe via escapeHtml) - Blue accent sub-heading "Ihre Anfrage ist eingegangen." - 1–2 Werktage response-time promise in bold - CTA button "Portfolio ansehen →" linking to joachim-hummel.de - Footer with name, role tagline, email + website links, and social links (Blog at blog.unixweb.de and n8n Creators profile — both present in portfolio) - Legal disclaimer note at the bottom No new dependencies introduced. Pre-existing typecheck errors in the file (missing express-rate-limit types, missing api-zod export) are unrelated to this change and pre-date this task. Replit-Task-Id: fd961a0e-5bc4-4d29-a6c3-e90227307fe0
This commit is contained in:
@@ -75,12 +75,11 @@ router.post("/contact", contactRateLimit, async (req, res) => {
|
|||||||
"",
|
"",
|
||||||
"Mit freundlichen Grüßen",
|
"Mit freundlichen Grüßen",
|
||||||
"Joachim Hummel",
|
"Joachim Hummel",
|
||||||
|
"",
|
||||||
|
"—",
|
||||||
|
"jh@unixweb.de",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
htmlContent: `
|
htmlContent: buildConfirmationEmail(name),
|
||||||
<p>Hallo ${escapeHtml(name)},</p>
|
|
||||||
<p>vielen Dank für Ihre Nachricht! Ich habe Ihre Anfrage erhalten und melde mich in der Regel innerhalb von <strong>1–2 Werktagen</strong> bei Ihnen.</p>
|
|
||||||
<p>Mit freundlichen Grüßen<br />Joachim Hummel</p>
|
|
||||||
`,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
req.log.info({ to: email }, "Confirmation email sent to sender via Brevo");
|
req.log.info({ to: email }, "Confirmation email sent to sender via Brevo");
|
||||||
@@ -104,4 +103,115 @@ function escapeHtml(text: string): string {
|
|||||||
.replace(/'/g, "'");
|
.replace(/'/g, "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildConfirmationEmail(name: string): string {
|
||||||
|
const safeName = escapeHtml(name);
|
||||||
|
const brandBlue = "#3f4ff4";
|
||||||
|
const textDark = "#0f172a";
|
||||||
|
const textMid = "#334155";
|
||||||
|
const textMuted = "#64748b";
|
||||||
|
const textLight = "#94a3b8";
|
||||||
|
const borderColor = "#e2e8f0";
|
||||||
|
const bgPage = "#f1f5f9";
|
||||||
|
const bgCard = "#ffffff";
|
||||||
|
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title>Anfrage erhalten</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:0;background-color:${bgPage};font-family:'Inter','Helvetica Neue',Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" role="presentation" style="background-color:${bgPage};">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding:48px 16px 40px;">
|
||||||
|
|
||||||
|
<table width="560" cellpadding="0" cellspacing="0" border="0" role="presentation" style="max-width:560px;width:100%;">
|
||||||
|
|
||||||
|
<!-- ── Header ── -->
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="background-color:${brandBlue};border-radius:12px 12px 0 0;padding:32px 40px 28px;">
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="width:44px;height:44px;background-color:rgba(255,255,255,0.18);border-radius:10px;font-size:18px;font-weight:700;color:#ffffff;letter-spacing:-0.5px;line-height:44px;">
|
||||||
|
JH
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p style="margin:10px 0 0;font-size:13px;font-weight:600;color:rgba(255,255,255,0.80);letter-spacing:1.5px;text-transform:uppercase;">Joachim Hummel</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- ── Body ── -->
|
||||||
|
<tr>
|
||||||
|
<td style="background-color:${bgCard};padding:40px 40px 32px;">
|
||||||
|
<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 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};">1–2 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>
|
||||||
|
|
||||||
|
<!-- CTA button -->
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="background-color:${brandBlue};border-radius:8px;">
|
||||||
|
<a href="https://joachim-hummel.de" target="_blank" style="display:inline-block;padding:13px 28px;font-size:14px;font-weight:600;color:#ffffff;text-decoration:none;letter-spacing:0.2px;border-radius:8px;">Portfolio ansehen →</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- ── Divider ── -->
|
||||||
|
<tr>
|
||||||
|
<td style="background-color:${bgCard};padding:0 40px;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;line-height:0;border-bottom:1px solid ${borderColor};"> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- ── Footer ── -->
|
||||||
|
<tr>
|
||||||
|
<td style="background-color:${bgCard};border-radius:0 0 12px 12px;padding:24px 40px 32px;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p style="margin:0 0 2px;font-size:14px;font-weight:700;color:${textDark};">Joachim Hummel</p>
|
||||||
|
<p style="margin:0 0 14px;font-size:13px;color:${textMuted};">Webentwicklung & Softwarelösungen</p>
|
||||||
|
<!-- Contact links -->
|
||||||
|
<p style="margin:0 0 10px;font-size:13px;color:${textLight};">
|
||||||
|
<a href="mailto:jh@unixweb.de" style="color:${brandBlue};text-decoration:none;">jh@unixweb.de</a>
|
||||||
|
<span style="color:${borderColor};"> • </span>
|
||||||
|
<a href="https://joachim-hummel.de" target="_blank" style="color:${brandBlue};text-decoration:none;">joachim-hummel.de</a>
|
||||||
|
</p>
|
||||||
|
<!-- Social links -->
|
||||||
|
<p style="margin:0;font-size:13px;color:${textLight};">
|
||||||
|
<a href="https://blog.unixweb.de" target="_blank" style="color:${brandBlue};text-decoration:none;">Blog</a>
|
||||||
|
<span style="color:${borderColor};"> • </span>
|
||||||
|
<a href="https://n8n.io/creators/jhummel/" target="_blank" style="color:${brandBlue};text-decoration:none;">n8n Creators</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- ── Legal note ── -->
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding:20px 0 0;">
|
||||||
|
<p style="margin:0;font-size:12px;color:${textLight};line-height:1.6;">Diese E-Mail wurde automatisch versendet – bitte antworten Sie nicht direkt darauf.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
Reference in New Issue
Block a user