feat: Kontaktformular für ein Erstgespräch
Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 4m31s

Neuer Abschnitt nach den Fragen, im Menü als Erstgespräch. Anfrage geht als E-Mail an die Veranstalter-Adresse (Admin) bzw. ADMIN_NOTIFY_EMAIL, sofort zugestellt, sonst über den Worker wiederholt. Gleicher Schutz wie die Buchung: nur JSON von der eigenen Seite, Rate Limit (5/h je IP), Prüfung auf dem Server, Honeypot gegen Bots, doppeltes Absenden ergibt eine Mail. Keine automatische Mail an die angegebene Adresse (sonst Missbrauch für Spam an Dritte).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-06 11:37:27 +02:00
1 parent d45700a9ef
commit 7745d5862d
9 files changed
+336 -1

No files matched your search

+19 -1
View File
@@ -3,12 +3,13 @@
* bestaetigten Status — eine Teilzahlung heisst nie "vollstaendig bezahlt".
*/
import type { Booking, Installment } from "@/generated/prisma/client";
import type { ContactRequest } from "./contact";
import { escapeHtml, type EmailDraft } from "./email";
import { appUrl } from "./env";
import { formatEuro } from "./money";
import { PLAN_LABEL } from "./payment-plan";
import { PHASE_LABEL } from "./pricing";
import { formatDate } from "./time";
import { formatDate, formatDateTime } from "./time";
type BookingWithInstallments = Booking & { installments: Installment[] };
@@ -163,3 +164,20 @@ export function reviewCustomerEmail(booking: Booking, token: string): EmailDraft
),
);
}
/** Anfrage fuer ein Erstgespraech an den Veranstalter (src/lib/contact.ts). */
export function contactInquiryEmail(to: string, inquiry: ContactRequest, dedupeKey: string, receivedAt: Date): EmailDraft {
const subject = `Anfrage Erstgespräch: ${inquiry.name}`;
const lines = [
`Über die Webseite kam eine Anfrage für ein Erstgespräch (${formatDateTime(receivedAt)}).`,
[
`Name: ${inquiry.name}`,
`E-Mail: ${inquiry.email}`,
`Telefon: ${inquiry.phone ?? "–"}`,
`Erreichbar: ${inquiry.availability ?? "–"}`,
].join("\n"),
inquiry.message ? `Nachricht:\n${inquiry.message}` : "Keine Nachricht angegeben.",
];
const reply = `mailto:${encodeURIComponent(inquiry.email)}?subject=${encodeURIComponent(`Dein Erstgespräch – ${BRAND}`)}`;
return { dedupeKey, to, subject: `${subject} | ${BRAND}`, ...wrap(subject, lines, { href: reply, label: "Antworten" }) };
}