Repository navigation
Guides and examples: email providers, i18n, monorepos, MJML and Beefree, upgrading to 0.3 - #71
Open
ivoIturrieta wants to merge 64 commits into
Open
ivoIturrieta wants to merge 64 commits into
ivoIturrieta wants to merge 64 commits into
Conversation
… i18n example The content-workflows example adds Mailgun, Azure, MailerSend, Scaleway, Plunk, Lettermint, Brevo, Inbound and EUSend, each tested against a mocked endpoint. The new i18n example renders one template in English and Arabic with react-intl, react-i18next and next-intl, right to left included, and runs in CI. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
The monorepo guide previews templates with a small render script, so it needs nothing that isn't published. The READMEs point to the Elements skill in unlayer/unlayer-skills, and list @unlayer/migrate as published, with its MJML and Beefree sources. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
The MJML guide leads with `--from mjml` (what becomes blocks, what stays MJML's HTML, includes, MJML 3, what the check compares against), with the hand mapping kept for tuning. The Beefree guide covers the JSON it reads, the HTML export to check against, and declared differences. Both were checked by running the CLI on the committed fixtures. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
What changes from 0.2 to 0.3 and what to check (the text block's markup, images sized in their slot, the editor's base stylesheet, the Provider, right-to-left alignment, migrate's range), and the 0.1 to 0.2 guide. The changelog points to it. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
The example runs on Node 20 (its script and test pass there), but the Azure SDK's packages declare Node 22 or newer, so the guide and the example's README say to use 22+ to send with Azure. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
ivoIturrieta
marked this pull request as ready for review
October 16, 2026 15:24
The 0.2 to 0.3 section now says that Elements renders templates with a renderer of its own (no react-dom in a render, each component run once, Next.js's server layers and edge runtimes, and the Pages Router edge route's `unstable_allowDynamic` with webpack), that it checks the installed React (`UNLAYER_SKIP_REACT_CHECK=1`), that a part that fails throws a `RenderError` by default (`onError` and `omitted` to leave it out), that async and suspending components fail the synchronous renderers, and that `useId()` gives React's own ids, with `idPrefix` among what's new. A Row that doesn't fit its layout fails the render now, where the guide said it was left out. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
The upgrade guide gives React 18.0 to 18.3 as supported, says that a render throws by default and that error boundaries don't catch, what a <Suspense> around the template does, how the plain text differs from 0.2's, that a class component around the root is unwrapped, and, under what's new, that Tailwind's logical classes follow the text direction and that Markdown keeps raw HTML to an allowlist. The i18n guide says an async component fails the render and that a class provider's root settings are read; the monorepo guide says what two copies of React do now that Elements renders without react-dom. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
…nt text fails The MJML guide gave <mjml dir="rtl"> as textDirection="rtl" and a section's direction="rtl" as always listed. A right-to-left email in Elements lays out every Row's columns right to left, which MJML's dir doesn't, so dir="rtl" is kept only where no section or group shows two or more columns left to right; otherwise the run lists it as not kept. A section's or group's direction="rtl" is listed only in a left-to-right email, and the table to approximate it says how columns render in each direction. The MJML and Beefree guides also say that clear text turned too faint to read fails the check. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
khashaba
approved these changes
Oct 20, 2026
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
…plate's error names it The upgrade guide said Elements is tested with React 19.0: the tests run on React 18.0, 18.2, 18.3, 19.1, 19.2 and 19.3, as the README now says. The synchronous renderers now throw a RenderError naming an async or suspending template (or the component around its root that waits), as they do for one inside it: the upgrade guide gives the message with the component's name, and the i18n guide says the error for a template whose messages are loading is that RenderError. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
0.2 passed an app's renderToString providers through to a context a template doesn't provide; 0.3 reads the app's value there only before it has learned that context's default. The 0.2-to-0.3 checklist says so, and what to do instead: put the provider inside the element rendered, or pass the value as a prop. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
0.3 writes a javascript: URL in the links and images it writes as one that throws with React 18 too, where 0.2 wrote it as given there, and a Button's or Image's link of null renders as none, where 0.2 failed: both are listed with what else changed. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
…s are skipped 0.3 now writes the editor's exporters' own links (a Button's, an Image's, an email's Menu and Social) with a javascript: URL as one that throws too, which 0.2 wrote as given, and skips a null Menu item or Social icon, where 0.2 failed: the upgrade guide's entries say so. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
… can't add markup The upgrade guide's "Also changed" said any prop given as `null` is one not given: only a style prop is; a content prop given as `null` still renders no content, as in 0.2. A Video's id joins the values that can't add markup, percent-encoded where it holds what an id doesn't. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <[email protected]>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on the MJML and Beefree importers PR (#70), which is stacked on the 0.3.0 library PR (#69): it shows only the guides and examples. Merge it after both, with its base changed to
mainfirst. The repo's tests only run on pull requests intomainordevelop, so CI starts here once the base ismain; until then, the validation below was run locally, with #70's head72c037d(which has #69's head11ec4e2) merged in.What
<FormattedMessage>/<Trans>children, right alignment by default in RTL, Tailwind's logical classes following the direction, a class provider such as<IntlProvider>around the root, and an async component failing a synchronous render).npx @unlayer/migrate <dir> --from mjml: what becomes editable blocks, what stays MJML's HTML, includes, MJML 3, what the check compares against (text made too faint to read fails it), and direction:<mjml dir="rtl">becomes a right-to-left email only where no section or group shows its columns left to right, since Elements lays out every Row in the email's direction and MJML doesn't. The hand mapping stays, for tuning the output.?for unverified, and the declared differences.<Suspense>around the template, how the plain text differs, React's ids, migrate's range, an app'srenderToStringproviders reaching a template only till Elements learns the context's default, …), what else changed (a class component around the root is unwrapped), the new entries (Tailwind's logical classes follow the text direction; Markdown keeps raw HTML to an allowlist), and the 0.1 to 0.2 guide.@unlayer/migrateas published, with its MJML and Beefree sources. The changelog only gets a line pointing to the upgrade guide (its 0.1.1 to 0.2.1 history is in @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69).Validation
0.3.0 (2026-10-22)next to the line this PR adds pointing to the upgrade guide; both are kept. The last ones, which bring @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's optional peers of any version (Tailwind CSS 3 and prettier 2 stay installed, with messages that say to upgrade them; the Tailwind and async guides say so) and its Next.js 16 consumer on 16.4.2, had none. The upgrade and i18n guides follow @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's last fixes (the React versions the tests run on; an async template given to a synchronous renderer is aRenderErrornaming it). The merge before it, which brought @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's fixes for Tailwind and link-target values that could end an attribute, Markdown's size limits, inline JSX's encoded URLs, merge tags in the plain text, and what a context reads around an app's renders, had no conflicts; the upgrade guide adds item 17 for the last of these (0.2.1 passed an app'srenderToStringproviders through; 0.3.0 reads the app's value only before it has learned the context's default, as @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's tests pin). A merge of @unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head that brought @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's fixes for very long Markdown, Markdown and Tailwind classes from data rendering in time linear in their size, a table in nested quotes charged once, a class whoseurl()could run code refused, and link targets, Menu text and Social names that aren't strings had no conflicts. A merge of @unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head that brought the round before: ajavascript:URL in the links Elements writes is one that throws with React 18 too; a render reads its first 2,000 different Tailwind classes; only the Tailwind class built alone and seen to fail is refused; a<Tailwind>'s own classes are built at once; acid:or base64 imageurl()is kept; a link ofnullis none had no conflicts. A merge of @unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head (@unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's headec22f7e) brought @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's last fixes: a Tailwind plugin that throws for one class fails only the component whose class it is, from the first render, and changes nothing in other renders on the same handle (where Tailwind compiles again, a render that needs a class fails saying to render again, and the async renderers wait); the links the editor's exporters write (a Button's, an Image's, an email's Menu and Social) get ajavascript:URL as one that throws, in the HTML and the design; anullMenu item or Social icon is skipped; the Markdown guide gives output and memory per kilobyte. The latest merge of @unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head (72c037d, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head11ec4e2) brings @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's last fixes: every timing test in the react and shared packages through one helper (a cost at two sizes timed again where it looks slow, each size's fastest of three samples of 20 ms at least; a cost against a limit run twice more where it's over, held to the limit times the machine's measured slowness), after one more CI flake on a 5 ms baseline; and the streamed-context test's page data released once its shell is written, not on a timer that React 18's setImmediate-scheduled shell could lose to. The merge before it (@unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's headaa39347, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head8c7eef3) brought: Tailwind's stylesheet, which each build reads again whole, read in time linear in its size on Node 20 (a compiler's of 2,400 classes, 114 KB, took 118 ms a build, now 5.6 ms); the Tailwind class growth fuzz retimed, with the one variant shape Tailwind's own compiler reads in time squared left out and read at the 512-character cap under 100 ms. The merge before it (@unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's heada60e642, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head37c8880) brought: tests that hold on CI's runners (a turn of the event loop between tests, so vitest's report of them can't time out; the Tailwind swap test's stylesheets read where Elements reads them; the growth tests time a shape that looks slow again, then at ten times the size, and each quote against a quarter of its size; the deep code test within Node 20's stack; 30 s a test), and docs that say what ships (moduleResolution: "node"needsskipLibCheck, an image's source is written as given, a prop leaves out aurl()in double quotes). The merge before it (@unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head72d24c6, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head1c1a3c2) brought: markup's inline styles hold what an escapedstylecan (a theme's double-quoted font names,url("…"), a string's;), and refuse only what could end the attribute or start a tag once read, or a URL a component can't take (url(javascript:…)); a render's shared work is kept across copies of Elements and in all the code it runs; plain text leaves out blocked URLs and keeps its placeholders from text that decodes to them; text placed straight in a Row or Column is said once per render; the main entry's budget is 221 KB. The merge before it (@unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head07ef311, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head1894c35) brought: a handle keeps a fresh Tailwind compiler ready, so a synchronous loop of renders gets one at once, and past 8,000 classes on one compiler with none ready a render that needs a new class fails saying to yield or use the async renderers; markup inlines no class value a prop can't take; a render's shared work is kept to its own render (template wrappers, nested renders) and Markdown's table cells count against it; code themes are read by their own names; plain text's skip names are read as written; the main entry's budget is 219.5 KB. The merge before it (@unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head94c22fe, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head4613284) brought: Tailwind's shared compiler is given only classes it builds in time (512 characters, 8 variants, 4&in a class's variants, brackets 8 deep, a render's 64 KB of classes), and a class its build throws for without keeping it is found and left out; a Markdown's headings' and rich text children's classes are compiled with the render's others; an image's size string is read once; Markdown's and code's work is shared across a render, not per block; plain text's tag names are indexed by their parts; the main entry's budget is 218 KB. The merge before it (@unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head8c16073, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's headcdbbce2) brought: a content prop given asnullrenders nothing, as in 0.2.1; a logo's height class keeps its shape; a Video's id can't add markup; Tailwind classes, rich text's markup, HTML's plain text, code's trailing whitespace, and Markdown's would-be tables, link text, list items, paragraph lines and references are read in time linear in their size; the main entry's budget is 216 KB (the upgrade guide's "Also changed" now says a style prop given asnullis one not given and a content prop still renders nothing, and adds a Video's id to the values that can't add markup). The merge before it (@unlayer/migrate: MJML and Beefree importers (--from mjml|beefree) #70's head4b6a73d, @unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head739bc41) brought: a Tailwind plugin's error tied to its class by the value Tailwind gave the function; Tailwind classes in Heading and Button children and on a lone<Link>inlined; a prop given asnullone not given; a<script>'s or<style>'s text left out of rich text; a Row's anchor, a Video's link and thumbnail and a Social icon's image kept from adding markup; a Font'sjavascript:URL loading nothing; highlight.js's time bounded; Markdown's quotes and task lists anddesignToElements()linear in their size (the upgrade guide's "Also changed" adds anullprop and the anchors and video links). The merge before it (@unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head0bc71fe) brought: Markdown's inline syntax from data is read in time linear in its size; a Tailwind plugin's function that gives nothing, or an@applyTailwind can't compile, no longer breaks the shared handle, and an error that can't be tied to a class fails only its render; an async render that renders again lists and says each part it leaves out once; the docs say a Table's cells are HTML and style props are written as given, and the Markdown guide gives its densest shapes' output, time and memory (the upgrade guide's entry on what's written as given lists them too). The merge before it (@unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes #69's head06a1825) brought: a block's link is read with its character references decoded (javascript:,javascript:), in every shape, so nojavascript:link the editor's exporters write runs; a Tailwind plugin's error is pinned on its class where another utility gives the class CSS too and inside compound variants, in every render; an async render whose own class makes Tailwind compile again renders again once it has; the Markdown guide gives memory for its worst shape, deeply nested quotes. It had no conflicts, and the upgrade guide's "Also changed" lists what of these changes what 0.2 did: everyjavascript:link runs nothing (the editor's exporters' own links too, and on React 18), and a link or a Menu item or Social icon ofnullis none. The packages' tests pass on the merged branch (react 1,787, migrate 366, from-react-email 344, convert-core 220, shared 129), and the typechecks are clean.?).--strict, includes outside the folder, MJML 3, unknown Beefree versions and the Elements version check were run before the merge, which doesn't change that code; migrate's tests for them pass.<Suspense>around the template, Tailwind's logical classes in right-to-left), and so were the i18n and monorepo guides' statements about errors, class providers and two copies of React.@unlayer/migrateREADME, and the MJML and Beefree guides' faint-text failure follows its check; migrate's CLI tests for a right-to-left MJML template and for links made too faint to read pass on the merged branch.