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 //# sourceMappingURL=panelSmtpAdapter.js.map