import nodemailer from "nodemailer";
import { queueEmail } from "./email-queue";
// Port 587 = STARTTLS (bestätigt erreichbar vom Server + Docker-Container)
// Port 465 = SSL/TLS (auf diesem Server geblockt – nicht verwenden)
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT ?? 587),
secure: false, // STARTTLS auf Port 587
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
connectionTimeout: 15000, // 15 s (war default 2 min – schlägt jetzt schneller fehl)
greetingTimeout: 10000,
socketTimeout: 20000,
tls: {
rejectUnauthorized: false,
ciphers: "SSLv3", // Kompatibilitätsmodus für ältere SMTP-Server
},
});
export interface AnfrageEmailData {
anfrageId: string;
firma: string;
telefon: string;
email: string;
positionen: {
maschineName: string;
mietbeginn: string;
mietende: string;
gesamtTage: number;
lieferung: boolean;
lieferadresse: string;
anmerkung: string;
tagessatz: number | null;
preisStufe?: "tag" | "woche" | "monat" | null;
zubehoer?: {
id: string;
name: string;
preisTag: number | null;
preisWoche?: number | null;
preisMonat?: number | null;
}[];
}[];
}
// ─── Preisberechnung ──────────────────────────────────────────────────────
const VERSICHERUNG_PROZENT = 7.5;
const MWST_PROZENT = 19;
function fmt(n: number) {
return n.toLocaleString("de-DE", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function formatDatum(iso: string) {
return new Date(iso).toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
}
/** Alle Sonntage zwischen von und bis (inkl.) als formatierte Strings */
function getSonntage(von: string, bis: string): string[] {
const result: string[] = [];
const end = new Date(bis);
for (let d = new Date(von); d <= end; d.setDate(d.getDate() + 1)) {
if (d.getDay() === 0) {
result.push(
new Date(d).toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" })
);
}
}
return result;
}
/** Kalendertage (inkl. Sonntage) zwischen von und bis */
export function getKalenderTage(von: string, bis: string): number {
const start = new Date(von);
const end = new Date(bis);
return Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1;
}
function zubehoerTagessatz(
z: { preisTag: number | null; preisWoche?: number | null; preisMonat?: number | null },
stufe: string | null | undefined
): number | null {
if (stufe === "monat" && z.preisMonat != null) return z.preisMonat;
if (stufe === "woche" && z.preisWoche != null) return z.preisWoche;
return z.preisTag;
}
function positionNetto(p: AnfrageEmailData["positionen"][number]): number {
const maschine = p.tagessatz != null ? p.tagessatz * p.gesamtTage : 0;
const zubehoer = (p.zubehoer ?? []).reduce((sum, z) => {
const rate = zubehoerTagessatz(z, p.preisStufe);
return sum + (rate != null ? rate * p.gesamtTage : 0);
}, 0);
return maschine + zubehoer;
}
function buildPreisBlock(positionen: AnfrageEmailData["positionen"]) {
const gesamtNetto = positionen.reduce((s, p) => s + positionNetto(p), 0);
const versicherungBetrag = gesamtNetto * (VERSICHERUNG_PROZENT / 100);
const mwstBetrag = (gesamtNetto + versicherungBetrag) * (MWST_PROZENT / 100);
const gesamtBrutto = gesamtNetto + versicherungBetrag + mwstBetrag;
const posRows = positionen.map((p, i) => {
const hatPreis = p.tagessatz != null;
const netto = hatPreis ? positionNetto(p) : null;
const zubehoerMitPreis = (p.zubehoer ?? []).filter(
(z) => zubehoerTagessatz(z, p.preisStufe) != null
);
// Sonntage berechnen
const kalenderTage = getKalenderTage(p.mietbeginn, p.mietende);
const sonntage = getSonntage(p.mietbeginn, p.mietende);
const hatSonntage = sonntage.length > 0 && kalenderTage !== p.gesamtTage;
const zubehoerRows = zubehoerMitPreis
.map((z) => {
const rate = zubehoerTagessatz(z, p.preisStufe)!;
return `
| ↳ ${z.name} |
${fmt(rate * p.gesamtTage)} € |
`;
})
.join("");
const sonntageHtml = hatSonntage
? `
${kalenderTage} Kalendertage – ${sonntage.length} Sonntag${sonntage.length > 1 ? "e" : ""} nicht berechnet
(${sonntage.join(", ")})
`
: "";
const details = [
p.tagessatz ? `${p.tagessatz} €/Tag · ${p.gesamtTage} berechnete Tag${p.gesamtTage !== 1 ? "e" : ""}` : "",
p.lieferung ? `Lieferung: ${p.lieferadresse}` : "",
p.anmerkung ? `Anmerkung: ${p.anmerkung}` : "",
]
.filter(Boolean)
.map((d) => `${d}`)
.join("");
return `
|
${i + 1}. ${p.maschineName}
|
${formatDatum(p.mietbeginn)} – ${formatDatum(p.mietende)}
${details}
${sonntageHtml}
|
${netto != null ? `${fmt(netto)} €` : "Auf Anfrage"}
|
${zubehoerRows}`;
}).join("");
const html = `
| Maschine / Gerät |
Zeitraum & Details |
Mietpreis |
${posRows}
| Zwischensumme (netto) |
${fmt(gesamtNetto)} € |
| + Versicherung (${VERSICHERUNG_PROZENT} %) |
${fmt(versicherungBetrag)} € |
| Summe netto inkl. Versicherung |
${fmt(gesamtNetto + versicherungBetrag)} € |
| + MwSt. (${MWST_PROZENT} %) |
${fmt(mwstBetrag)} € |
| Gesamtbetrag (brutto) |
${fmt(gesamtBrutto)} € |
`;
const text =
positionen
.map((p, i) => {
const netto = positionNetto(p);
const kalenderTage = getKalenderTage(p.mietbeginn, p.mietende);
const sonntage = getSonntage(p.mietbeginn, p.mietende);
const hatSonntage = sonntage.length > 0 && kalenderTage !== p.gesamtTage;
const lines = [
`${i + 1}. ${p.maschineName}`,
` ${formatDatum(p.mietbeginn)} – ${formatDatum(p.mietende)}`,
hatSonntage
? ` ${kalenderTage} Kalendertage – ${sonntage.length} Sonntag${sonntage.length > 1 ? "e" : ""} nicht berechnet (${sonntage.join(", ")})`
: null,
p.tagessatz
? ` ${p.tagessatz} €/Tag × ${p.gesamtTage} Tage = ${fmt(p.tagessatz * p.gesamtTage)} € Maschinenmiete`
: " Preis auf Anfrage",
...(p.zubehoer ?? [])
.filter((z) => zubehoerTagessatz(z, p.preisStufe) != null)
.map((z) => {
const rate = zubehoerTagessatz(z, p.preisStufe)!;
return ` + ${z.name}: ${fmt(rate * p.gesamtTage)} €`;
}),
p.lieferung ? ` Lieferung: ${p.lieferadresse}` : null,
p.anmerkung ? ` Anmerkung: ${p.anmerkung}` : null,
` Positionssumme: ${fmt(netto)} €`,
].filter((l): l is string => l != null && l !== "");
return lines.join("\n");
})
.join("\n\n") +
`\n\n${"─".repeat(40)}\nZwischensumme (netto): ${fmt(gesamtNetto)} €\n+ Versicherung ${VERSICHERUNG_PROZENT} %: ${fmt(versicherungBetrag)} €\n+ MwSt. ${MWST_PROZENT} %: ${fmt(mwstBetrag)} €\n${"─".repeat(40)}\nGesamtbetrag (brutto): ${fmt(gesamtBrutto)} €`;
return { html, text, gesamtNetto, versicherungBetrag, mwstBetrag, gesamtBrutto };
}
// ─── Robuster Send mit Queue-Fallback ────────────────────────────────────
interface MailOptions {
from: string;
to: string | undefined;
replyTo?: string;
subject: string;
text: string;
html: string;
}
async function sendWithFallback(options: MailOptions, label: string) {
if (!options.to) {
console.error(`[Mailer] Kein Empfänger für "${label}" – Mail übersprungen`);
return;
}
try {
await transporter.sendMail(options);
console.log(`[Mailer] ✓ Mail "${label}" an "${options.to}" gesendet`);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.error(`[Mailer] ✗ Mail "${label}" fehlgeschlagen (${msg}) – in Queue gespeichert`);
await queueEmail({
mail_from: options.from,
mail_to: options.to,
reply_to: options.replyTo,
subject: options.subject,
html: options.html,
body_text: options.text,
});
}
}
// ─── Kontaktformular ──────────────────────────────────────────────────────
export interface KontaktEmailData {
name: string;
anrede?: string;
telefon: string;
email: string;
betreff: string;
nachricht?: string;
}
export async function sendeKontaktEmail(data: KontaktEmailData) {
const anrede = data.anrede
? `${data.anrede.charAt(0).toUpperCase() + data.anrede.slice(1)} `
: "";
const html = `
Mietpark Hahn
Neue Kontaktanfrage
Kontaktanfrage von ${anrede}${data.name}
${data.nachricht ? `
` : ""}
`;
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: process.env.SMTP_TO,
replyTo: data.email,
subject: `Kontaktanfrage: ${anrede}${data.name} – ${data.betreff} – Mietpark Hahn`,
text: `Neue Kontaktanfrage\n\nName: ${anrede}${data.name}\nTelefon: ${data.telefon}\nE-Mail: ${data.email}\nBetreff: ${data.betreff}${data.nachricht ? `\n\nNachricht:\n${data.nachricht}` : ""}`,
html,
}, `Kontaktanfrage ${data.name}`);
}
// ─── Kunden-Eingangsbestätigung ───────────────────────────────────────────
export async function sendeKundenEingangsbestaetigung(data: AnfrageEmailData) {
const { html: preisHtml, text: preisText } = buildPreisBlock(data.positionen);
// Build a simple equipment list
const equipmentHtml = `
Ihre gemieteten Geräte
${data.positionen
.map((p) => `
-
${p.maschineName}
· ${formatDatum(p.mietbeginn)} bis ${formatDatum(p.mietende)}
`)
.join("")}
`;
const html = `
Mietpark Hahn
Ihre Mietanfrage ist eingegangen
Vielen Dank für Ihre Anfrage!
Guten Tag ${data.firma},
wir haben Ihre Mietanfrage erhalten und werden uns schnellstmöglich bei Ihnen melden.
${equipmentHtml}
Detaillierte Preisübersicht
${preisHtml}
Hinweis: Die angezeigten Preise sind Mietpreise inkl. ${VERSICHERUNG_PROZENT} % Versicherung und ${MWST_PROZENT} % MwSt.
Die Bestätigung erfolgt nach Prüfung durch den Verleih.
Bei Fragen erreichen Sie uns unter
${process.env.COMPANY_PHONE ?? ""}
oder per E-Mail an
${process.env.SMTP_FROM ?? ""}
`;
const equipmentList = data.positionen
.map((p) => `• ${p.maschineName} (${formatDatum(p.mietbeginn)} bis ${formatDatum(p.mietende)})`)
.join("\n");
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: data.email,
subject: `Ihre Mietanfrage ist eingegangen – Mietpark Hahn`,
text: `Guten Tag ${data.firma},\n\nvielen Dank für Ihre Mietanfrage. Wir werden uns schnellstmöglich bei Ihnen melden.\n\nIhre gemieteten Geräte:\n${equipmentList}\n\nDetaillierte Preisübersicht:\n\n${preisText}\n\nHinweis: Die angezeigten Preise sind Mietpreise inkl. ${VERSICHERUNG_PROZENT} % Versicherung und ${MWST_PROZENT} % MwSt. Die Bestätigung erfolgt nach Prüfung durch den Verleih.\n\nMit freundlichen Grüßen\nMietpark Hahn\nAnfrage-ID: ${data.anfrageId}\n\nUnsere AGB finden Sie unter: ${process.env.APP_URL ?? "https://www.mietparkhahn.de"}/agb`,
html,
}, `Kundeneingang ${data.firma}`);
}
// ─── Admin-Benachrichtigung (Vermieter) ───────────────────────────────────
export async function sendeAnfrageEmail(data: AnfrageEmailData) {
const { html: preisHtml, text: preisText } = buildPreisBlock(data.positionen);
// Action-Tokens für direkte Email-Links generieren
const { createActionToken } = await import("@/lib/admin-auth");
const [tokenBestaetigt, tokenAbgelehnt, tokenAbgeschlossen] = await Promise.all([
createActionToken(data.anfrageId, "bestaetigt"),
createActionToken(data.anfrageId, "abgelehnt"),
createActionToken(data.anfrageId, "abgeschlossen"),
]);
const baseUrl = process.env.APP_URL ?? "https://www.mietparkhahn.de";
const urlBestaetigt = `${baseUrl}/api/admin/anfragen-action?token=${tokenBestaetigt}`;
const urlAbgelehnt = `${baseUrl}/api/admin/anfragen-action?token=${tokenAbgelehnt}`;
const urlAbgeschlossen = `${baseUrl}/api/admin/anfragen-action?token=${tokenAbgeschlossen}`;
const html = `
Mietpark Hahn
Neue Mietanfrage eingegangen
Neue Mietanfrage
Kontaktdaten Kunde
Angefragte Maschinen & Preisübersicht
${preisHtml}
`;
const posText = `Kontakt:\nFirma/Name: ${data.firma}\nTelefon: ${data.telefon}\nE-Mail: ${data.email}\n\nMaschinen & Preise:\n\n${preisText}`;
const actionLinks = `\nQuickaktion aus Email:\n✓ Bestätigen: ${urlBestaetigt}\n✕ Ablehnen: ${urlAbgelehnt}\n✓ Abschließen: ${urlAbgeschlossen}`;
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: process.env.SMTP_TO,
subject: `Neue Mietanfrage: ${data.firma} – ${data.positionen.length} Gerät${data.positionen.length !== 1 ? "e" : ""} – Mietpark Hahn`,
text: `Neue Mietanfrage\n\n${posText}\n\nAdmin: ${baseUrl}/admin/anfragen/${data.anfrageId}${actionLinks}`,
html,
}, `Admin-Anfrage ${data.firma}`);
}
// ─── Kunden-Statusbenachrichtigung ────────────────────────────────────────
export interface StatusEmailData {
anfrageId: string;
firma: string;
email: string;
neuerStatus: "bestaetigt" | "abgelehnt" | "abgeschlossen";
notizen?: string;
positionen?: {
maschineName: string;
mietbeginn: string;
mietende: string;
gesamtTage: number;
lieferung: boolean;
lieferadresse: string;
anmerkung: string;
tagessatz: number | null;
preisStufe?: "tag" | "woche" | "monat" | null;
zubehoer?: {
id: string;
name: string;
preisTag: number | null;
preisWoche?: number | null;
preisMonat?: number | null;
}[];
}[];
}
const STATUS_TEXTE: Record<
StatusEmailData["neuerStatus"],
{ betreff: string; headline: string; text: string; farbe: string }
> = {
bestaetigt: {
betreff: "Ihre Mietanfrage wurde bestätigt – Mietpark Hahn",
headline: "Ihre Anfrage ist bestätigt!",
text: "Wir freuen uns, Ihnen mitteilen zu können, dass Ihre Mietanfrage bestätigt wurde. Wir setzen uns zur Vorbereitung mit Ihnen in Verbindung.",
farbe: "#16a34a",
},
abgelehnt: {
betreff: "Zu Ihrer Mietanfrage – Mietpark Hahn",
headline: "Zu Ihrer Mietanfrage",
text: "Leider können wir Ihre Mietanfrage im angefragten Zeitraum nicht erfüllen. Bitte kontaktieren Sie uns für alternative Termine.",
farbe: "#dc2626",
},
abgeschlossen: {
betreff: "Ihre Miete wurde abgeschlossen – Mietpark Hahn",
headline: "Vielen Dank!",
text: "Ihre Miete wurde erfolgreich abgeschlossen. Wir hoffen, Sie waren mit unserem Service zufrieden und freuen uns auf Ihre nächste Anfrage.",
farbe: "#475569",
},
};
export async function sendeKundenStatusEmail(data: StatusEmailData) {
const info = STATUS_TEXTE[data.neuerStatus];
// Geräte-Übersicht (falls vorhanden)
const equipmentHtml = data.positionen ? `
Ihre gemieteten Geräte
${data.positionen
.map((p) => `
-
${p.maschineName}
· ${formatDatum(p.mietbeginn)} bis ${formatDatum(p.mietende)}
`)
.join("")}
` : "";
// Preisblock (falls vorhanden)
const preisBlock = data.positionen ? buildPreisBlock(data.positionen) : null;
const preisHtml = preisBlock?.html ?? "";
const html = `
Mietpark Hahn
Update zu Ihrer Mietanfrage
${info.headline}
Guten Tag ${data.firma},
${info.text}
${equipmentHtml}
${data.positionen && preisHtml ? `
Detaillierte Preisübersicht
${preisHtml}
` : ""}
${
data.notizen
? `
NACHRICHT VOM VERLEIH
${data.notizen}
`
: ""
}
Bei Fragen erreichen Sie uns unter
${process.env.SMTP_FROM ?? ""}
`;
const equipmentList = data.positionen
? data.positionen
.map((p) => `• ${p.maschineName} (${formatDatum(p.mietbeginn)} bis ${formatDatum(p.mietende)})`)
.join("\n")
: "";
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: data.email,
subject: info.betreff,
text: `Guten Tag ${data.firma},\n\n${info.text}${equipmentList ? `\n\nIhre gemieteten Geräte:\n${equipmentList}` : ""}${data.notizen ? `\n\nNachricht vom Verleih:\n${data.notizen}` : ""}\n\nMit freundlichen Grüßen\nMietpark Hahn\n\nUnsere AGB finden Sie unter: ${process.env.APP_URL ?? "https://www.mietparkhahn.de"}/agb`,
html,
}, `Status-${data.neuerStatus} ${data.firma}`);
}
// ─── Registrierungsbestätigung ─────────────────────────────────────────────
export async function sendeRegistrierungsBestaetigung(data: {
email: string;
firma?: string;
bestaetigungsLink: string;
}) {
const name = data.firma || data.email;
const html = `
Mietpark Hahn
E-Mail-Adresse bestätigen
Guten Tag ${name},
vielen Dank für Ihre Registrierung bei Mietpark Hahn. Bitte bestätigen Sie Ihre
E-Mail-Adresse, um Zugang zu Ihrem Kundenbereich zu erhalten.
E-Mail-Adresse bestätigen →
Dieser Link ist 24 Stunden gültig. Falls Sie sich nicht registriert haben, können Sie
diese E-Mail ignorieren.
`;
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: data.email,
subject: "Bitte bestätigen Sie Ihre E-Mail-Adresse – Mietpark Hahn",
text: `Guten Tag ${name},\n\nbitte bestätigen Sie Ihre E-Mail-Adresse:\n\n${data.bestaetigungsLink}\n\nDieser Link ist 24 Stunden gültig.\n\nMit freundlichen Grüßen\nMietpark Hahn\n\nUnsere AGB finden Sie unter: ${process.env.APP_URL ?? "https://www.mietparkhahn.de"}/agb`,
html,
}, `Registrierung ${name}`);
}
// ─── Maschinen-Bedarfscheck ────────────────────────────────────────────────
export async function sendeBedarfscheckAnKunde(data: {
name: string;
email: string;
}) {
const html = `
Mietpark Hahn
Ihr kostenloser Maschinen-Bedarfscheck
Guten Tag ${data.name},
vielen Dank für Ihre Anfrage! Hier sind die Antworten zu den 7 wichtigsten Fragen vor der Maschinen-Miete:
-
Welche Maschinenklasse passt zu meinem Projekt?
Minibagger (1,5–3t) für Garten- und enge Baustellenarbeiten, Kettenbagger (5–16t) für Baugruben und Schachtarbeiten, Radlader für Materialtransport und Verdichtung.
-
Welches Zubehör brauche ich?
Tieflöffel für Erde, Grabenräumlöffel für Leitungsgräben, Abbruchhammer für Beton/Asphalt, Greifer für Schüttgut. Die genaue Auswahl beraten wir gerne persönlich.
-
Wie plane ich Mietdauer & Lieferung richtig?
Immer 1 Puffertag einkalkulieren. Lieferung mindestens 1 Werktag vorab anfragen. In Frühjahr/Herbst frühzeitig reservieren, da die Nachfrage hoch ist.
-
Was muss ich bei der Übergabe prüfen?
Betriebsstunden und Ölstände notieren, Schäden fotografieren, Bedienungsanleitung mitnehmen. Dies schützt Sie vor Haftungsansprüchen.
-
Welche Versicherung ist sinnvoll?
Privatpersonen sollten ihre Haftpflichtversicherung prüfen. Firmen benötigen Baugeräteversicherung. Eine Maschinenbruchversicherung über uns ist optional, aber empfohlen.
-
Wie spare ich durch Wochenmiete?
Wochentarife sind ca. 30–40% günstiger als 5× Tagessatz. Schon ab 4 Tagen Einsatz lohnt sich die Wochenmiete gegenüber Tagesmietung.
-
Was tun, wenn die Maschine ausfällt?
Sofort den Verleih anrufen. Wenn kein Verschulden vorliegt, entstehen keine Kosten während des Ausfalls. Ersatz wird schnellstmöglich bereitgestellt.
Nächste Schritte: Rufen Sie uns an oder stellen Sie eine Anfrage mit den gewünschten Maschinen. Unser Team prüft sofort die Verfügbarkeit und meldet sich bei Ihnen!
Bei Fragen erreichen Sie uns unter
${process.env.COMPANY_PHONE ?? ""}
oder per E-Mail an
${process.env.SMTP_FROM ?? ""}
`;
const text = `Guten Tag ${data.name},
vielen Dank für Ihre Anfrage! Hier sind die Antworten zu den 7 wichtigsten Fragen vor der Maschinen-Miete:
1. Welche Maschinenklasse passt zu meinem Projekt?
Minibagger (1,5–3t) für Garten- und enge Baustellenarbeiten, Kettenbagger (5–16t) für Baugruben und Schachtarbeiten, Radlader für Materialtransport und Verdichtung.
2. Welches Zubehör brauche ich?
Tieflöffel für Erde, Grabenräumlöffel für Leitungsgräben, Abbruchhammer für Beton/Asphalt, Greifer für Schüttgut. Die genaue Auswahl beraten wir gerne persönlich.
3. Wie plane ich Mietdauer & Lieferung richtig?
Immer 1 Puffertag einkalkulieren. Lieferung mindestens 1 Werktag vorab anfragen. In Frühjahr/Herbst frühzeitig reservieren, da die Nachfrage hoch ist.
4. Was muss ich bei der Übergabe prüfen?
Betriebsstunden und Ölstände notieren, Schäden fotografieren, Bedienungsanleitung mitnehmen. Dies schützt Sie vor Haftungsansprüchen.
5. Welche Versicherung ist sinnvoll?
Privatpersonen sollten ihre Haftpflichtversicherung prüfen. Firmen benötigen Baugeräteversicherung. Eine Maschinenbruchversicherung über uns ist optional, aber empfohlen.
6. Wie spare ich durch Wochenmiete?
Wochentarife sind ca. 30–40% günstiger als 5× Tagessatz. Schon ab 4 Tagen Einsatz lohnt sich die Wochenmiete gegenüber Tagesmietung.
7. Was tun, wenn die Maschine ausfällt?
Sofort den Verleih anrufen. Wenn kein Verschulden vorliegt, entstehen keine Kosten während des Ausfalls. Ersatz wird schnellstmöglich bereitgestellt.
Nächste Schritte: Rufen Sie uns an oder stellen Sie eine Anfrage mit den gewünschten Maschinen.
Mit freundlichen Grüßen
Mietpark Hahn
Unsere AGB finden Sie unter: ${process.env.APP_URL ?? "https://www.mietparkhahn.de"}/agb`;
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: data.email,
subject: "Ihr kostenloser Maschinen-Bedarfscheck – Mietpark Hahn",
text,
html,
}, `Bedarfscheck ${data.name}`);
}
export async function sendeBedarfscheckAnVermieter(data: {
name: string;
email: string;
telefon: string;
adresse: string | null;
}) {
const html = `
Mietpark Hahn
Neuer Lead aus Bedarfscheck
Neuer Lead aus Maschinen-Bedarfscheck
Kontaktdaten
Quelle: Maschinen-Bedarfscheck (Lead Magnet)
`;
const text = `Neuer Lead aus Maschinen-Bedarfscheck
Kontaktdaten:
Name/Firma: ${data.name}
E-Mail: ${data.email}
Telefon: ${data.telefon}
${data.adresse ? `Adresse: ${data.adresse}` : ""}
Quelle: Maschinen-Bedarfscheck`;
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: process.env.SMTP_TO,
subject: `Neuer Lead: ${data.name} – Maschinen-Bedarfscheck`,
text,
html,
}, `Lead ${data.name}`);
}
// ─── Email-Änderungsbestätigung (Admin-Profil) ────────────────────────────────
export async function sendeEmailAenderungsBestaetigung(data: {
adminEmail: string;
adminName: string;
bestaetigungsLink: string;
}) {
const html = `
Mietpark Hahn
Admin-Bereich: E-Mail-Änderung
Guten Tag ${data.adminName},
Sie haben eine neue E-Mail-Adresse für Ihr Admin-Konto hinterlegt.
Bitte bestätigen Sie diese Änderung, um die neue E-Mail-Adresse zu aktivieren.
E-Mail-Adresse bestätigen →
Wichtig: Ihre bisherige E-Mail-Adresse bleibt aktiv, bis Sie diesen Link klicken.
Dieser Link ist 24 Stunden gültig.
Falls Sie diese E-Mail nicht angefordert haben, können Sie sie ignorieren.
Ihre E-Mail-Adresse wird nicht geändert.
`;
const text = `Guten Tag ${data.adminName},
Sie haben eine neue E-Mail-Adresse für Ihr Admin-Konto hinterlegt.
Bitte bestätigen Sie diese Änderung unter folgendem Link:
${data.bestaetigungsLink}
Dieser Link ist 24 Stunden gültig.
WICHTIG: Ihre bisherige E-Mail-Adresse bleibt aktiv, bis Sie den Link oben bestätigen.
Mit freundlichen Grüßen
Mietpark Hahn
Unsere AGB finden Sie unter: ${process.env.APP_URL ?? "https://www.mietparkhahn.de"}/agb`;
await sendWithFallback({
from: `"Mietpark Hahn" <${process.env.SMTP_FROM}>`,
to: data.adminEmail,
subject: "Neue E-Mail-Adresse bestätigen – Mietpark Hahn",
text,
html,
}, `Email-Änderung ${data.adminName}`);
}