83 lines
4.1 KiB
JavaScript
83 lines
4.1 KiB
JavaScript
import { getSiteIntegrations } from '../payload/index.js';
|
|
/**
|
|
* Payload email adapter backed by the SMTP settings in the SiteIntegrations
|
|
* global.
|
|
*
|
|
* Why this exists: Payload builds its email adapter once, at boot, from the
|
|
* config — which would normally mean SMTP credentials living in env vars and a
|
|
* redeploy to change them. This adapter instead resolves SMTP on every send, so
|
|
* an editor can change the mailbox in the admin panel and the next email uses
|
|
* it.
|
|
*
|
|
* Wiring it into the config means `payload.sendEmail` works everywhere — which
|
|
* includes the form-builder's own submission emails (the ones an editor
|
|
* configures per form under "Emails"). Those go out over the panel's SMTP with
|
|
* no extra code.
|
|
*
|
|
* Boot-time constraints shape two details:
|
|
* - `defaultFromAddress` / `defaultFromName` must be returned synchronously, so
|
|
* they're placeholders; the real from-address is applied per message below.
|
|
* - nodemailer is imported dynamically inside sendEmail, so merely loading the
|
|
* Payload config (or running `generate:importmap`) doesn't pull it in.
|
|
*/ export const panelSmtpAdapter = (args = {})=>({ payload })=>({
|
|
name: 'ipal-panel-smtp',
|
|
defaultFromAddress: args.fallbackFromAddress ?? 'noreply@localhost',
|
|
defaultFromName: args.fallbackFromName ?? 'Website',
|
|
sendEmail: async (message)=>{
|
|
const smtp = await getSiteIntegrations(payload);
|
|
const host = smtp.smtpHost;
|
|
const port = smtp.smtpPort ?? 587;
|
|
const user = smtp.smtpUser;
|
|
const pass = smtp.smtpPassword;
|
|
if (!host || !user || !pass) {
|
|
payload.logger.error('[ipal] Email not sent: SMTP is not configured in Site Integrations.');
|
|
return {
|
|
error: 'SMTP is not configured in Site Integrations.',
|
|
sent: false
|
|
};
|
|
}
|
|
// The panel is the source of truth for the sender; fall back to whatever
|
|
// the caller set (Payload fills in defaultFromAddress when unset).
|
|
const fromAddress = smtp.smtpFromAddress;
|
|
const fromName = smtp.smtpFromName;
|
|
const from = fromAddress ? fromName ? `${fromName} <${fromAddress}>` : fromAddress : message.from;
|
|
// Defence in depth against header injection. Modern nodemailer already
|
|
// normalises CRLF in standard headers, but the form-builder interpolates
|
|
// user data into the subject via {{field}} placeholders, so strip any
|
|
// newlines from header-bound values before they reach the transport.
|
|
const stripCRLF = (v)=>String(v ?? '').replace(/[\r\n]+/g, ' ').trim();
|
|
const { default: nodemailer } = await import('nodemailer');
|
|
const transporter = nodemailer.createTransport({
|
|
auth: {
|
|
pass,
|
|
user
|
|
},
|
|
host,
|
|
port,
|
|
secure: port === 465
|
|
});
|
|
try {
|
|
const info = await transporter.sendMail({
|
|
...message,
|
|
from,
|
|
...message.subject ? {
|
|
subject: stripCRLF(message.subject)
|
|
} : {}
|
|
});
|
|
return {
|
|
messageId: info.messageId,
|
|
sent: true
|
|
};
|
|
} catch (err) {
|
|
// Never surface SMTP internals to the caller — a form submission
|
|
// shouldn't fail loudly because the mailbox is misconfigured.
|
|
payload.logger.error(`[ipal] Email send failed: ${err.message}`);
|
|
return {
|
|
error: 'Failed to send email.',
|
|
sent: false
|
|
};
|
|
}
|
|
}
|
|
});
|
|
|
|
//# sourceMappingURL=panelSmtpAdapter.js.map
|