Add SMTP admin settings and contact form delivery

This commit is contained in:
MOH
2026-03-08 06:27:09 +01:00
parent 993a4673d8
commit 5ca4819bcb
28 changed files with 2087 additions and 55 deletions
+172
View File
@@ -0,0 +1,172 @@
import nodemailer, { type SendMailOptions } from "nodemailer";
import { getMailSettings } from "@/lib/app-config";
import type { AppLocale } from "@/lib/locale";
import type { MailSettings } from "@/lib/mail-settings";
type MailTransportOptions = {
host: string;
port: number;
secure: boolean;
auth: {
user: string;
pass: string;
};
};
type MailTransport = {
sendMail: (options: SendMailOptions) => Promise<unknown>;
};
type CreateTransport = (options: MailTransportOptions) => MailTransport;
type SendMailDeps = {
settings?: MailSettings;
createTransport?: CreateTransport;
};
type SendMailInput = {
to: string;
subject: string;
text: string;
replyTo?: string;
};
type ContactMessageInput = {
name: string;
email: string;
phone?: string;
company?: string;
message: string;
locale: AppLocale;
};
function requireValue(value: string, message: string) {
if (!value.trim()) {
throw new Error(message);
}
return value.trim();
}
function getConfiguredTransportSettings(settings: MailSettings) {
const smtpHost = requireValue(settings.smtp.host, "SMTP host is required.");
const smtpUsername = requireValue(settings.smtp.username, "SMTP username is required.");
const smtpPassword = requireValue(settings.smtp.password, "SMTP password is required.");
const fromEmail = requireValue(settings.sender.email, "From email is required.");
return {
smtpHost,
smtpUsername,
smtpPassword,
fromEmail,
};
}
function getConfiguredRecipient(settings: MailSettings, recipientKey: "contact" | "test") {
if (recipientKey === "contact") {
return requireValue(
settings.recipients.contact || settings.recipients.test,
"Contact recipient email is required.",
);
}
return requireValue(
settings.recipients.test || settings.recipients.contact,
"Test recipient email is required.",
);
}
export function createSmtpTransport(
settings: MailSettings,
createTransport: CreateTransport = nodemailer.createTransport,
) {
const { smtpHost, smtpUsername, smtpPassword } = getConfiguredTransportSettings(settings);
return createTransport({
host: smtpHost,
port: settings.smtp.port,
secure: settings.smtp.secure,
auth: {
user: smtpUsername,
pass: smtpPassword,
},
});
}
export async function sendMail(
input: SendMailInput,
deps: SendMailDeps = {},
) {
const settings = deps.settings ?? await getMailSettings();
const { fromEmail } = getConfiguredTransportSettings(settings);
const transport = createSmtpTransport(settings, deps.createTransport);
await transport.sendMail({
from: settings.sender.name ? `${settings.sender.name} <${fromEmail}>` : fromEmail,
to: input.to,
subject: input.subject,
text: input.text,
replyTo: input.replyTo,
});
}
export async function sendContactMessage(
input: ContactMessageInput,
deps: SendMailDeps = {},
) {
const settings = deps.settings ?? await getMailSettings();
const recipient = getConfiguredRecipient(settings, "contact");
await sendMail(
{
to: recipient,
subject: "New contact message",
replyTo: input.email,
text: [
"A new contact message was submitted.",
"",
`Name: ${input.name}`,
`Email: ${input.email}`,
`Phone: ${input.phone?.trim() || "-"}`,
`Company: ${input.company?.trim() || "-"}`,
`Locale: ${input.locale}`,
`Submitted at: ${new Date().toISOString()}`,
"",
"Message:",
input.message,
].join("\n"),
},
{
settings,
createTransport: deps.createTransport,
},
);
}
export async function sendTestEmail(
deps: SendMailDeps = {},
) {
const settings = deps.settings ?? await getMailSettings();
const recipient = getConfiguredRecipient(settings, "test");
const { fromEmail } = getConfiguredTransportSettings(settings);
await sendMail(
{
to: recipient,
subject: "SMTP test email",
text: [
"This is a backend SMTP test email.",
"",
`Timestamp: ${new Date().toISOString()}`,
`Configured from email: ${fromEmail}`,
`Configured recipient: ${recipient}`,
`Environment: ${process.env.NODE_ENV ?? "development"}`,
].join("\n"),
},
{
settings,
createTransport: deps.createTransport,
},
);
}