diff --git a/.github/workflows/ci-emails.yaml b/.github/workflows/ci-emails.yaml new file mode 100644 index 000000000..f64104865 --- /dev/null +++ b/.github/workflows/ci-emails.yaml @@ -0,0 +1,62 @@ +name: CI Emails +on: + push: + branches: + - main + + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changed-files-check: + uses: ./.github/workflows/changed-files.yaml + with: + files: | + package.json + packages/twenty-emails/** + emails-test: + needs: changed-files-check + if: needs.changed-files-check.outputs.any_changed == 'true' + timeout-minutes: 10 + runs-on: depot-ubuntu-24.04-8 + env: + NX_REJECT_UNKNOWN_LOCAL_CACHE: 0 + steps: + - name: Fetch custom Github Actions and base branch history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install dependencies + uses: ./.github/workflows/actions/yarn-install + - name: Build twenty-emails + run: npx nx build twenty-emails + - name: Run email tests + run: | + # Start the email server in the background + npx nx run twenty-emails:start & + SERVER_PID=$! + + # Wait for server to start + sleep 20 + + # Check if server is running + if ! curl -s http://localhost:4001/preview/test.email > /dev/null; then + echo "Email server failed to start" + kill $SERVER_PID + exit 1 + fi + + # Kill the server + kill $SERVER_PID + ci-emails-status-check: + if: always() && !cancelled() + timeout-minutes: 1 + runs-on: depot-ubuntu-24.04-8 + needs: [changed-files-check, emails-test] + steps: + - name: Fail job if any needs failed + if: contains(needs.*.result, 'failure') + run: exit 1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 138eea1d9..d4aa48c14 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ dump.rdb /flake.lock /flake.nix -.crowdin.yml \ No newline at end of file +.crowdin.yml +.react-email/ \ No newline at end of file diff --git a/package.json b/package.json index a1de01034..970bf9934 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,6 @@ "@octokit/graphql": "^7.0.2", "@ptc-org/nestjs-query-core": "^4.2.0", "@ptc-org/nestjs-query-typeorm": "4.2.1-alpha.2", - "@react-email/components": "0.0.32", - "@react-email/render": "0.0.17", "@sentry/node": "^8", "@sentry/profiling-node": "^8", "@sentry/react": "^8", @@ -220,6 +218,8 @@ "@nx/vite": "18.3.3", "@nx/web": "18.3.3", "@playwright/test": "^1.46.0", + "@react-email/components": "0.0.35", + "@react-email/render": "0.0.17", "@sentry/types": "^7.109.0", "@storybook/addon-actions": "^7.6.3", "@storybook/addon-coverage": "^1.0.0", diff --git a/packages/twenty-emails/.babelrc b/packages/twenty-emails/.babelrc deleted file mode 100644 index 1ea870ead..000000000 --- a/packages/twenty-emails/.babelrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presets": [ - [ - "@nx/react/babel", - { - "runtime": "automatic", - "useBuiltIns": "usage" - } - ] - ], - "plugins": [] -} diff --git a/packages/twenty-emails/.gitignore b/packages/twenty-emails/.gitignore index 1521c8b76..1f18a594e 100644 --- a/packages/twenty-emails/.gitignore +++ b/packages/twenty-emails/.gitignore @@ -1 +1,2 @@ dist +.react-email/ diff --git a/packages/twenty-emails/README.md b/packages/twenty-emails/README.md new file mode 100644 index 000000000..1cd3763f6 --- /dev/null +++ b/packages/twenty-emails/README.md @@ -0,0 +1,33 @@ +# Twenty Emails + +This package contains the email templates used by Twenty. + +## Features + +- Email templates built with [React Email](https://react.email/) +- Internationalization (i18n) support via [@lingui/react](https://lingui.dev/) +- Local preview server for testing email templates + +## Getting Started + +### Starting the Local Preview Server + +To start the local preview server for email development: + +```bash +npx nx start twenty-emails +``` + +This will run the development server on port 4001. You can then view your email templates at [http://localhost:4001](http://localhost:4001). + +### Building Emails + +To build the email templates: + +```bash +npx nx build twenty-emails +``` + +## Email Structure + +Each email template is located in the `src/emails` directory. The templates use various components from the `src/components` directory to maintain consistent styling and functionality. diff --git a/packages/twenty-emails/package.json b/packages/twenty-emails/package.json index ce8fe1426..07be59c58 100644 --- a/packages/twenty-emails/package.json +++ b/packages/twenty-emails/package.json @@ -14,10 +14,17 @@ "@lingui/react": "^5.1.2", "twenty-shared": "workspace:*" }, + "peerDependencies": { + "react": "^18.2.0 || ^19.0.0", + "react-dom": "^18.2.0 || ^19.0.0" + }, "devDependencies": { "@lingui/cli": "^5.1.2", "@lingui/swc-plugin": "^5.1.0", - "@lingui/vite-plugin": "^5.1.2" + "@lingui/vite-plugin": "^5.1.2", + "@types/react": "^19", + "@types/react-dom": "^19", + "react-email": "4.0.3" }, "exports": { ".": { diff --git a/packages/twenty-emails/project.json b/packages/twenty-emails/project.json index 8442006c6..3f6bfd540 100644 --- a/packages/twenty-emails/project.json +++ b/packages/twenty-emails/project.json @@ -11,6 +11,13 @@ }, "dependsOn": ["^build"] }, + "start": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "command": "email dev -d src/emails -p 4001" + } + }, "typecheck": {}, "lint": { "options": { diff --git a/packages/twenty-emails/src/components/BaseEmail.tsx b/packages/twenty-emails/src/components/BaseEmail.tsx index 882f66280..56e428e0f 100644 --- a/packages/twenty-emails/src/components/BaseEmail.tsx +++ b/packages/twenty-emails/src/components/BaseEmail.tsx @@ -1,10 +1,11 @@ import { i18n, Messages } from '@lingui/core'; import { I18nProvider } from '@lingui/react'; import { Container, Html } from '@react-email/components'; -import { PropsWithChildren } from 'react'; import { BaseHead } from 'src/components/BaseHead'; +import { Footer } from 'src/components/Footer'; import { Logo } from 'src/components/Logo'; +import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; import { messages as afMessages } from '../locales/generated/af-ZA'; import { messages as arMessages } from '../locales/generated/ar-SA'; import { messages as caMessages } from '../locales/generated/ca-ES'; @@ -36,12 +37,12 @@ import { messages as ukMessages } from '../locales/generated/uk-UA'; import { messages as viMessages } from '../locales/generated/vi-VN'; import { messages as zhHansMessages } from '../locales/generated/zh-CN'; import { messages as zhHantMessages } from '../locales/generated/zh-TW'; -import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; -type BaseEmailProps = PropsWithChildren<{ +type BaseEmailProps = { + children: JSX.Element | JSX.Element[] | string; width?: number; locale: keyof typeof APP_LOCALES; -}>; +}; const messages: Record = { en: enMessages, @@ -95,6 +96,7 @@ export const BaseEmail = ({ children, width, locale }: BaseEmailProps) => { {children} +