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 { optimizeImage } from "./image-optimizer";
|
||||||
import { getAuthUrl, exchangeCodeForTokens, isConnected, fetchGalleryFromDropbox, getValidAccessToken, migrateGalleryToCloudinary } from "./dropbox";
|
import { getAuthUrl, exchangeCodeForTokens, isConnected, fetchGalleryFromDropbox, getValidAccessToken, migrateGalleryToCloudinary } from "./dropbox";
|
||||||
import { listCloudinaryGalleryDetailed, deleteFromCloudinary } from "./cloudinary";
|
import { listCloudinaryGalleryDetailed, deleteFromCloudinary } from "./cloudinary";
|
||||||
|
import { sendContactEmail } from "./mailer";
|
||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@ -473,7 +474,7 @@ export async function registerRoutes(
|
|||||||
if (fs.existsSync(contactPath)) {
|
if (fs.existsSync(contactPath)) {
|
||||||
messages = JSON.parse(fs.readFileSync(contactPath, "utf-8"));
|
messages = JSON.parse(fs.readFileSync(contactPath, "utf-8"));
|
||||||
}
|
}
|
||||||
messages.push({
|
const entry = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
@ -481,9 +482,12 @@ export async function registerRoutes(
|
|||||||
message,
|
message,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
read: false,
|
read: false,
|
||||||
});
|
};
|
||||||
|
messages.push(entry);
|
||||||
fs.writeFileSync(contactPath, JSON.stringify(messages, null, 2));
|
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 });
|
res.json({ ok: true });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(500).json({ message: err.message });
|
res.status(500).json({ message: err.message });
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user