/** * Auth mail templates for the Supabase Send Email hook (WL-05, WL-13). * * Swedish-only, like the other user-facing mail templates in lib/email/ * (see .claude/rules/i18n.md): these are transactional mails, not UI chrome, * so they deliberately do not go through next-intl. * * The appName parameter carries the brand of the requesting host (resolved * via resolveBrandByHost from the redirect_to origin); the default is the * platform name, so unbranded hosts render canonical mail. */ import { escapeHtml, sanitizeSubjectLine } from './user-text' export type AuthEmailActionType = | 'signup' | 'recovery' | 'magiclink' | 'invite' | 'email_change' | 'email_change_current' | 'reauthentication' interface AuthEmailContent { subject: string heading: string body: (appName: string) => string cta: string } const CONTENT: Record = { signup: { subject: 'Bekräfta din e-postadress', heading: 'Bekräfta din e-postadress', body: (appName) => `Klicka på knappen nedan för att bekräfta din e-postadress och slutföra din registrering hos ${appName}.`, cta: 'Bekräfta e-postadress', }, recovery: { subject: 'Återställ ditt lösenord', heading: 'Återställ ditt lösenord', body: (appName) => `Vi har tagit emot en begäran om att återställa lösenordet för ditt konto hos ${appName}. Klicka på knappen nedan för att välja ett nytt lösenord.`, cta: 'Återställ lösenord', }, magiclink: { subject: 'Din inloggningslänk', heading: 'Logga in', body: (appName) => `Klicka på knappen nedan för att logga in hos ${appName}.`, cta: 'Logga in', }, invite: { subject: 'Du har blivit inbjuden', heading: 'Du har blivit inbjuden', body: (appName) => `Du har blivit inbjuden till ${appName}. Klicka på knappen nedan för att skapa ditt konto.`, cta: 'Acceptera inbjudan', }, email_change: { subject: 'Bekräfta din nya e-postadress', heading: 'Bekräfta din nya e-postadress', body: (appName) => `Klicka på knappen nedan för att bekräfta din nya e-postadress hos ${appName}.`, cta: 'Bekräfta ny e-postadress', }, email_change_current: { subject: 'Godkänn ändrad e-postadress', heading: 'Godkänn ändrad e-postadress', body: (appName) => `En ändring av e-postadressen för ditt konto hos ${appName} har begärts. Klicka på knappen nedan för att godkänna ändringen från din nuvarande adress.`, cta: 'Godkänn ändringen', }, reauthentication: { subject: 'Din verifieringskod', heading: 'Din verifieringskod', body: (appName) => `Ange koden nedan för att bekräfta din identitet hos ${appName}.`, cta: '', }, } // Availability first: an action type this module does not know (Supabase can // add new mail classes) still produces a usable mail with the verify link // rather than dropping the send. const FALLBACK_CONTENT: AuthEmailContent = { subject: 'Bekräfta din åtgärd', heading: 'Bekräfta din åtgärd', body: (appName) => `Klicka på knappen nedan för att fortsätta hos ${appName}.`, cta: 'Fortsätt', } const IGNORE_NOTE = 'Om du inte begärde detta kan du bortse från det här meddelandet.' export interface AuthEmailInput { actionType: string appName: string /** Verify URL on the originating host (token_hash flow). */ actionUrl?: string /** One-time code, for reauthentication mail (no link). */ otpCode?: string } export interface AuthEmail { subject: string html: string text: string } function contentFor(actionType: string): AuthEmailContent { return (CONTENT as Record)[actionType] ?? FALLBACK_CONTENT } export function buildAuthEmail(input: AuthEmailInput): AuthEmail { const content = contentFor(input.actionType) const appName = input.appName const safeAppName = escapeHtml(appName) const body = content.body(appName) const action = input.otpCode ? `
${escapeHtml(input.otpCode)}
` : input.actionUrl ? `
${escapeHtml(content.cta)}
` : '' const html = ` ${escapeHtml(content.subject)}

${safeAppName.toUpperCase()}

${escapeHtml(content.heading)}

${escapeHtml(body)}

${action}

${IGNORE_NOTE}

` let text = `${content.heading}\n\n${body}\n\n` if (input.otpCode) { text += `Kod: ${input.otpCode}\n\n` } else if (input.actionUrl) { text += `${content.cta}: ${input.actionUrl}\n\n` } text += IGNORE_NOTE return { subject: sanitizeSubjectLine(content.subject), html, text, } }