diff --git a/.env.example b/.env.example index 07e9081000..02d1fca941 100644 --- a/.env.example +++ b/.env.example @@ -85,6 +85,8 @@ SENDGRID_EMAIL= TWILIO_SID= TWILIO_TOKEN= TWILIO_MESSAGING_SID= +TWILIO_PHONE_NUMBER= +NEXT_PUBLIC_SENDER_ID= # This is used so we can bypass emails in auth flows for E2E testing # Set it to "1" if you need to run E2E tests locally diff --git a/README.md b/README.md index b73b14fd67..79f7edb80b 100644 --- a/README.md +++ b/README.md @@ -419,14 +419,16 @@ following 2. Click ‘Get a Twilio phone number’ 3. Copy Account SID to your .env file into the TWILIO_SID field 4. Copy Auth Token to your .env file into the TWILIO_TOKEN field -5. Create a messaging service (Develop -> Messaging -> Services) -6. Choose any name for the messaging service -7. Click 'Add Senders' -8. Choose phone number as sender type -9. Add the listed phone number -10. Leave all other fields as they are -11. Complete setup and click ‘View my new Messaging Service’ -12. Copy Messaging Service SID to your .env file into the TWILIO_MESSAGING_SID field +5. Copy your Twilio phone number to your .env file into the TWILIO_PHONE_NUMBER field +6. Add your own sender id to the .env file into the NEXT_PUBLIC_SENDER_ID field (fallback is Cal) +7. Create a messaging service (Develop -> Messaging -> Services) +8. Choose any name for the messaging service +9. Click 'Add Senders' +10. Choose phone number as sender type +11. Add the listed phone number +12. Leave all other fields as they are +13. Complete setup and click ‘View my new Messaging Service’ +14. Copy Messaging Service SID to your .env file into the TWILIO_MESSAGING_SID field diff --git a/packages/features/ee/workflows/api/scheduleSMSReminders.ts b/packages/features/ee/workflows/api/scheduleSMSReminders.ts index f0186cb219..ac61ba7a90 100644 --- a/packages/features/ee/workflows/api/scheduleSMSReminders.ts +++ b/packages/features/ee/workflows/api/scheduleSMSReminders.ts @@ -6,6 +6,7 @@ import dayjs from "@calcom/dayjs"; import { defaultHandler } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; +import { getSenderId } from "../lib/alphanumericSenderIdSupport"; import * as twilio from "../lib/reminders/smsProviders/twilioProvider"; import customTemplate, { VariablesType } from "../lib/reminders/templates/customTemplate"; import smsReminderTemplate from "../lib/reminders/templates/smsReminderTemplate"; @@ -72,6 +73,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { ? reminder.booking?.attendees[0].timeZone : reminder.booking?.user?.timeZone; + const senderID = getSenderId(sendTo, reminder.workflowStep.sender); + let message: string | null = reminder.workflowStep.reminderBody; switch (reminder.workflowStep.template) { case WorkflowTemplates.REMINDER: @@ -105,12 +108,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { break; } if (message?.length && message?.length > 0 && sendTo) { - const scheduledSMS = await twilio.scheduleSMS( - sendTo, - message, - reminder.scheduledDate, - reminder.workflowStep.sender || "Cal" - ); + const scheduledSMS = await twilio.scheduleSMS(sendTo, message, reminder.scheduledDate, senderID); await prisma.workflowReminder.update({ where: { diff --git a/packages/features/ee/workflows/components/AddActionDialog.tsx b/packages/features/ee/workflows/components/AddActionDialog.tsx index c515b8e26b..b2945754db 100644 --- a/packages/features/ee/workflows/components/AddActionDialog.tsx +++ b/packages/features/ee/workflows/components/AddActionDialog.tsx @@ -5,6 +5,7 @@ import { Dispatch, SetStateAction, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; +import { SENDER_ID } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, @@ -68,7 +69,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { mode: "onSubmit", defaultValues: { action: WorkflowActions.EMAIL_HOST, - sender: "Cal", + sender: SENDER_ID, }, resolver: zodResolver(formSchema), }); @@ -166,7 +167,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { diff --git a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx index b2d220f11d..6569a3242a 100644 --- a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import { Dispatch, SetStateAction, useMemo, useState } from "react"; import { Controller, UseFormReturn } from "react-hook-form"; +import { SENDER_ID } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; @@ -73,7 +74,7 @@ export default function WorkflowDetailsPage(props: Props) { emailSubject: null, template: WorkflowTemplates.CUSTOM, numberRequired: numberRequired || false, - sender: sender || "Cal", + sender: sender || SENDER_ID, }; steps?.push(step); form.setValue("steps", steps); diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index 5a2e525651..e8b990e0fa 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -9,6 +9,7 @@ import { Dispatch, SetStateAction, useRef, useState } from "react"; import { Controller, UseFormReturn } from "react-hook-form"; import "react-phone-number-input/style.css"; +import { SENDER_ID } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import { trpc } from "@calcom/trpc/react"; @@ -366,7 +367,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { @@ -557,7 +558,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { emailSubject, reminderBody, template: step.template, - sender: step.sender || "Cal", + sender: step.sender || SENDER_ID, }); } else { const isNumberValid = @@ -596,6 +597,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { reminderBody: reminderBody || "", template: step.template, sendTo: step.sendTo || "", + sender: step.sender || SENDER_ID, }); setConfirmationDialogOpen(false); }}> diff --git a/packages/features/ee/workflows/lib/alphanumericSenderIdSupport.ts b/packages/features/ee/workflows/lib/alphanumericSenderIdSupport.ts new file mode 100644 index 0000000000..1271577673 --- /dev/null +++ b/packages/features/ee/workflows/lib/alphanumericSenderIdSupport.ts @@ -0,0 +1,87 @@ +import { SENDER_ID } from "@calcom/lib/constants"; + +export function getSenderId(phoneNumber?: string | null, sender?: string | null) { + const isAlphanumericSenderIdSupported = !noAlphanumericSenderIdSupport.find( + (code) => code === phoneNumber?.substring(0, code.length) + ); + + const senderID = isAlphanumericSenderIdSupported ? sender || SENDER_ID : ""; + + return senderID; +} + +const noAlphanumericSenderIdSupport = [ + "+93", + "+54", + "+374", + "+1", + "+375", + "+32", + "+229", + "+55", + "+237", + "+56", + "+86", + "+57", + "+243", + "+506", + "+53", + "+42", + "+593", + "+20", + "+503", + "+251", + "+594", + "+233", + "+224", + "+245", + "+852", + "+36", + "+91", + "+62", + "+98", + "+972", + "+225", + "+962", + "+7", + "+254", + "+965", + "+996", + "+231", + "+60", + "+52", + "+212", + "+95", + "+674", + "+977", + "+64", + "+505", + "+234", + "+968", + "+507", + "+595", + "+51", + "+63", + "+974", + "+7", + "+250", + "+966", + "+27", + "+82", + "+211", + "+94", + "+249", + "+268", + "+963", + "+886", + "+255", + "+66", + "+216", + "+90", + "+256", + "+971", + "+598", + "+58", + "+84", + "+260", +]; diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index 5990830bb8..e104b41c88 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -6,6 +6,7 @@ import { WorkflowTriggerEvents, } from "@prisma/client"; +import { SENDER_ID } from "@calcom/lib/constants"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { scheduleEmailReminder } from "./emailReminderManager"; @@ -51,7 +52,7 @@ export const scheduleWorkflowReminders = async ( step.reminderBody || "", step.id, step.template, - step.sender || "Cal" + step.sender || SENDER_ID ); } else if ( step.action === WorkflowActions.EMAIL_ATTENDEE || @@ -120,7 +121,7 @@ export const sendCancelledReminders = async ( step.reminderBody || "", step.id, step.template, - step.sender || "Cal" + step.sender || SENDER_ID ); } else if ( step.action === WorkflowActions.EMAIL_ATTENDEE || diff --git a/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts b/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts index 420c6159d5..6119550974 100644 --- a/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts +++ b/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts @@ -25,7 +25,7 @@ export const sendSMS = async (phoneNumber: string, body: string, sender: string) body: body, messagingServiceSid: process.env.TWILIO_MESSAGING_SID, to: phoneNumber, - from: sender, + from: sender ? sender : process.env.TWILIO_PHONE_NUMBER, }); return response; @@ -39,7 +39,7 @@ export const scheduleSMS = async (phoneNumber: string, body: string, scheduledDa to: phoneNumber, scheduleType: "fixed", sendAt: scheduledDate, - from: sender, + from: sender ? sender : process.env.TWILIO_PHONE_NUMBER, }); return response; diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts index ecc60d0bad..ffea491ba2 100644 --- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts @@ -10,6 +10,7 @@ import dayjs from "@calcom/dayjs"; import prisma from "@calcom/prisma"; import { Prisma } from "@calcom/prisma/client"; +import { getSenderId } from "../alphanumericSenderIdSupport"; import * as twilio from "./smsProviders/twilioProvider"; import customTemplate, { VariablesType } from "./templates/customTemplate"; import smsReminderTemplate from "./templates/smsReminderTemplate"; @@ -57,6 +58,8 @@ export const scheduleSMSReminder = async ( const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase; let scheduledDate = null; + const senderID = getSenderId(reminderPhone, sender); + if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) { scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null; } else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) { @@ -98,7 +101,7 @@ export const scheduleSMSReminder = async ( triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT ) { try { - await twilio.sendSMS(reminderPhone, message, sender); + await twilio.sendSMS(reminderPhone, message, senderID); } catch (error) { console.log(`Error sending SMS with error ${error}`); } @@ -117,7 +120,7 @@ export const scheduleSMSReminder = async ( reminderPhone, message, scheduledDate.toDate(), - sender + senderID ); await prisma.workflowReminder.create({ diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index d48ade52bd..aa895ff6c0 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -9,6 +9,7 @@ export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL || "https://cal.c export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME || "Cal.com"; export const SUPPORT_MAIL_ADDRESS = process.env.NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS || "help@cal.com"; export const COMPANY_NAME = process.env.NEXT_PUBLIC_COMPANY_NAME || "Cal.com, Inc."; +export const SENDER_ID = process.env.NEXT_PUBLIC_SENDER_ID || "Cal"; // This is the URL from which all Cal Links and their assets are served. // Use website URL to make links shorter(cal.com and not app.cal.com) diff --git a/packages/trpc/server/routers/viewer/workflows.tsx b/packages/trpc/server/routers/viewer/workflows.tsx index b4cd0aadfd..47b87bd94c 100644 --- a/packages/trpc/server/routers/viewer/workflows.tsx +++ b/packages/trpc/server/routers/viewer/workflows.tsx @@ -26,6 +26,7 @@ import { deleteScheduledSMSReminder, scheduleSMSReminder, } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; +import { SENDER_ID } from "@calcom/lib/constants"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { TRPCError } from "@trpc/server"; @@ -154,7 +155,7 @@ export const workflowsRouter = router({ action: WorkflowActions.EMAIL_HOST, template: WorkflowTemplates.REMINDER, workflowId: workflow.id, - sender: "Cal", + sender: SENDER_ID, }, }); return { workflow }; @@ -469,7 +470,7 @@ export const workflowsRouter = router({ step.reminderBody || "", step.id, step.template, - step.sender || "Cal" + step.sender || SENDER_ID ); } }); @@ -535,7 +536,7 @@ export const workflowsRouter = router({ emailSubject: newStep.template === WorkflowTemplates.CUSTOM ? newStep.emailSubject : null, template: newStep.template, numberRequired: newStep.numberRequired, - sender: newStep.sender || "Cal", + sender: newStep.sender || SENDER_ID, }, }); //cancel all reminders of step and create new ones (not for newEventTypes) @@ -647,7 +648,7 @@ export const workflowsRouter = router({ newStep.reminderBody || "", newStep.id || 0, newStep.template, - newStep.sender || "Cal" + newStep.sender || SENDER_ID ); } }); @@ -671,7 +672,7 @@ export const workflowsRouter = router({ addedSteps.forEach(async (step) => { if (step) { const newStep = step; - newStep.sender = step.sender || "Cal"; + newStep.sender = step.sender || SENDER_ID; const createdStep = await ctx.prisma.workflowStep.create({ data: step, }); @@ -760,7 +761,7 @@ export const workflowsRouter = router({ step.reminderBody || "", createdStep.id, step.template, - step.sender || "Cal" + step.sender || SENDER_ID ); } }); @@ -897,7 +898,7 @@ export const workflowsRouter = router({ reminderBody, 0, template, - sender || "Cal" + sender || SENDER_ID ); return { message: "Notification sent" }; } diff --git a/turbo.json b/turbo.json index c380af8b8c..3afb07c50c 100644 --- a/turbo.json +++ b/turbo.json @@ -183,6 +183,7 @@ "$TWILIO_TOKEN", "$TWILIO_SID", "$TWILIO_MESSAGING_SID", + "$TWILIO_PHONE_NUMBER", "$CRON_API_KEY", "$DAILY_API_KEY", "$DAILY_SCALE_PLAN", @@ -221,6 +222,7 @@ "$NEXT_PUBLIC_APP_NAME", "$NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS", "$NEXT_PUBLIC_COMPANY_NAME", + "$NEXT_PUBLIC_SENDER_ID", "$NEXTAUTH_COOKIE_DOMAIN", "$NEXTAUTH_SECRET", "$NEXTAUTH_URL",