-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
6658 workflows add a first twenty piece email sender #6965
Changes from 3 commits
6339a4e
e484c60
4528242
cb33021
2e44781
4978270
52ac80d
d030c01
5b50565
77e78a8
2f4a6de
fa03414
8fca876
4bcb226
f99d805
98fdcfb
3e74a5d
5726382
611b4ca
c8b0b4a
b3bde02
6ecd69d
d0d9376
7afe454
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { BaseEmail } from 'src/components/BaseEmail'; | ||
import { Title } from 'src/components/Title'; | ||
import { MainText } from 'src/components/MainText'; | ||
import { CallToAction } from 'src/components/CallToAction'; | ||
|
||
type WorkflowActionEmailProps = { | ||
mainText?: string; | ||
title?: string; | ||
callToAction?: { | ||
value: string; | ||
href: string; | ||
}; | ||
}; | ||
export const WorkflowActionEmail = ({ | ||
mainText, | ||
title, | ||
callToAction, | ||
}: WorkflowActionEmailProps) => { | ||
return ( | ||
<BaseEmail withLogo={false}> | ||
{title && <Title value={title} />} | ||
{mainText && <MainText>{mainText}</MainText>} | ||
{callToAction && ( | ||
<CallToAction value={callToAction.value} href={callToAction.href} /> | ||
)} | ||
</BaseEmail> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { BaseWorkflowSettings } from 'src/modules/workflow/common/types/settings/workflow-base-settings.type'; | ||
|
||
export type WorkflowCodeSettings = BaseWorkflowSettings & { | ||
serverlessFunctionId: string; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { BaseWorkflowSettings } from 'src/modules/workflow/common/types/settings/workflow-base-settings.type'; | ||
import { WorkflowSystemActionType } from 'src/modules/workflow/common/types/workflow-system-action.type'; | ||
|
||
type BaseSystemActionSettings = BaseWorkflowSettings & { | ||
systemActionType: WorkflowSystemActionType; | ||
}; | ||
|
||
export type WorkflowSystemSendEmailActionSettings = BaseSystemActionSettings & { | ||
subject: string; | ||
template?: string; | ||
title?: string; | ||
callToAction?: { | ||
value: string; | ||
href: string; | ||
}; | ||
}; | ||
|
||
export type WorkflowSystemActionSettings = | ||
WorkflowSystemSendEmailActionSettings; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export enum WorkflowSystemActionType { | ||
SEND_EMAIL = 'SEND_EMAIL', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { WorkflowStepExecutor } from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.interface'; | ||
import { WorkflowSystemStep } from 'src/modules/workflow/common/types/workflow-step.type'; | ||
import { WorkflowStepResult } from 'src/modules/workflow/common/types/workflow-step-result.type'; | ||
import { WorkflowSystemActionFactory } from 'src/modules/workflow/workflow-system-action/workflow-system-action.factory'; | ||
|
||
@Injectable() | ||
export class SystemActionExecutor implements WorkflowStepExecutor { | ||
constructor( | ||
private readonly workflowSystemActionFactory: WorkflowSystemActionFactory, | ||
) {} | ||
|
||
async execute({ | ||
step, | ||
payload, | ||
}: { | ||
step: WorkflowSystemStep; | ||
payload?: object; | ||
}): Promise<WorkflowStepResult> { | ||
const workflowSystemAction = this.workflowSystemActionFactory.get( | ||
step.settings.systemActionType, | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Add error handling in case the systemActionType is invalid or not found |
||
|
||
return await workflowSystemAction.execute({ step, payload }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider wrapping this in a try/catch block to handle potential errors during execution |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { CustomException } from 'src/utils/custom-exception'; | ||
|
||
export class WorkflowSystemActionException extends CustomException { | ||
code: WorkflowSystemActionExceptionCode; | ||
constructor(message: string, code: WorkflowSystemActionExceptionCode) { | ||
super(message, code); | ||
} | ||
} | ||
|
||
export enum WorkflowSystemActionExceptionCode { | ||
INVALID_SYSTEM_ACTION_TYPE = 'INVALID_SYSTEM_ACTION_TYPE', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { WorkflowSystemAction } from 'src/modules/workflow/workflow-system-action/workflow-system-action.interface'; | ||
import { | ||
WorkflowSystemActionException, | ||
WorkflowSystemActionExceptionCode, | ||
} from 'src/modules/workflow/workflow-system-action/workflow-system-action.exception'; | ||
import { SendEmailAction } from 'src/modules/workflow/workflow-system-action/workflow-system-actions/send-email-action'; | ||
import { WorkflowSystemActionType } from 'src/modules/workflow/common/types/workflow-system-action.type'; | ||
|
||
@Injectable() | ||
export class WorkflowSystemActionFactory { | ||
constructor(private readonly sendEmailAction: SendEmailAction) {} | ||
|
||
get( | ||
workflowSystemActionType: WorkflowSystemActionType, | ||
): WorkflowSystemAction { | ||
switch (workflowSystemActionType) { | ||
case WorkflowSystemActionType.SEND_EMAIL: | ||
return this.sendEmailAction; | ||
default: | ||
throw new WorkflowSystemActionException( | ||
`Workflow system action not found for system action type '${workflowSystemActionType}'`, | ||
WorkflowSystemActionExceptionCode.INVALID_SYSTEM_ACTION_TYPE, | ||
); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider using a Map instead of a switch statement for better maintainability as more action types are added |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { WorkflowSystemStep } from 'src/modules/workflow/common/types/workflow-step.type'; | ||
import { WorkflowStepResult } from 'src/modules/workflow/common/types/workflow-step-result.type'; | ||
|
||
export interface WorkflowSystemAction { | ||
execute({ | ||
step, | ||
payload, | ||
}: { | ||
step: WorkflowSystemStep; | ||
payload?: object; | ||
}): Promise<WorkflowStepResult>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Module } from '@nestjs/common'; | ||
|
||
import { WorkflowSystemActionFactory } from 'src/modules/workflow/workflow-system-action/workflow-system-action.factory'; | ||
import { SendEmailAction } from 'src/modules/workflow/workflow-system-action/workflow-system-actions/send-email-action'; | ||
|
||
@Module({ | ||
providers: [WorkflowSystemActionFactory, SendEmailAction], | ||
exports: [WorkflowSystemActionFactory], | ||
}) | ||
export class WorkflowSystemActionModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { WorkflowActionEmail } from 'twenty-emails'; | ||
import { render } from '@react-email/components'; | ||
|
||
import { WorkflowSystemAction } from 'src/modules/workflow/workflow-system-action/workflow-system-action.interface'; | ||
import { WorkflowSystemStep } from 'src/modules/workflow/common/types/workflow-step.type'; | ||
import { WorkflowStepResult } from 'src/modules/workflow/common/types/workflow-step-result.type'; | ||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; | ||
import { EmailService } from 'src/engine/integrations/email/email.service'; | ||
import { isDefined } from 'src/utils/is-defined'; | ||
|
||
@Injectable() | ||
export class SendEmailAction implements WorkflowSystemAction { | ||
martmull marked this conversation as resolved.
Show resolved
Hide resolved
|
||
constructor( | ||
private readonly environmentService: EnvironmentService, | ||
private readonly emailService: EmailService, | ||
) {} | ||
|
||
async execute({ | ||
step, | ||
payload, | ||
}: { | ||
step: WorkflowSystemStep; | ||
payload: { | ||
email: string; | ||
[key: string]: string; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider using a more specific type for payload instead of [key: string]: string. This could lead to runtime errors if non-string values are passed. |
||
}): Promise<WorkflowStepResult> { | ||
let mainText = step.settings.template; | ||
|
||
if (isDefined(payload)) { | ||
Object.keys(payload).forEach( | ||
(key: string) => | ||
(mainText = mainText?.replace(`{{${key}}}`, payload[key])), | ||
); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: This template replacement logic doesn't handle cases where a placeholder in the template doesn't have a corresponding value in the payload. Consider adding a fallback or error handling for missing values. |
||
|
||
const email = WorkflowActionEmail({ | ||
mainText: mainText, | ||
title: step.settings.title, | ||
callToAction: step.settings.callToAction, | ||
}); | ||
const html = render(email, { | ||
pretty: true, | ||
}); | ||
const text = render(email, { | ||
plainText: true, | ||
}); | ||
|
||
await this.emailService.send({ | ||
from: `${this.environmentService.get( | ||
'EMAIL_FROM_NAME', | ||
)} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`, | ||
to: payload.email, | ||
subject: step.settings.subject, | ||
text, | ||
html, | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Add error handling around the email sending process. If the email fails to send, the current implementation will throw an unhandled exception. |
||
|
||
return { data: null }; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider making default width a named constant for better maintainability