import { env } from "cloudflare:workers"; const LOOPS_TRANSACTIONAL_URL = "https://app.loops.so/api/v1/transactional"; function getRequiredEnv(name: string) { const value: unknown = Reflect.get(env, name); const trimmed = typeof value === "string" ? value.trim() : ""; if (!trimmed) { throw new Error(`${name} is required in hosted mode`); } return trimmed; } function getHostedAuthEmailConfig() { return { apiKey: getRequiredEnv("LOOPS_API_KEY"), verificationTemplateId: getRequiredEnv( "LOOPS_TRANSACTIONAL_VERIFY_EMAIL_ID", ), passwordResetTemplateId: getRequiredEnv( "LOOPS_TRANSACTIONAL_RESET_PASSWORD_ID", ), }; } async function sendLoopsTransactionalEmail({ apiKey, email, transactionalId, dataVariables, }: { apiKey: string; email: string; transactionalId: string; dataVariables: Record; }) { const response = await fetch(LOOPS_TRANSACTIONAL_URL, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ transactionalId, email, addToAudience: false, dataVariables, }), }); if (response.ok) { return; } const errorPayload = await response.json().catch(() => null); console.error("Loops transactional email error:", { status: response.status, email, transactionalId, errorPayload, }); throw new Error( `Failed to send Loops transactional email (${response.status})`, ); } export async function sendHostedVerificationEmail({ email, confirmationUrl, }: { email: string; confirmationUrl: string; }) { const config = getHostedAuthEmailConfig(); await sendLoopsTransactionalEmail({ apiKey: config.apiKey, email, transactionalId: config.verificationTemplateId, dataVariables: { appName: "OpenSEO", confirmationUrl, }, }); } export async function sendHostedPasswordResetEmail({ email, resetUrl, }: { email: string; resetUrl: string; }) { const config = getHostedAuthEmailConfig(); await sendLoopsTransactionalEmail({ apiKey: config.apiKey, email, transactionalId: config.passwordResetTemplateId, dataVariables: { appName: "OpenSEO", resetUrl, }, }); }