Add email notification system for contact form submissions
Implement a new mailer module to send email notifications upon contact form submission, integrating with SMTP settings and formatting the email content. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 3a6f3b4b-2588-4a9e-9f58-fbe9c763e594 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/koutl3W Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
1d4ab71636
commit
67ce78ae3b
@ -1,11 +1 @@
|
||||
[
|
||||
{
|
||||
"id": 1772805974560,
|
||||
"name": "sebastjan",
|
||||
"email": "sebastjan.artic@folx.tv",
|
||||
"subject": "Allgemeine Anfrage",
|
||||
"message": "hallo ",
|
||||
"createdAt": "2026-03-06T14:06:14.560Z",
|
||||
"read": false
|
||||
}
|
||||
]
|
||||
[]
|
||||
|
||||
75
server/mailer.ts
Normal file
75
server/mailer.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST || "mail.folx.tv",
|
||||
port: parseInt(process.env.SMTP_PORT || "465"),
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER || "web@folx.tv",
|
||||
pass: process.env.SMTP_PASSWORD || "",
|
||||
},
|
||||
});
|
||||
|
||||
interface ContactMessage {
|
||||
name: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export async function sendContactEmail(data: ContactMessage): Promise<boolean> {
|
||||
const subjectLine = data.subject
|
||||
? `[FOLX TV Kontakt] ${data.subject} – von ${data.name}`
|
||||
: `[FOLX TV Kontakt] Nachricht von ${data.name}`;
|
||||
|
||||
const htmlBody = `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<div style="background: #1a1a2e; padding: 20px; border-radius: 8px 8px 0 0;">
|
||||
<h2 style="color: #fff; margin: 0; font-size: 18px;">Neue Kontaktanfrage</h2>
|
||||
</div>
|
||||
<div style="background: #f9f9f9; padding: 24px; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 8px 8px;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; font-weight: bold; color: #555; width: 100px; vertical-align: top;">Name:</td>
|
||||
<td style="padding: 8px 12px; color: #222;">${escapeHtml(data.name)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; font-weight: bold; color: #555; vertical-align: top;">E-Mail:</td>
|
||||
<td style="padding: 8px 12px; color: #222;"><a href="mailto:${escapeHtml(data.email)}" style="color: #1a73e8;">${escapeHtml(data.email)}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; font-weight: bold; color: #555; vertical-align: top;">Betreff:</td>
|
||||
<td style="padding: 8px 12px; color: #222;">${escapeHtml(data.subject || "Keine Angabe")}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr style="border: none; border-top: 1px solid #e0e0e0; margin: 16px 0;" />
|
||||
<div style="padding: 8px 12px; color: #222; white-space: pre-wrap; line-height: 1.6;">${escapeHtml(data.message)}</div>
|
||||
</div>
|
||||
<p style="text-align: center; color: #999; font-size: 11px; margin-top: 16px;">Gesendet über das Kontaktformular auf folx.tv</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
from: `"FOLX TV Website" <${process.env.SMTP_FROM || "web@folx.tv"}>`,
|
||||
to: process.env.CONTACT_TO || "office@folx.tv",
|
||||
replyTo: data.email,
|
||||
subject: subjectLine,
|
||||
html: htmlBody,
|
||||
text: `Neue Kontaktanfrage\n\nName: ${data.name}\nE-Mail: ${data.email}\nBetreff: ${data.subject || "Keine Angabe"}\n\nNachricht:\n${data.message}`,
|
||||
});
|
||||
console.log(`[mailer] Contact email sent successfully (from: ${data.email})`);
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
console.error(`[mailer] Failed to send email:`, err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
@ -8,6 +8,7 @@ import { analyzeAllArticleImages, getCachedFocalPoints } from "./focal-point";
|
||||
import { optimizeImage } from "./image-optimizer";
|
||||
import { getAuthUrl, exchangeCodeForTokens, isConnected, fetchGalleryFromDropbox, getValidAccessToken, migrateGalleryToCloudinary } from "./dropbox";
|
||||
import { listCloudinaryGalleryDetailed, deleteFromCloudinary } from "./cloudinary";
|
||||
import { sendContactEmail } from "./mailer";
|
||||
import multer from "multer";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
@ -473,7 +474,7 @@ export async function registerRoutes(
|
||||
if (fs.existsSync(contactPath)) {
|
||||
messages = JSON.parse(fs.readFileSync(contactPath, "utf-8"));
|
||||
}
|
||||
messages.push({
|
||||
const entry = {
|
||||
id: Date.now(),
|
||||
name,
|
||||
email,
|
||||
@ -481,9 +482,12 @@ export async function registerRoutes(
|
||||
message,
|
||||
createdAt: new Date().toISOString(),
|
||||
read: false,
|
||||
});
|
||||
};
|
||||
messages.push(entry);
|
||||
fs.writeFileSync(contactPath, JSON.stringify(messages, null, 2));
|
||||
console.log(`[contact] New message from ${name} <${email}>: ${subject || "No subject"}`);
|
||||
|
||||
const emailSent = await sendContactEmail({ name, email, subject: subject || "", message });
|
||||
console.log(`[contact] New message from ${name} <${email}>: ${subject || "No subject"} (email ${emailSent ? "sent" : "failed"})`);
|
||||
res.json({ ok: true });
|
||||
} catch (err: any) {
|
||||
res.status(500).json({ message: err.message });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user