diff --git a/packages/bot-engine/src/blocks/integrations/sendEmail/executeSendEmailBlock.tsx b/packages/bot-engine/src/blocks/integrations/sendEmail/executeSendEmailBlock.tsx index 342c8d6004..a7ef42d10b 100644 --- a/packages/bot-engine/src/blocks/integrations/sendEmail/executeSendEmailBlock.tsx +++ b/packages/bot-engine/src/blocks/integrations/sendEmail/executeSendEmailBlock.tsx @@ -1,4 +1,4 @@ -import { defaultSendEmailOptions } from "@typebot.io/blocks-integrations/sendEmail/constants"; +import { TRPCError } from "@trpc/server"; import type { SendEmailBlock, SmtpCredentials, @@ -35,6 +35,10 @@ import { defaultFrom, defaultTransportOptions } from "./constants"; export const sendEmailSuccessDescription = "Email successfully sent"; export const sendEmailErrorDescription = "Email not sent"; +let prevHash: string | undefined; +let emailSendingCount = 0; +const maxEmailSending = 5; + export const executeSendEmailBlock = async ( state: SessionState, block: SendEmailBlock, @@ -42,7 +46,11 @@ export const executeSendEmailBlock = async ( const logs: ChatLog[] = []; const { options } = block; if (!state.typebotsQueue[0]) throw new Error("No typebot in queue"); - const { typebot, resultId, answers } = state.typebotsQueue[0]; + const { + typebot: { id, variables }, + resultId, + answers, + } = state.typebotsQueue[0]; const isPreview = !resultId; if (isPreview) return { @@ -55,12 +63,12 @@ export const executeSendEmailBlock = async ( ], }; - const bodyUniqueVariable = findUniqueVariable(typebot.variables)( + const bodyUniqueVariable = findUniqueVariable(variables)( options?.body, )?.value; const body = bodyUniqueVariable ? stringifyUniqueVariableValueAsHtml(bodyUniqueVariable) - : parseVariables(typebot.variables, { isInsideHtml: !options?.isBodyCode })( + : parseVariables(variables, { isInsideHtml: !options?.isBodyCode })( options?.body ?? "", ); @@ -75,26 +83,28 @@ export const executeSendEmailBlock = async ( }); return { outgoingEdgeId: block.outgoingEdgeId, logs }; } + + if (emailSendingCount >= maxEmailSending) + throw new TRPCError({ + code: "FORBIDDEN", + message: "Attempt to send more than 5 emails", + }); try { const sendEmailLogs = await sendEmail({ - typebot, + typebot: { id, variables }, answers, credentialsId: options.credentialsId, - recipients: options.recipients.map(parseVariables(typebot.variables)), + recipients: options.recipients.map(parseVariables(variables)), subject: options.subject - ? parseVariables(typebot.variables)(options?.subject) + ? parseVariables(variables)(options?.subject) : undefined, body, - cc: options.cc - ? options.cc.map(parseVariables(typebot.variables)) - : undefined, - bcc: options.bcc - ? options.bcc.map(parseVariables(typebot.variables)) - : undefined, + cc: options.cc ? options.cc.map(parseVariables(variables)) : undefined, + bcc: options.bcc ? options.bcc.map(parseVariables(variables)) : undefined, replyTo: options.replyTo - ? parseVariables(typebot.variables)(options.replyTo) + ? parseVariables(variables)(options.replyTo) : undefined, - fileUrls: getFileUrls(typebot.variables)(options.attachmentsVariableId), + fileUrls: getFileUrls(variables)(options.attachmentsVariableId), isCustomBody: options.isCustomBody, isBodyCode: options.isBodyCode, }); @@ -107,6 +117,8 @@ export const executeSendEmailBlock = async ( }); } + emailSendingCount += 1; + return { outgoingEdgeId: block.outgoingEdgeId, logs }; }; @@ -133,7 +145,7 @@ const sendEmail = async ({ replyTo: string | undefined; isBodyCode: boolean | undefined; isCustomBody: boolean | undefined; - typebot: TypebotInSession; + typebot: Pick; answers: AnswerInSessionState[]; fileUrls?: string | string[]; }): Promise => { @@ -191,6 +203,15 @@ const sendEmail = async ({ attachments: await parseAttachments(fileUrls, typebot.id), ...emailBody, }; + + const hash = JSON.stringify(email); + if (prevHash && prevHash === hash) + throw new TRPCError({ + code: "FORBIDDEN", + message: "Attempt to send the same email twice", + }); + prevHash = hash; + try { await transporter.sendMail(email); logs.push({ @@ -251,7 +272,7 @@ const getEmailBody = async ({ typebot, answersInSession, }: { - typebot: TypebotInSession; + typebot: Pick; answersInSession: AnswerInSessionState[]; } & Pick< NonNullable,