fix: use existing email transport for phone summaries
This commit is contained in:
parent
0d23693642
commit
74a23ae2af
4 changed files with 56 additions and 53 deletions
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ ADMIN_EMAIL=
|
|||
|
||||
PHONE_AGENT_INTERNAL_TOKEN=
|
||||
PHONE_CALL_SUMMARY_FROM_EMAIL=
|
||||
RESEND_API_KEY=
|
||||
|
||||
USESEND_API_KEY=
|
||||
USESEND_BASE_URL=
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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, "<")
|
||||
.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 `<li><strong>${role}:</strong> ${text}</li>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const html = `
|
||||
<div style="font-family: Arial, sans-serif; color: #111827; line-height: 1.6;">
|
||||
<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,
|
||||
to: [adminEmail],
|
||||
to: adminEmail,
|
||||
subject: `[RMV Phone] ${statusLabel} call from ${callerNumber}`,
|
||||
text,
|
||||
}),
|
||||
});
|
||||
|
||||
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}.`;
|
||||
html,
|
||||
});
|
||||
|
||||
return {
|
||||
status: "sent" as const,
|
||||
error: undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: "failed" as const,
|
||||
error: errorText,
|
||||
error: error instanceof Error ? error.message : "Failed to send phone call summary email.",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "sent" as const,
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFallbackPhoneCallUrl(callId: string) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue