+
+
+
{formatPhoneNumber(phone.phoneNumber)}
{phone.subscriptionStatus === PhoneNumberSubscriptionStatus.ACTIVE && (
diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts
index 298923d830..159df8a147 100644
--- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts
+++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts
@@ -1,5 +1,4 @@
import type { EventStatus } from "ics";
-import { v4 as uuidv4 } from "uuid";
import dayjs from "@calcom/dayjs";
import generateIcsString from "@calcom/emails/lib/generateIcsString";
@@ -24,7 +23,10 @@ import { sendOrScheduleWorkflowEmails } from "./providers/emailProvider";
import type { FormSubmissionData, WorkflowContextData } from "./reminderScheduler";
import type { AttendeeInBookingInfo, BookingInfo, timeUnitLowerCase } from "./smsReminderManager";
import type { VariablesType } from "./templates/customTemplate";
-import customTemplate from "./templates/customTemplate";
+import customTemplate, {
+ transformBookingResponsesToVariableFormat,
+ transformRoutingFormResponsesToVariableFormat,
+} from "./templates/customTemplate";
import emailRatingTemplate from "./templates/emailRatingTemplate";
import emailReminderTemplate from "./templates/emailReminderTemplate";
@@ -62,7 +64,12 @@ type SendEmailReminderParams = {
subject: string;
html: string;
replyTo?: string;
- attachments?: any[];
+ attachments?: {
+ content: string;
+ filename: string;
+ contentType: string;
+ disposition: string;
+ }[];
sender?: string | null;
};
sendTo: string[];
@@ -159,7 +166,7 @@ const scheduleEmailReminderForEvt = async (args: scheduleEmailReminderArgs & { e
attendeeName = attendeeToBeUsedInMail.name;
timeZone = evt.organizer.timeZone;
break;
- case WorkflowActions.EMAIL_ATTENDEE:
+ case WorkflowActions.EMAIL_ATTENDEE: {
// check if first attendee of sendTo is present in the attendees list, if not take the evt attendee
const attendeeEmailToBeUsedInMailFromEvt = evt.attendees.find(
(attendee) => attendee.email === sendTo[0]
@@ -171,6 +178,7 @@ const scheduleEmailReminderForEvt = async (args: scheduleEmailReminderArgs & { e
attendeeName = evt.organizer.name;
timeZone = attendeeToBeUsedInMail.timeZone;
break;
+ }
}
let emailContent = {
@@ -199,7 +207,7 @@ const scheduleEmailReminderForEvt = async (args: scheduleEmailReminderArgs & { e
timeZone: timeZone,
location: evt.location,
additionalNotes: evt.additionalNotes,
- responses: evt.responses,
+ responses: transformBookingResponsesToVariableFormat(evt.responses),
meetingUrl: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl,
cancelLink: `${bookerUrl}/booking/${evt.uid}?cancel=true${
recipientEmail ? `&cancelledBy=${encodeURIComponent(recipientEmail)}` : ""
@@ -358,12 +366,16 @@ const scheduleEmailReminderForForm = async (
if (emailBody) {
const timeFormat = getTimeFormatStringFromUserTimeFormat(formData.user.timeFormat);
- //todo: add variables
- const emailSubjectTemplate = customTemplate(emailSubject, {}, formData.user.locale, timeFormat);
+
+ const variables: VariablesType = {
+ responses: transformRoutingFormResponsesToVariableFormat(formData.responses),
+ };
+
+ const emailSubjectTemplate = customTemplate(emailSubject, variables, formData.user.locale, timeFormat);
emailContent.emailSubject = emailSubjectTemplate.text;
emailContent.emailBody = customTemplate(
emailBody,
- {},
+ variables,
formData.user.locale,
timeFormat,
hideBranding
diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
index 2aa68e9ab6..1b06e6e4de 100644
--- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
+++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
@@ -10,6 +10,7 @@ import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { getTranslation } from "@calcom/lib/server/i18n";
import type { TimeFormat } from "@calcom/lib/timeFormat";
+import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import type { PrismaClient } from "@calcom/prisma";
import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
@@ -26,6 +27,7 @@ import type { ScheduleReminderArgs } from "./emailReminderManager";
import { scheduleSmsOrFallbackEmail, sendSmsOrFallbackEmail } from "./messageDispatcher";
import * as twilio from "./providers/twilioProvider";
import type { FormSubmissionData } from "./reminderScheduler";
+import customTemplate, { transformRoutingFormResponsesToVariableFormat } from "./templates/customTemplate";
import smsReminderTemplate from "./templates/smsReminderTemplate";
export enum timeUnitLowerCase {
@@ -318,7 +320,18 @@ const scheduleSMSReminderForForm = async (
) => {
const { message, triggerEvent, reminderPhone, sender, userId, teamId, action, formData } = args;
- const smsMessage = message;
+ let smsMessage = message;
+
+ if (smsMessage && formData.responses) {
+ const timeFormat = getTimeFormatStringFromUserTimeFormat(formData.user.timeFormat);
+
+ const variables = {
+ responses: transformRoutingFormResponsesToVariableFormat(formData.responses),
+ };
+
+ const processedMessage = customTemplate(smsMessage, variables, formData.user.locale, timeFormat);
+ smsMessage = processedMessage.text;
+ }
if (smsMessage.trim().length > 0) {
const smsMessageWithoutOptOut = await WorkflowOptOutService.addOptOutMessage(
diff --git a/packages/features/ee/workflows/lib/reminders/templates/customTemplate.ts b/packages/features/ee/workflows/lib/reminders/templates/customTemplate.ts
index 1966c2eb7c..889489dfe9 100644
--- a/packages/features/ee/workflows/lib/reminders/templates/customTemplate.ts
+++ b/packages/features/ee/workflows/lib/reminders/templates/customTemplate.ts
@@ -1,10 +1,68 @@
import { guessEventLocationType } from "@calcom/app-store/locations";
+import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils";
import type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
import { APP_NAME } from "@calcom/lib/constants";
import { TimeFormat } from "@calcom/lib/timeFormat";
import type { CalEventResponses } from "@calcom/types/Calendar";
+export type WorkflowVariableResponses = Record<
+ string,
+ {
+ value:
+ | string
+ | number
+ | boolean
+ | string[]
+ | Record
+ | { value: string; optionValue: string };
+ }
+>;
+
+export function transformBookingResponsesToVariableFormat(
+ responses: CalEventResponses | null | undefined
+): WorkflowVariableResponses | null {
+ if (!responses) return null;
+
+ const transformed: WorkflowVariableResponses = {};
+
+ for (const [key, response] of Object.entries(responses)) {
+ if (response?.value !== undefined) {
+ transformed[key] = {
+ value: response.value,
+ };
+ }
+ }
+
+ return Object.keys(transformed).length > 0 ? transformed : null;
+}
+
+export function transformRoutingFormResponsesToVariableFormat(
+ responses: FORM_SUBMITTED_WEBHOOK_RESPONSES | null | undefined
+): WorkflowVariableResponses | null {
+ if (!responses) return null;
+
+ const transformed: WorkflowVariableResponses = {};
+
+ for (const [key, response] of Object.entries(responses)) {
+ if (response?.value !== undefined) {
+ transformed[key] = {
+ value: response.value,
+ };
+ }
+ }
+
+ return Object.keys(transformed).length > 0 ? transformed : null;
+}
+
+export function formatIdentifierToVariable(key: string): string {
+ return key
+ .replace(/[^a-zA-Z0-9 ]/g, "")
+ .trim()
+ .replaceAll(" ", "_")
+ .toUpperCase();
+}
+
export type VariablesType = {
eventName?: string;
organizerName?: string;
@@ -17,7 +75,7 @@ export type VariablesType = {
timeZone?: string;
location?: string | null;
additionalNotes?: string | null;
- responses?: CalEventResponses | null;
+ responses?: WorkflowVariableResponses | null;
meetingUrl?: string;
cancelLink?: string;
cancelReason?: string | null;
@@ -42,12 +100,15 @@ const customTemplate = (
timeFormat?: TimeFormat,
isBrandingDisabled?: boolean
) => {
- const translatedDate = new Intl.DateTimeFormat(locale, {
- weekday: "long",
- month: "long",
- day: "numeric",
- year: "numeric",
- }).format(variables.eventDate?.add(dayjs().tz(variables.timeZone).utcOffset(), "minute").toDate());
+ const eventDate = variables.eventDate;
+ const translatedDate = eventDate
+ ? new Intl.DateTimeFormat(locale, {
+ weekday: "long",
+ month: "long",
+ day: "numeric",
+ year: "numeric",
+ }).format(eventDate.add(dayjs().tz(variables.timeZone).utcOffset(), "minute").toDate())
+ : "";
let locationString = variables.location || "";
@@ -132,23 +193,21 @@ const customTemplate = (
return;
}
+ // handle custom variables from form/booking responses
if (variables.responses) {
Object.keys(variables.responses).forEach((customInput) => {
- const formatedToVariable = customInput
- .replace(/[^a-zA-Z0-9 ]/g, "")
- .trim()
- .replaceAll(" ", "_")
- .toUpperCase();
+ const formatedToVariable = formatIdentifierToVariable(customInput);
- if (
- variable === formatedToVariable &&
- variables.responses &&
- variables.responses[customInput as keyof typeof variables.responses].value
- ) {
- dynamicText = dynamicText.replace(
- `{${variable}}`,
- variables.responses[customInput as keyof typeof variables.responses].value.toString()
- );
+ if (variable === formatedToVariable && variables.responses) {
+ const response = variables.responses[customInput];
+ if (response?.value !== undefined) {
+ const responseValue = response.value;
+ const valueString = Array.isArray(responseValue)
+ ? responseValue.join(", ")
+ : String(responseValue);
+
+ dynamicText = dynamicText.replace(`{${variable}}`, valueString);
+ }
}
});
}
diff --git a/packages/features/ee/workflows/lib/reminders/utils.ts b/packages/features/ee/workflows/lib/reminders/utils.ts
index e152975989..5d363af61a 100644
--- a/packages/features/ee/workflows/lib/reminders/utils.ts
+++ b/packages/features/ee/workflows/lib/reminders/utils.ts
@@ -8,7 +8,7 @@ import { IMMEDIATE_WORKFLOW_TRIGGER_EVENTS } from "../constants";
import { getWorkflowRecipientEmail } from "../getWorkflowReminders";
import type { AttendeeInBookingInfo, BookingInfo } from "./smsReminderManager";
import type { VariablesType } from "./templates/customTemplate";
-import customTemplate from "./templates/customTemplate";
+import customTemplate, { transformBookingResponsesToVariableFormat } from "./templates/customTemplate";
export const bulkShortenLinks = async (links: string[]) => {
if (!process.env.DUB_API_KEY) {
@@ -74,7 +74,7 @@ export const getSMSMessageWithVariables = async (
timeZone: timeZone,
location: evt.location,
additionalNotes: evt.additionalNotes,
- responses: evt.responses,
+ responses: transformBookingResponsesToVariableFormat(evt.responses),
meetingUrl,
cancelLink,
rescheduleLink,
diff --git a/packages/features/ee/workflows/lib/test/workflows.test.ts b/packages/features/ee/workflows/lib/test/workflows.test.ts
index 284d7f94ba..ac7a089146 100644
--- a/packages/features/ee/workflows/lib/test/workflows.test.ts
+++ b/packages/features/ee/workflows/lib/test/workflows.test.ts
@@ -17,6 +17,9 @@ import { v4 as uuidv4 } from "uuid";
import { describe, expect, beforeAll, vi, beforeEach } from "vitest";
import dayjs from "@calcom/dayjs";
+import { FeaturesRepository } from "@calcom/features/flags/features.repository";
+import tasker from "@calcom/features/tasker";
+import * as rateLimitModule from "@calcom/lib/checkRateLimitAndThrowError";
import type { Prisma } from "@calcom/prisma/client";
import {
BookingStatus,
@@ -33,6 +36,7 @@ import {
import { test } from "@calcom/web/test/fixtures/fixtures";
import { deleteWorkfowRemindersOfRemovedMember } from "../../../teams/lib/deleteWorkflowRemindersOfRemovedMember";
+import { scheduleAIPhoneCall } from "../reminders/aiPhoneCallManager";
import { scheduleEmailReminder } from "../reminders/emailReminderManager";
import * as emailProvider from "../reminders/providers/emailProvider";
@@ -261,7 +265,7 @@ vi.mock("@calcom/lib/constants", async () => {
});
describe("deleteRemindersOfActiveOnIds", () => {
- test("should delete all reminders and tasks from removed event types", async ({}) => {
+ test("should delete all reminders and tasks from removed event types", async () => {
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
@@ -326,7 +330,7 @@ describe("deleteRemindersOfActiveOnIds", () => {
);
});
- test("should delete all reminders from removed event types (org workflow)", async ({}) => {
+ test("should delete all reminders from removed event types (org workflow)", async () => {
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -454,7 +458,7 @@ describe("deleteRemindersOfActiveOnIds", () => {
describe("scheduleBookingReminders", () => {
setupAndTeardown();
- test("schedules workflow notifications with before event trigger and email to host action", async ({}) => {
+ test("schedules workflow notifications with before event trigger and email to host action", async () => {
// organizer is part of org and two teams
const organizer = getOrganizer({
name: "Organizer",
@@ -547,7 +551,7 @@ describe("scheduleBookingReminders", () => {
});
});
- test("schedules workflow notifications with after event trigger and email to host action", async ({}) => {
+ test("schedules workflow notifications with after event trigger and email to host action", async () => {
// organizer is part of org and two teams
const organizer = getOrganizer({
name: "Organizer",
@@ -773,7 +777,7 @@ describe("scheduleBookingReminders", () => {
});
});
- test("should not schedule reminders if date is already in the past", async ({}) => {
+ test("should not schedule reminders if date is already in the past", async () => {
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
@@ -850,7 +854,7 @@ describe("scheduleBookingReminders", () => {
});
describe("deleteWorkfowRemindersOfRemovedMember", () => {
- test("deletes all workflow reminders when member is removed from org", async ({}) => {
+ test("deletes all workflow reminders when member is removed from org", async () => {
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -926,7 +930,7 @@ describe("deleteWorkfowRemindersOfRemovedMember", () => {
expect(tasks.length).toBe(0);
});
- test("deletes reminders if member is removed from an org team ", async ({}) => {
+ test("deletes reminders if member is removed from an org team ", async () => {
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -1100,3 +1104,235 @@ describe("Workflow SMTP Emails Feature Flag", () => {
expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled();
});
});
+
+describe("Routing Form Variables", () => {
+ beforeEach(() => {
+ // Reset global test storage
+ globalThis.testEmails = [];
+ globalThis.testSMS = [];
+ });
+
+ test("should substitute routing form variables in email subject and body", async () => {
+ const mockFormData = {
+ responses: {
+ "contact person": {
+ value: "Jane Smith",
+ response: "Jane Smith",
+ },
+ "company name": {
+ value: "Acme Solutions",
+ response: "Acme Solutions",
+ },
+ department: {
+ value: "Engineering",
+ response: "Engineering",
+ },
+ },
+ routedEventTypeId: null,
+ user: {
+ email: "user@test.com",
+ timeFormat: 12,
+ locale: "en",
+ },
+ };
+
+ const emailArgs = {
+ emailBody: "Dear {CONTACT_PERSON} from {COMPANY_NAME} ({DEPARTMENT}), thank you for your submission.",
+ emailSubject: "Welcome {COMPANY_NAME} - {DEPARTMENT} Team",
+ triggerEvent: WorkflowTriggerEvents.FORM_SUBMITTED,
+ sendTo: ["recipient@test.com"],
+ action: WorkflowActions.EMAIL_HOST,
+ verifiedAt: new Date(),
+ formData: mockFormData,
+ };
+
+ await scheduleEmailReminder(emailArgs);
+
+ // Check that email was sent with processed variables
+ expect(globalThis.testEmails).toHaveLength(1);
+ const sentEmail = globalThis.testEmails[0];
+
+ expect(sentEmail.subject).toBe("Welcome Acme Solutions - Engineering Team");
+ expect(sentEmail.html).toContain("Dear Jane Smith from Acme Solutions (Engineering)");
+ });
+
+ test("should handle array values in routing form variables", async () => {
+ // Using actual structure - for multi-select fields, value is array of labels, response is array of objects
+ const mockFormData = {
+ responses: {
+ "selected services": {
+ value: ["web development", "mobile app", "consulting"],
+ response: [
+ { label: "web development", id: "web-dev" },
+ { label: "mobile app", id: "mobile" },
+ { label: "consulting", id: "consulting" },
+ ],
+ },
+ "company name": {
+ value: "Digital Corp",
+ response: "Digital Corp",
+ },
+ },
+ routedEventTypeId: null,
+ user: {
+ email: "user@test.com",
+ timeFormat: 12,
+ locale: "en",
+ },
+ };
+
+ const emailArgs = {
+ emailBody: "Hello {COMPANY_NAME}, you selected: {SELECTED_SERVICES}",
+ emailSubject: "Services for {COMPANY_NAME}",
+ triggerEvent: WorkflowTriggerEvents.FORM_SUBMITTED,
+ sendTo: ["test@example.com"],
+ action: WorkflowActions.EMAIL_HOST,
+ verifiedAt: new Date(),
+ formData: mockFormData,
+ };
+
+ await scheduleEmailReminder(emailArgs);
+
+ expect(globalThis.testEmails).toHaveLength(1);
+ const sentEmail = globalThis.testEmails[0];
+
+ expect(sentEmail.subject).toBe("Services for Digital Corp");
+ expect(sentEmail.html).toContain(
+ "Hello Digital Corp, you selected: web development, mobile app, consulting"
+ );
+ });
+
+ test("should handle field names with special characters in routing forms", async () => {
+ const mockFormData = {
+ responses: {
+ "company-name!@#": {
+ value: "Special Characters LLC",
+ response: "Special Characters LLC",
+ },
+ "phone number (primary)": {
+ value: "+1-555-0123",
+ response: "+1-555-0123",
+ },
+ },
+ routedEventTypeId: null,
+ user: {
+ email: "user@test.com",
+ timeFormat: 12,
+ locale: "en",
+ },
+ };
+
+ const emailArgs = {
+ emailBody: "Company: {COMPANYNAME}, Phone: {PHONE_NUMBER_PRIMARY}",
+ emailSubject: "Contact info for {COMPANYNAME}",
+ triggerEvent: WorkflowTriggerEvents.FORM_SUBMITTED,
+ sendTo: ["test@example.com"],
+ action: WorkflowActions.EMAIL_HOST,
+ verifiedAt: new Date(),
+ formData: mockFormData,
+ };
+
+ await scheduleEmailReminder(emailArgs);
+
+ expect(globalThis.testEmails).toHaveLength(1);
+ const sentEmail = globalThis.testEmails[0];
+
+ expect(sentEmail.subject).toBe("Contact info for Special Characters LLC");
+ expect(sentEmail.html).toContain("Company: Special Characters LLC, Phone: +1-555-0123");
+ });
+
+ test("should pass routing form responses to AI phone call workflows", async () => {
+ // Mock the feature flag
+ const mockCheckFeature = vi
+ .spyOn(FeaturesRepository.prototype, "checkIfFeatureIsEnabledGlobally")
+ .mockResolvedValue(true);
+
+ // Mock rate limiting
+ const mockRateLimit = vi
+ .spyOn(rateLimitModule, "checkRateLimitAndThrowError")
+ .mockResolvedValue(undefined);
+
+ // Mock the AI agent setup
+ await prismock.agent.create({
+ data: {
+ id: "test-agent-id",
+ providerAgentId: "provider-123",
+ outboundPhoneNumbers: {
+ create: {
+ phoneNumber: "+1234567890",
+ subscriptionStatus: "ACTIVE",
+ },
+ },
+ },
+ });
+
+ await prismock.workflowStep.create({
+ data: {
+ id: 999,
+ stepNumber: 1,
+ action: WorkflowActions.CAL_AI_PHONE_CALL,
+ workflowId: 1,
+ agentId: "test-agent-id",
+ verifiedAt: new Date(),
+ },
+ });
+
+ const mockFormData = {
+ responses: {
+ "customer name": {
+ value: "John Doe",
+ response: "John Doe",
+ },
+ "phone preference": {
+ value: "mobile",
+ response: { label: "mobile", id: "mobile" },
+ },
+ priority: {
+ value: "high",
+ response: { label: "high", id: "high" },
+ },
+ },
+ routedEventTypeId: 123,
+ user: {
+ email: "user@test.com",
+ timeFormat: 12,
+ locale: "en",
+ },
+ };
+
+ const aiPhoneArgs = {
+ submittedPhoneNumber: "+1-555-9999",
+ triggerEvent: WorkflowTriggerEvents.FORM_SUBMITTED,
+ timeSpan: { time: null, timeUnit: null },
+ workflowStepId: 999,
+ userId: 1,
+ teamId: null,
+ verifiedAt: new Date(),
+ formData: mockFormData,
+ routedEventTypeId: 123,
+ };
+
+ const mockTaskerCreate = vi.spyOn(tasker, "create").mockResolvedValue("task-id");
+
+ await scheduleAIPhoneCall(aiPhoneArgs);
+
+ // Verify that the task was created with form responses
+ expect(mockTaskerCreate).toHaveBeenCalled();
+ expect(mockTaskerCreate).toHaveBeenCalledWith(
+ "executeAIPhoneCall",
+ expect.any(Object),
+ expect.any(Object)
+ );
+ const taskData = mockTaskerCreate.mock.calls[0][1] as {
+ responses?: typeof mockFormData.responses;
+ routedEventTypeId?: number;
+ };
+ expect(taskData.responses).toEqual(mockFormData.responses);
+ expect(taskData.routedEventTypeId).toBe(123);
+
+ // Clean up
+ mockTaskerCreate.mockRestore();
+ mockCheckFeature.mockRestore();
+ mockRateLimit.mockRestore();
+ });
+});
diff --git a/packages/features/tasker/tasks/executeAIPhoneCall.ts b/packages/features/tasker/tasks/executeAIPhoneCall.ts
index c90b679f8f..af8a270dd2 100644
--- a/packages/features/tasker/tasks/executeAIPhoneCall.ts
+++ b/packages/features/tasker/tasks/executeAIPhoneCall.ts
@@ -3,6 +3,7 @@ import dayjs from "@calcom/dayjs";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { createDefaultAIPhoneServiceProvider } from "@calcom/features/calAIPhone";
import { handleInsufficientCredits } from "@calcom/features/ee/billing/helpers/handleInsufficientCredits";
+import { formatIdentifierToVariable } from "@calcom/features/ee/workflows/lib/reminders/templates/customTemplate";
import { WorkflowReminderRepository } from "@calcom/features/ee/workflows/lib/repository/workflowReminder";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import {
@@ -51,6 +52,13 @@ function getVariablesFromFormResponse({
ATTENDEE_EMAIL: submittedEmail || "",
NUMBER_TO_CALL: numberToCall,
eventTypeId: eventTypeId?.toString() || "",
+ // Include any custom form responses
+ ...Object.fromEntries(
+ Object.entries(responses || {}).map(([key, value]) => [
+ formatIdentifierToVariable(key),
+ value.value?.toString() || "",
+ ])
+ ),
};
}
@@ -97,11 +105,7 @@ function getVariablesFromBooking(booking: BookingWithRelations, numberToCall: st
// Include any custom form responses
...Object.fromEntries(
Object.entries(responses || {}).map(([key, value]) => [
- key
- .replace(/[^a-zA-Z0-9 ]/g, "")
- .trim()
- .replaceAll(" ", "_")
- .toUpperCase(),
+ formatIdentifierToVariable(key),
value.value?.toString() || "",
])
),
@@ -192,9 +196,9 @@ export async function executeAIPhoneCall(payload: string) {
// form triggers don't have a booking
const booking = workflowReminder.booking as BookingWithRelations | null;
- const responses = data.responses;
+ const routingFormResponses = data.responses;
- if (!booking && !responses) {
+ if (!booking && !routingFormResponses) {
log.warn(`No form responses or booking found for workflow reminder ${data.workflowReminderId}`);
throw new Error("No booking, response, or form responses found");
}
@@ -214,7 +218,7 @@ export async function executeAIPhoneCall(payload: string) {
// Prefer response variables if present, else fall back to booking
let dynamicVariables: VariablesType | undefined;
- if (responses) {
+ if (routingFormResponses) {
const workflowStep = workflowReminder.workflowStep;
const eventTypeId =
data.routedEventTypeId ??
@@ -228,7 +232,7 @@ export async function executeAIPhoneCall(payload: string) {
return;
}
dynamicVariables = getVariablesFromFormResponse({
- responses,
+ responses: routingFormResponses,
eventTypeId,
numberToCall,
});