fix: use existing email transport for phone summaries

This commit is contained in:
DMleadgen 2026-04-01 14:32:51 -06:00
parent 0d23693642
commit 74a23ae2af
Signed by: matt
GPG key ID: C2720CF8CD701894
4 changed files with 56 additions and 53 deletions

View file

@ -33,7 +33,6 @@ ADMIN_EMAIL=
# Direct phone-call visibility # Direct phone-call visibility
PHONE_AGENT_INTERNAL_TOKEN= PHONE_AGENT_INTERNAL_TOKEN=
PHONE_CALL_SUMMARY_FROM_EMAIL= PHONE_CALL_SUMMARY_FROM_EMAIL=
RESEND_API_KEY=
# Placeholder for a later LiveKit rollout # Placeholder for a later LiveKit rollout
LIVEKIT_URL= LIVEKIT_URL=

View file

@ -13,7 +13,6 @@ ADMIN_EMAIL=
PHONE_AGENT_INTERNAL_TOKEN= PHONE_AGENT_INTERNAL_TOKEN=
PHONE_CALL_SUMMARY_FROM_EMAIL= PHONE_CALL_SUMMARY_FROM_EMAIL=
RESEND_API_KEY=
USESEND_API_KEY= USESEND_API_KEY=
USESEND_BASE_URL= USESEND_BASE_URL=

View file

@ -46,15 +46,19 @@ export async function sendTransactionalEmail({
subject, subject,
html, html,
replyTo, replyTo,
from,
}: { }: {
to: string; to: string;
subject: string; subject: string;
html: string; html: string;
replyTo?: string; replyTo?: string;
from?: string;
}) { }) {
const sender = from || FROM_EMAIL;
if (usesend) { if (usesend) {
return usesend.emails.send({ return usesend.emails.send({
from: FROM_EMAIL, from: sender,
to, to,
subject, subject,
html, html,
@ -65,7 +69,7 @@ export async function sendTransactionalEmail({
if (sesClient) { if (sesClient) {
return sesClient.send( return sesClient.send(
new SendEmailCommand({ new SendEmailCommand({
FromEmailAddress: FROM_EMAIL, FromEmailAddress: sender,
Destination: { Destination: {
ToAddresses: [to], ToAddresses: [to],
}, },

View file

@ -1,4 +1,5 @@
import { businessConfig } from "@/lib/seo-config"; import { businessConfig } from "@/lib/seo-config";
import { isEmailConfigured, sendTransactionalEmail } from "@/lib/email";
export type AdminPhoneCallTurn = { export type AdminPhoneCallTurn = {
id: string; id: string;
@ -132,15 +133,14 @@ export async function sendPhoneCallSummaryEmail(args: {
detail: AdminPhoneCallDetail; detail: AdminPhoneCallDetail;
adminUrl: string; adminUrl: string;
}) { }) {
const resendApiKey = String(process.env.RESEND_API_KEY || "").trim();
const adminEmail = String(process.env.ADMIN_EMAIL || "").trim().toLowerCase(); const adminEmail = String(process.env.ADMIN_EMAIL || "").trim().toLowerCase();
const fromEmail = String(process.env.PHONE_CALL_SUMMARY_FROM_EMAIL || "").trim(); const fromEmail = String(process.env.PHONE_CALL_SUMMARY_FROM_EMAIL || "").trim();
if (!adminEmail || !resendApiKey || !fromEmail) { if (!adminEmail || !fromEmail || !isEmailConfigured()) {
const missing = [ const missing = [
!adminEmail ? "ADMIN_EMAIL" : null, !adminEmail ? "ADMIN_EMAIL" : null,
!resendApiKey ? "RESEND_API_KEY" : null,
!fromEmail ? "PHONE_CALL_SUMMARY_FROM_EMAIL" : null, !fromEmail ? "PHONE_CALL_SUMMARY_FROM_EMAIL" : null,
!isEmailConfigured() ? "email transport" : null,
].filter(Boolean); ].filter(Boolean);
return { return {
@ -154,60 +154,61 @@ export async function sendPhoneCallSummaryEmail(args: {
const callerNumber = normalizePhoneFromIdentity(args.detail.call.participantIdentity) || "Unknown caller"; const callerNumber = normalizePhoneFromIdentity(args.detail.call.participantIdentity) || "Unknown caller";
const statusLabel = args.detail.call.callStatus.toUpperCase(); const statusLabel = args.detail.call.callStatus.toUpperCase();
const text = [ const transcriptHtml = args.detail.turns
`Rocky Mountain Vending phone call summary`, .slice(-6)
``, .map((turn) => {
`Caller: ${callerNumber}`, const role = turn.role.replace(/[<>&"]/g, "");
`Started: ${formatPhoneCallTimestamp(args.detail.call.startedAt)}`, const text = turn.text
`Duration: ${formatPhoneCallDuration(args.detail.call.durationMs)}`, .replace(/&/g, "&amp;")
`Call status: ${statusLabel}`, .replace(/</g, "&lt;")
`Jessica answered: ${args.detail.call.answered ? "Yes" : "No"}`, .replace(/>/g, "&gt;");
`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 response = await fetch("https://api.resend.com/emails", { return `<li><strong>${role}:</strong> ${text}</li>`;
method: "POST", })
headers: { .join("");
Authorization: `Bearer ${resendApiKey}`,
"Content-Type": "application/json", const html = `
}, <div style="font-family: Arial, sans-serif; color: #111827; line-height: 1.6;">
body: JSON.stringify({ <h1 style="font-size: 20px; margin-bottom: 16px;">Rocky Mountain Vending phone call summary</h1>
<p><strong>Caller:</strong> ${callerNumber}</p>
<p><strong>Started:</strong> ${formatPhoneCallTimestamp(args.detail.call.startedAt)}</p>
<p><strong>Duration:</strong> ${formatPhoneCallDuration(args.detail.call.durationMs)}</p>
<p><strong>Call status:</strong> ${statusLabel}</p>
<p><strong>Jessica answered:</strong> ${args.detail.call.answered ? "Yes" : "No"}</p>
<p><strong>Lead outcome:</strong> ${args.detail.call.leadOutcome}</p>
<p><strong>Handoff requested:</strong> ${args.detail.call.handoffRequested ? "Yes" : "No"}</p>
<p><strong>Recording status:</strong> ${args.detail.call.recordingStatus || "Unavailable"}</p>
<p><strong>Recording URL:</strong> ${
args.detail.call.recordingUrl
? `<a href="${args.detail.call.recordingUrl}">${args.detail.call.recordingUrl}</a>`
: "Not available in RMV admin"
}</p>
<p><strong>Summary:</strong> ${summaryText}</p>
<p><strong>Admin call detail:</strong> <a href="${callUrl}">${callUrl}</a></p>
<p><strong>Linked lead:</strong> ${args.detail.linkedLead?.id || "None"}</p>
<h2 style="font-size: 16px; margin-top: 24px;">Recent transcript</h2>
<ul>${transcriptHtml || "<li>No transcript turns were captured.</li>"}</ul>
</div>
`;
try {
await sendTransactionalEmail({
from: fromEmail, from: fromEmail,
to: [adminEmail], to: adminEmail,
subject: `[RMV Phone] ${statusLabel} call from ${callerNumber}`, subject: `[RMV Phone] ${statusLabel} call from ${callerNumber}`,
text, html,
}),
}); });
const body = (await response.json().catch(() => ({}))) as { message?: string; error?: unknown };
if (!response.ok) {
const errorText =
typeof body?.message === "string"
? body.message
: typeof body?.error === "string"
? body.error
: `Resend request failed with status ${response.status}.`;
return {
status: "failed" as const,
error: errorText,
};
}
return { return {
status: "sent" as const, status: "sent" as const,
error: undefined, error: undefined,
}; };
} catch (error) {
return {
status: "failed" as const,
error: error instanceof Error ? error.message : "Failed to send phone call summary email.",
};
}
} }
export function buildFallbackPhoneCallUrl(callId: string) { export function buildFallbackPhoneCallUrl(callId: string) {