## Problem
Fixes#23307. Every transactional email (workspace invite, password
reset,
email verification, etc.) is delivered with an **empty body** — no
title, text,
or CTA.
## Root cause
`twenty-server` pins `@react-email/render` directly at `^1.2.3`:
```jsonc
// packages/twenty-server/package.json
"@react-email/render": "^1.2.3",
```
In 1.2.3, `render()` reads `renderToReadableStream` **before** the email
template's async Suspense boundary (i18n/locale load) has resolved. The
result
is the Suspense fallback marker instead of the real markup:
```html
<!DOCTYPE html ...><!--$!--><template></template><!--/$-->
```
This was fixed upstream in `@react-email/render@2.0.6`
(*"await stream.allReady before reading renderToReadableStream
output"*).
`twenty-emails` already resolves a 2.x render via `react-email@6.5.0`,
so the
server's direct pin was simply stale — the two were out of sync.
## Fix
Bump the direct pin to `^2.0.6` (resolves to `2.1.0`) and regenerate the
lockfile. The server's `render()` imports now use the fixed 2.x.
> Note: a `1.2.3` entry remains in `yarn.lock` — it is an internal
transitive
> pin of `@react-email/components@0.5.3`, not the server render path, so
it is
> expected and harmless.
## Verification
Rendering `SendInviteLinkEmail` through the real `render()` (Node 24)
now
returns full markup (5.7 kB) with no Suspense marker and the resolved
invite
link + workspace content, instead of the empty fallback.
A jest unit test was intentionally not added: `@react-email/render` 2.x
uses a
dynamic import that jest's CJS runtime rejects ("A dynamic import
callback was
invoked without --experimental-vm-modules") — which is exactly why the
existing
email specs mock `render`. The fix was verified with a standalone Node
script.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23323?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
**1. Shared Lingui factory in `twenty-shared`**
- Extracted `createI18nInstanceFactory` into
`packages/twenty-shared/src/i18n/create-i18n-instance-factory.ts` so
every package gets the same per-render Lingui bootstrap with a
per-locale singleton cache and a `SOURCE_LOCALE` fallback.
- `twenty-emails/src/utils/i18n.utils.ts` now consumes the shared
factory.
**2. `twenty-website-new` Lingui bootstrap + Crowdin wiring**
- `lingui.config.ts`, `src/lib/i18n/*`, `nx run
twenty-website-new:lingui:{extract,compile}`.
- 31 locale PO files generated; minified compiled output kept out of
Prettier and Oxlint.
- `i18n-{push,pull}.yaml` workflows updated to include
`twenty-website-new` in Crowdin sync.
**3. `app/[locale]/...` segment routing with English at the root**
- All marketing routes moved under `src/app/[locale]/`; static
generation preserved (15 routes × 31 locales = 465 prerendered URLs).
- Middleware behavior:
- `/{en}/...` → 301 redirect to unprefixed canonical.
- `/{non-en}/...` → pass through, set `NEXT_LOCALE` cookie.
### What this PR explicitly does not do (deferred)
- Lingui-wrapping the actual marketing copy. Keys, build pipeline, and
runtime are wired; copy migration is a separate, reviewer-friendlier
PR.
## Summary
This PR enforces the use of `@/` alias for imports instead of relative
parent imports (`../`).
## Changes
### ESLint Configuration
- Added `no-restricted-imports` pattern in `eslint.config.react.mjs` to
block `../*` imports with the message "Relative parent imports are not
allowed. Use @/ alias instead."
- Removed the non-working `import/no-relative-parent-imports` rule
(doesn't work properly in ESLint flat config)
### VS Code Settings
- Added `javascript.preferences.importModuleSpecifier: non-relative` to
`.vscode/settings.json` (TypeScript setting was already there)
### Code Fixes
- Fixed **941 relative parent imports** across **706 files** in
`packages/twenty-front`
- All `../` imports converted to use `@/` alias
## Why
- Consistent import style across the codebase
- Easier to move files without breaking imports
- Better IDE support for auto-imports
- Clearer understanding of where imports come from
Closes#16557
Tiptap Editor (which the Send Email Node uses) , creates a content json
with type 'hardBreak' for line breaks.
The was no rederer defined for this `hardBreak` node type, so the
`renderNode` function was ignoring that node (returning null).
**Fix :** Added a renderer for `hardBreak` node type.
## Description
- This PR address issue -
https://github.com/twentyhq/core-team-issues/issues/1768
- Added listkit bundle from tiptap which includes BulletList,
orderedList, ListItem and ListKeymap in one single import
- This bundle also includes keyboards shortcuts - `Cmd + Shift + 7` and
`Cmd + Shift + 8` for ordered and bullet list
## Visual Appearance
https://github.com/user-attachments/assets/7eff1233-8503-4854-bad2-2521898bc568
## Why this Approach
- our current version of tiptap is 3.4.2 while the latest is 3.8.0 hence
installing these versions manually would install the latest version of
3.8.0. The issue when downgrading to 3.4.2 was that Version 3.8.0 of
`@tiptap/extension-list` requires `renderNestedMarkdownContent` from
@tiptap/core
but our `@tiptap/core` version 3.4.2 doesn't export this function.
Fixes#8601
We had 3 implementations of getImageAbsoluteURI: in twenty-front, in
twenty-ui and in twenty-emails. I was able to remove the one in
twenty-front but I could not remove it from twenty-emails as this is a
standalone for now. The vision is to introduce shared utils in a
twenty-shared package