/** * SMS sending — per-org, bring your own key. * * Supported providers: * - Twilio * * Each charity pastes their Twilio credentials in Settings. * SMS comes from THEIR number. */ interface SmsConfig { provider: string accountSid: string authToken: string fromNumber: string } interface SendResult { success: boolean messageId?: string error?: string } /** * Send an SMS using the org's configured provider. */ export async function sendSms( config: SmsConfig, to: string, body: string ): Promise { if (!config.accountSid || !config.authToken || !config.fromNumber) { return { success: false, error: "SMS not configured" } } try { if (config.provider === "twilio") { return await sendViaTwilio(config, to, body) } return { success: false, error: `Unknown provider: ${config.provider}` } } catch (err) { console.error("[SMS]", err) return { success: false, error: String(err) } } } async function sendViaTwilio( config: SmsConfig, to: string, body: string ): Promise { // Normalize UK number let phone = to.replace(/[\s\-\(\)]/g, "") if (phone.startsWith("0")) phone = "+44" + phone.slice(1) if (!phone.startsWith("+")) phone = "+" + phone const url = `https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json` const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64") const res = await fetch(url, { method: "POST", headers: { "Authorization": `Basic ${auth}`, "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ To: phone, From: config.fromNumber, Body: body, }), }) const data = await res.json() if (data.sid) { return { success: true, messageId: data.sid } } return { success: false, error: data.message || "Twilio error" } }