Commit Graph

7 Commits

Author SHA1 Message Date
joachimhummel
d34902cd5f 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
2026-05-15 17:14:35 +00:00
joachimhummel
3ef76a4433 Task #17: Send a branded notification email to Joachim on contact form submission
Replaced the minimal plain-HTML notification email (sent to jh@unixweb.de) with a
fully branded HTML email that matches the existing confirmation email layout.

Changes:
- Added `buildNotificationEmail()` function in `artifacts/api-server/src/routes/contact.ts`
- Same visual structure as `buildConfirmationEmail()`: blue header with JH monogram,
  white card body, divider, branded footer with links
- Sender details (name, e-mail, subject, message) are displayed in a structured field
  table inside the card body, with subtle background shading and label rows
- "Absender antworten →" CTA button links to `mailto:{email}?subject=Re: {subject}`
  so Joachim can reply to the sender in one click
- Subject field row is conditionally rendered (omitted when no subject was provided)
- All user-supplied values are escaped through the existing `escapeHtml()` helper
- The first `sendTransacEmail` call now passes `buildNotificationEmail(...)` instead
  of the inline minimal HTML string

No schema, API contract, or dependency changes. Typecheck passes cleanly after
running codegen to resolve a pre-existing import gap in @workspace/api-zod.

Replit-Task-Id: 601aa064-22da-4553-9ad0-a15f82563eb6
2026-05-15 17:11:43 +00:00
joachimhummel
84e0f9ddfd 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
2026-05-15 16:44:50 +00:00
joachimhummel
a111d2930d Add rate-limiting to /api/contact to prevent spam
Task #11: Formular gegen Spam schützen

- Installed `express-rate-limit` (^8.5.2) as a runtime dependency in
  `@workspace/api-server`
- Created a rate limiter (5 requests per IP per hour, 1-hour sliding window)
  using `rateLimit()` from express-rate-limit
- Applied the limiter as inline middleware on POST /contact so it runs before
  the handler
- On limit exceeded the API returns HTTP 429 with a German-language JSON error:
  { success: false, message: "Zu viele Anfragen. Bitte versuchen Sie es in
  einer Stunde erneut." }
- Uses `standardHeaders: "draft-8"` (RateLimit header group) and disables
  legacy X-RateLimit-* headers
- Added `app.set("trust proxy", 1)` in app.ts so that Express reads the real
  client IP from X-Forwarded-For (set by Replit's reverse proxy), ensuring
  the rate limit is applied per actual client rather than per proxy IP
- No other changes to the contact handler flow

No deviations from the task description.

Replit-Task-Id: de2cecbd-511f-4046-8e87-567ec96e19fb
2026-05-15 16:39:43 +00:00
joachimhummel
f9e363f446 Task #10: Send automatic confirmation email to contact form submitters
After a visitor submits the contact form, Brevo now sends two emails:
1. The existing notification to jh@unixweb.de with the message details
2. A new confirmation email to the sender's address

The confirmation email:
- Is sent from "Joachim Hummel <jh@unixweb.de>"
- Addresses the sender by name
- Thanks them and sets response-time expectations (1-2 Werktage)
- Includes both plain-text and HTML versions
- Is logged separately via req.log.info for observability

If either email fails, the entire request returns a 500 error (atomic
behavior — both succeed or neither does from the user's perspective).

File changed:
- artifacts/api-server/src/routes/contact.ts

Also ran `pnpm --filter @workspace/api-spec run codegen` to fix a
pre-existing typecheck failure caused by stale generated lib output.
Typecheck passes cleanly after codegen.

Replit-Task-Id: a5d51157-6bd1-48c7-ba04-68e7d951eeab
2026-05-15 16:30:13 +00:00
joachimhummel
e9f0d1ed98 Merged changes from o10aupva/main
Replit-Task-Id: 96838fc6-bf00-4a8d-ae18-84ba08feec56
2026-05-15 16:11:01 +00:00
agent
758e23e905 Initial commit 2026-05-13 03:54:14 +00:00