import { DEFAULT_LOCALE, type Locale } from '@wellformed/shared'; import { config } from '../config.js'; /** * Outbound email, in the smallest shape that is still real. * * Two providers, one `fetch` each, no dependency and no template engine. Mail * from Wellformed is plain text on purpose: the three things it ever sends are a * link and a sentence explaining it, and plain text renders identically * everywhere, never lands in a spam filter for its markup, and cannot hide the * URL behind an anchor — which for a password reset is a security property. * * `send` never throws. A provider outage must not turn "reset your password" * into a 500 that also tells the caller whether the address exists. */ export interface Mail { to: string; subject: string; body: string; } export interface SendResult { sent: boolean; /** Set when a send was attempted and failed, for the log and nothing else. */ error?: string; } export function emailEnabled(): boolean { return config.email.provider !== 'none'; } export async function sendMail(mail: Mail): Promise { const { provider, apiKey, from } = config.email; if (provider === 'none') return { sent: false }; const request = provider === 'resend' ? { url: 'https://api.resend.com/emails', headers: { authorization: `Bearer ${apiKey}` }, body: { from, to: [mail.to], subject: mail.subject, text: mail.body }, } : { url: 'https://api.postmarkapp.com/email', headers: { 'x-postmark-server-token': apiKey }, body: { From: from, To: mail.to, Subject: mail.subject, TextBody: mail.body }, }; try { const response = await fetch(request.url, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json', ...request.headers }, body: JSON.stringify(request.body), signal: AbortSignal.timeout(10_000), }); if (!response.ok) { const detail = await response.text().catch(() => ''); return { sent: false, error: `${provider} answered ${response.status}: ${detail.slice(0, 300)}` }; } return { sent: true }; } catch (error) { return { sent: false, error: error instanceof Error ? error.message : String(error) }; } } /** Make a client-side path absolute for a message, which has no origin to ask. */ export function publicLink(path: string): string { return `${config.publicUrl}${path.startsWith('/') ? path : `/${path}`}`; } /** * The two messages, written per language rather than run through a translation * layer: two messages in two languages is four short texts, and a layer would * be bigger than all of them together. The account's own locale chooses; * an account that never chose gets English, same as the client's default. */ export function resetEmail(to: string, name: string, link: string, locale?: Locale | null): Mail { if ((locale ?? DEFAULT_LOCALE) === 'nl') { return { to, subject: 'Stel je Wellformed-wachtwoord opnieuw in', body: [ `Hallo ${name},`, '', 'Iemand heeft gevraagd het wachtwoord van dit Wellformed-account opnieuw in te', 'stellen. Open deze link om een nieuw wachtwoord te kiezen:', '', link, '', 'De link werkt één keer en verloopt na een uur. Heb je hier niet om gevraagd,', 'dan kun je dit bericht negeren — er is niets veranderd en je huidige', 'wachtwoord werkt nog gewoon.', ].join('\n'), }; } return { to, subject: 'Reset your Wellformed password', body: [ `Hello ${name},`, '', 'Someone asked to reset the password for this Wellformed account. Open this link', 'to choose a new one:', '', link, '', 'The link works once and expires in an hour. If you did not ask for this you', 'can ignore this message — nothing has changed, and your current password', 'still works.', ].join('\n'), }; } export function verifyEmail(to: string, name: string, link: string, locale?: Locale | null): Mail { if ((locale ?? DEFAULT_LOCALE) === 'nl') { return { to, subject: 'Bevestig je e-mailadres voor Wellformed', body: [ `Hallo ${name},`, '', 'Bevestig dit adres, zodat we je kunnen bereiken als je ooit niet meer kunt', 'inloggen:', '', link, '', 'De link werkt één keer en verloopt na een dag. Wellformed werkt hoe dan ook —', 'dit is alleen om je later een wachtwoordreset te kunnen sturen.', ].join('\n'), }; } return { to, subject: 'Confirm your Wellformed email address', body: [ `Hello ${name},`, '', 'Confirm this address so that you can be reached if you are ever locked out:', '', link, '', 'The link works once and expires in a day. Wellformed works either way — this is', 'only about being able to send you a password reset later.', ].join('\n'), }; }