From 9a2766feae11968273baefafe36bb5aa76308bab Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Sun, 21 Sep 2025 20:55:34 +0600 Subject: [PATCH] feat: rich text email body (#14482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: Félix Malfait Co-authored-by: Félix Malfait --- package.json | 4 +- packages/twenty-emails/package.json | 1 + packages/twenty-emails/src/index.ts | 4 +- .../utils/email-renderer/email-renderer.tsx | 34 ++ .../src/utils/email-renderer/marks/bold.tsx | 6 + .../src/utils/email-renderer/marks/italic.tsx | 6 + .../src/utils/email-renderer/marks/link.tsx | 21 ++ .../src/utils/email-renderer/marks/strike.tsx | 6 + .../utils/email-renderer/marks/underline.tsx | 6 + .../utils/email-renderer/nodes/heading.tsx | 35 ++ .../src/utils/email-renderer/nodes/image.tsx | 31 ++ .../utils/email-renderer/nodes/paragraph.tsx | 8 + .../src/utils/email-renderer/nodes/text.tsx | 17 + .../email-renderer/nodes/variable-tag.tsx | 12 + .../email-renderer/renderers/render-mark.tsx | 46 +++ .../email-renderer/renderers/render-node.tsx | 40 +++ packages/twenty-front/package.json | 7 + .../fullscreen/hooks/useFullScreenModal.tsx | 89 +++++ .../WorkflowEditActionServerlessFunction.tsx | 164 ++++----- ...owEditActionServerlessFunction.stories.tsx | 222 ++++++++++++ .../WorkflowEditActionSendEmail.tsx | 6 +- .../components/WorkflowEmailEditor.tsx | 98 ++++++ .../components/WorkflowSendEmailBody.tsx | 261 ++++++++++++++ .../WorkflowEmailEditor.stories.tsx | 276 +++++++++++++++ .../WorkflowSendEmailBody.stories.tsx | 320 ++++++++++++++++++ .../image-bubble-menu/ImageBubbleMenu.tsx | 96 ++++++ .../link-bubble-menu/LinkBubbleMenu.tsx | 62 ++++ .../text-bubble-menu/BubbleMenuIconButton.tsx | 44 +++ .../text-bubble-menu/EditLinkPopover.tsx | 79 +++++ .../text-bubble-menu/TextBubbleMenu.tsx | 87 +++++ .../TurnIntoBlockDropdown.tsx | 89 +++++ .../resizable-image/ResizableImage.ts | 21 ++ .../resizable-image/ResizableImageView.tsx | 215 ++++++++++++ .../resizable-image/UploadImageExtension.ts | 61 ++++ .../resizable-image/UploadImagePlugin.ts | 128 +++++++ .../email-action/hooks/useEmailEditor.ts | 103 ++++++ .../email-action/hooks/useTextBubbleState.ts | 20 ++ .../hooks/useTurnIntoBlockOptions.ts | 81 +++++ .../email-action/utils/isTextSelected.ts | 27 ++ .../components/WorkflowVariablesDropdown.tsx | 1 + .../utils/getInitialEmailEditorContent.ts | 31 ++ packages/twenty-server/package.json | 4 +- .../approved-access-domain.service.ts | 4 +- .../services/approved-access-domain.spec.ts | 11 + .../auth/services/auth.service.ts | 4 +- .../services/reset-password.service.spec.ts | 11 + .../auth/services/reset-password.service.ts | 4 +- .../services/email-verification.service.ts | 4 +- .../tools/send-email-tool/send-email-tool.ts | 13 +- .../workspace-invitation.service.spec.ts | 11 + .../services/workspace-invitation.service.ts | 4 +- .../services/cleaner.workspace-service.ts | 8 +- .../constants/standard-field-ids.ts | 2 + .../attachment.workspace-entity.ts | 17 + .../messaging-send-message-gmail.spec.ts | 147 ++++++++ .../messaging-send-message.service.ts | 23 +- .../__tests__/gmail-mime-message.spec.ts | 61 ++++ .../workflow.workspace-entity.ts | 13 + .../src/utils/parse-email-body.ts | 11 + packages/twenty-shared/src/utils/index.ts | 11 + .../twenty-shared/src/utils/tiptap/index.ts | 1 + .../src/utils/tiptap/tiptap-marks.ts | 42 +++ .../display/icon/components/TablerIcons.ts | 11 +- packages/twenty-ui/src/display/index.ts | 11 +- .../code-editor/components/CodeEditor.tsx | 3 +- yarn.lock | 292 ++++++++-------- 66 files changed, 3312 insertions(+), 276 deletions(-) create mode 100644 packages/twenty-emails/src/utils/email-renderer/email-renderer.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/marks/bold.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/marks/italic.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/marks/link.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/marks/strike.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/marks/underline.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/nodes/heading.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/nodes/image.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/nodes/paragraph.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/nodes/text.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/nodes/variable-tag.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/renderers/render-mark.tsx create mode 100644 packages/twenty-emails/src/utils/email-renderer/renderers/render-node.tsx create mode 100644 packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useFullScreenModal.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/__stories__/WorkflowEditActionServerlessFunction.stories.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/WorkflowEmailEditor.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/WorkflowSendEmailBody.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/__stories__/WorkflowEmailEditor.stories.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/__stories__/WorkflowSendEmailBody.stories.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/image-bubble-menu/ImageBubbleMenu.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/link-bubble-menu/LinkBubbleMenu.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/text-bubble-menu/BubbleMenuIconButton.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/text-bubble-menu/EditLinkPopover.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/text-bubble-menu/TextBubbleMenu.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/components/text-bubble-menu/TurnIntoBlockDropdown.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/extensions/resizable-image/ResizableImage.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/extensions/resizable-image/ResizableImageView.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/extensions/resizable-image/UploadImageExtension.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/extensions/resizable-image/UploadImagePlugin.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/hooks/useEmailEditor.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/hooks/useTextBubbleState.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/hooks/useTurnIntoBlockOptions.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/email-action/utils/isTextSelected.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialEmailEditorContent.ts create mode 100644 packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-send-message-gmail.spec.ts create mode 100644 packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/gmail-mime-message.spec.ts create mode 100644 packages/twenty-server/src/utils/parse-email-body.ts create mode 100644 packages/twenty-shared/src/utils/tiptap/index.ts create mode 100644 packages/twenty-shared/src/utils/tiptap/tiptap-marks.ts diff --git a/package.json b/package.json index fc8a1bad4f..56d2cbdbfd 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "@floating-ui/react": "^0.24.3", "@linaria/core": "^6.2.0", "@linaria/react": "^6.2.1", - "@react-email/components": "0.0.35", - "@react-email/render": "0.0.17", + "@react-email/components": "^0.5.3", + "@react-email/render": "^1.2.3", "@sentry/profiling-node": "^9.26.0", "@sentry/react": "^9.26.0", "@sniptt/guards": "^0.2.0", diff --git a/packages/twenty-emails/package.json b/packages/twenty-emails/package.json index dc09c2eb71..07f3da620e 100644 --- a/packages/twenty-emails/package.json +++ b/packages/twenty-emails/package.json @@ -21,6 +21,7 @@ "@lingui/cli": "^5.1.2", "@lingui/swc-plugin": "^5.6.0", "@lingui/vite-plugin": "^5.1.2", + "@tiptap/core": "^3.4.2", "@types/react": "^19", "@types/react-dom": "^19", "react-email": "4.0.3" diff --git a/packages/twenty-emails/src/index.ts b/packages/twenty-emails/src/index.ts index 07ff30d923..afc26b303d 100644 --- a/packages/twenty-emails/src/index.ts +++ b/packages/twenty-emails/src/index.ts @@ -1,7 +1,9 @@ +export type { JSONContent } from '@tiptap/core'; export * from './emails/clean-suspended-workspace.email'; export * from './emails/password-reset-link.email'; export * from './emails/password-update-notify.email'; export * from './emails/send-email-verification-link.email'; export * from './emails/send-invite-link.email'; -export * from './emails/warn-suspended-workspace.email'; export * from './emails/validate-approved-access-domain.email'; +export * from './emails/warn-suspended-workspace.email'; +export * from './utils/email-renderer/email-renderer'; diff --git a/packages/twenty-emails/src/utils/email-renderer/email-renderer.tsx b/packages/twenty-emails/src/utils/email-renderer/email-renderer.tsx new file mode 100644 index 0000000000..99d59bf2fe --- /dev/null +++ b/packages/twenty-emails/src/utils/email-renderer/email-renderer.tsx @@ -0,0 +1,34 @@ +import { Body, Container, Head, Html } from '@react-email/components'; +import { type JSONContent } from '@tiptap/core'; +import { mappedNodeContent } from 'src/utils/email-renderer/renderers/render-node'; + +export const reactMarkupFromJSON = (json: JSONContent | string) => { + if (typeof json === 'string') { + return json; + } + + const jsxNodes = mappedNodeContent(json); + return ( + + +