feat(mail): add smtp email delivery

This commit is contained in:
Schubert Ferenc 2026-07-04 00:06:20 +02:00
parent faddaa91d0
commit 3456b04ed4
25 changed files with 529 additions and 60 deletions

View file

@ -0,0 +1,94 @@
"use client";
import { useRef, useState, type FormEvent } from "react";
import type { PublicSmtpSettings } from "@/lib/mail/types";
type ApiResponse = {
message?: string;
ok?: boolean;
settings?: PublicSmtpSettings;
};
export default function SmtpSettingsForm({ initialSettings, configured }: { initialSettings: PublicSmtpSettings; configured: boolean }) {
const formRef = useRef<HTMLFormElement>(null);
const [settings, setSettings] = useState(initialSettings);
const [isConfigured, setIsConfigured] = useState(configured);
const [message, setMessage] = useState("");
const [error, setError] = useState("");
const [pending, setPending] = useState(false);
async function submitForm(form: HTMLFormElement, action: "save" | "test") {
setMessage("");
setError("");
setPending(true);
const formData = new FormData(form);
formData.set("action", action);
try {
const response = await fetch("/api/admin/smtp", { method: "POST", body: formData });
const result = await response.json() as ApiResponse;
if (result.settings) {
setSettings(result.settings);
setIsConfigured(Boolean(result.settings.host && result.settings.username && result.settings.hasPassword && result.settings.fromAddress && result.settings.recipientAddress));
}
if (!response.ok) throw new Error(result.message ?? "SMTP-Aktion fehlgeschlagen.");
setMessage(result.message ?? "Aktion erfolgreich.");
} catch (err) {
setError(err instanceof Error ? err.message : "SMTP-Aktion fehlgeschlagen.");
} finally {
setPending(false);
}
}
function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
void submitForm(event.currentTarget, "save");
}
function test() {
if (formRef.current) void submitForm(formRef.current, "test");
}
return (
<>
<section className="admin-card">
<h2>Status</h2>
<p className="admin-muted">SMTP ist {isConfigured ? "konfiguriert" : "nicht konfiguriert"}.</p>
{settings.lastTestAt && <p>Letzte Testmail: {new Date(settings.lastTestAt).toLocaleString("de-DE")} ({settings.lastTestStatus})</p>}
{settings.lastDeliveryAt && <p>Letzter Formularversand: {new Date(settings.lastDeliveryAt).toLocaleString("de-DE")} ({settings.lastDeliveryStatus})</p>}
{settings.lastError && <p className="error">Letzter Fehler: {settings.lastError}</p>}
<p className="notice">Für Apple Mail/iCloud bitte smtp.mail.me.com, Port 587, STARTTLS und ein app-spezifisches Passwort verwenden.</p>
</section>
{message && <p className="success admin-message">{message}</p>}
{error && <p className="error admin-message">{error}</p>}
<form ref={formRef} className="admin-card admin-form" onSubmit={submit}>
<label>SMTP Host<input name="host" defaultValue={settings.host} placeholder="smtp.mail.me.com" /></label>
<label>SMTP Port<input name="port" defaultValue={settings.port} inputMode="numeric" placeholder="587" /></label>
<label>SMTP Benutzername<input name="username" defaultValue={settings.username} placeholder="name@example.com" /></label>
<label>
SMTP Passwort
<input name="password" type="password" placeholder={settings.hasPassword ? "Passwort ist gesetzt" : "App-spezifisches Passwort"} autoComplete="new-password" />
</label>
<label>
Verschlüsselung
<select name="security" defaultValue={settings.security}>
<option value="starttls">STARTTLS</option>
<option value="tls">TLS</option>
<option value="none">Keine</option>
</select>
</label>
<label>Absenderadresse<input name="fromAddress" type="email" defaultValue={settings.fromAddress} /></label>
<label>Antwortadresse<input name="replyToAddress" type="email" defaultValue={settings.replyToAddress} /></label>
<label>Empfängeradresse<input name="recipientAddress" type="email" defaultValue={settings.recipientAddress} /></label>
<label>BCC optional<input name="bccAddress" type="email" defaultValue={settings.bccAddress} /></label>
<div className="actions">
<button className="button" type="submit" disabled={pending}>Speichern</button>
<button className="button light" type="button" disabled={pending} onClick={test}>Test-E-Mail senden</button>
</div>
</form>
</>
);
}