diff --git a/.env.example b/.env.example index 47af96aa..3ade6c84 100644 --- a/.env.example +++ b/.env.example @@ -33,7 +33,6 @@ ADMIN_EMAIL= # Direct phone-call visibility PHONE_AGENT_INTERNAL_TOKEN= PHONE_CALL_SUMMARY_FROM_EMAIL= -RESEND_API_KEY= # Placeholder for a later LiveKit rollout LIVEKIT_URL= diff --git a/.env.staging.example b/.env.staging.example index 01fb56dc..4c1ec233 100644 --- a/.env.staging.example +++ b/.env.staging.example @@ -13,7 +13,6 @@ ADMIN_EMAIL= PHONE_AGENT_INTERNAL_TOKEN= PHONE_CALL_SUMMARY_FROM_EMAIL= -RESEND_API_KEY= USESEND_API_KEY= USESEND_BASE_URL= diff --git a/lib/email.ts b/lib/email.ts index c9fbcaa5..96cf2a0b 100644 --- a/lib/email.ts +++ b/lib/email.ts @@ -46,15 +46,19 @@ export async function sendTransactionalEmail({ subject, html, replyTo, + from, }: { to: string; subject: string; html: string; replyTo?: string; + from?: string; }) { + const sender = from || FROM_EMAIL; + if (usesend) { return usesend.emails.send({ - from: FROM_EMAIL, + from: sender, to, subject, html, @@ -65,7 +69,7 @@ export async function sendTransactionalEmail({ if (sesClient) { return sesClient.send( new SendEmailCommand({ - FromEmailAddress: FROM_EMAIL, + FromEmailAddress: sender, Destination: { ToAddresses: [to], }, diff --git a/lib/phone-calls.ts b/lib/phone-calls.ts index 36b2515a..27a05d6d 100644 --- a/lib/phone-calls.ts +++ b/lib/phone-calls.ts @@ -1,4 +1,5 @@ import { businessConfig } from "@/lib/seo-config"; +import { isEmailConfigured, sendTransactionalEmail } from "@/lib/email"; export type AdminPhoneCallTurn = { id: string; @@ -132,15 +133,14 @@ export async function sendPhoneCallSummaryEmail(args: { detail: AdminPhoneCallDetail; adminUrl: string; }) { - const resendApiKey = String(process.env.RESEND_API_KEY || "").trim(); const adminEmail = String(process.env.ADMIN_EMAIL || "").trim().toLowerCase(); const fromEmail = String(process.env.PHONE_CALL_SUMMARY_FROM_EMAIL || "").trim(); - if (!adminEmail || !resendApiKey || !fromEmail) { + if (!adminEmail || !fromEmail || !isEmailConfigured()) { const missing = [ !adminEmail ? "ADMIN_EMAIL" : null, - !resendApiKey ? "RESEND_API_KEY" : null, !fromEmail ? "PHONE_CALL_SUMMARY_FROM_EMAIL" : null, + !isEmailConfigured() ? "email transport" : null, ].filter(Boolean); return { @@ -154,60 +154,61 @@ export async function sendPhoneCallSummaryEmail(args: { const callerNumber = normalizePhoneFromIdentity(args.detail.call.participantIdentity) || "Unknown caller"; const statusLabel = args.detail.call.callStatus.toUpperCase(); - const text = [ - `Rocky Mountain Vending phone call summary`, - ``, - `Caller: ${callerNumber}`, - `Started: ${formatPhoneCallTimestamp(args.detail.call.startedAt)}`, - `Duration: ${formatPhoneCallDuration(args.detail.call.durationMs)}`, - `Call status: ${statusLabel}`, - `Jessica answered: ${args.detail.call.answered ? "Yes" : "No"}`, - `Lead outcome: ${args.detail.call.leadOutcome}`, - `Handoff requested: ${args.detail.call.handoffRequested ? "Yes" : "No"}`, - `Recording status: ${args.detail.call.recordingStatus || "Unavailable"}`, - args.detail.call.recordingUrl ? `Recording URL: ${args.detail.call.recordingUrl}` : `Recording URL: Not available in RMV admin`, - `Summary: ${summaryText}`, - `Admin call detail: ${callUrl}`, - args.detail.linkedLead?.id ? `Linked lead: ${args.detail.linkedLead.id}` : `Linked lead: None`, - ``, - `Recent transcript:`, - ...args.detail.turns.slice(-6).map((turn) => `${turn.role}: ${turn.text}`), - ].join("\n"); + const transcriptHtml = args.detail.turns + .slice(-6) + .map((turn) => { + const role = turn.role.replace(/[<>&"]/g, ""); + const text = turn.text + .replace(/&/g, "&") + .replace(//g, ">"); - const response = await fetch("https://api.resend.com/emails", { - method: "POST", - headers: { - Authorization: `Bearer ${resendApiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ + return `
Caller: ${callerNumber}
+Started: ${formatPhoneCallTimestamp(args.detail.call.startedAt)}
+Duration: ${formatPhoneCallDuration(args.detail.call.durationMs)}
+Call status: ${statusLabel}
+Jessica answered: ${args.detail.call.answered ? "Yes" : "No"}
+Lead outcome: ${args.detail.call.leadOutcome}
+Handoff requested: ${args.detail.call.handoffRequested ? "Yes" : "No"}
+Recording status: ${args.detail.call.recordingStatus || "Unavailable"}
+Recording URL: ${ + args.detail.call.recordingUrl + ? `${args.detail.call.recordingUrl}` + : "Not available in RMV admin" + }
+Summary: ${summaryText}
+Admin call detail: ${callUrl}
+Linked lead: ${args.detail.linkedLead?.id || "None"}
+