tolgee-platform icon indicating copy to clipboard operation
tolgee-platform copied to clipboard

feat: well designed transactional e-mails

Open cyyynthia opened this issue 2 years ago • 9 comments

This PR introduces tooling for writing HTML emails using React Email, that is then compiled to Thymeleaf templates the backend can consume.

Features

  • Familiar React syntax, that'll get compiled to ugly (but email-friendly) HTML
  • Helpers to easily make conditionals and for-loops based on variables set during final render before sending
  • Live preview & hot-reload, with some linting and miscellaneous convenience tools from React Email
  • i18n-ready: uses ICU4J to render strings with proper localization when rendering the email to send
  • Supports FormatJS's XML tags rendering. Allows for convenient use of advanced markup inside i18n strings
  • Tolgee CLI fully configured and integrated, with a custom extractor to automagically sync strings
  • Templates can differ based on whether the email is being sent from Tolgee Cloud or not
    • This is detected by the presence of billing capabilities

The email design isn't the one made in #2710, but rather the first draft I originally made when first prototyping with this more than a year ago. (quick note: the design has a different footer based on whether it's sent from Cloud or Self-Hosted). This is why I'm marking this as "chore" more than "feat"; it doesn't bring any feature to the product yet.

I added exports to the package.json as a potential way to have additional email template workspaces in ee and billing: those would be able to import core parts like the layout and essential components/parts, while also defining their own component/parts separately, providing good isolation of them and keeping clear legal boundaries between Tolgee OSS, Tolgee EE, and proprietary Tolgee Cloud licenses.

I included documentation about how to use it, in email/HACKING.md. Gradle config has been updated to build emails as part of the backend build process.

Closes #2707

Summary by CodeRabbit

  • New Features

    • Full email system: React-based templates, layouts, components, localization, image/resource handling, build/export tooling, and centralized template-rendered sending with subject extraction and global variables.
  • Bug Fixes

    • Improved email delivery/verification stability by adding retry/wait handling in tests and flows.
  • Tests

    • New unit/integration tests for email rendering, ICU formatting, and security; added email template test harness.
  • Documentation

    • Comprehensive guide for creating and testing email templates.
  • Chores

    • CI/workflow and build updates to integrate email build steps, packaging, and artifacts.

cyyynthia avatar Nov 14 '23 10:11 cyyynthia

Walkthrough

Adds a full email subsystem (React Email templates, extractor, i18n, Tailwind, docs), integrates email build into Gradle and CI, introduces Kotlin EmailService/engine/resolver, adapts senders/tests for async email delivery, modernizes Gradle tasks, and updates E2E email tooling and fixtures.

Changes

Cohort / File(s) Summary
Backend — Email core
backend/data/src/main/kotlin/io/tolgee/email/*
New EmailService, EmailTemplateEngine, EmailMessageResolver, EmailGlobalVariablesProvider and EmailTemplateConfig for Thymeleaf-based email rendering, ICU handling and global variables.
Backend — Email usage & DTOs
backend/data/src/main/kotlin/io/tolgee/component/email/*, backend/data/src/main/kotlin/io/tolgee/dtos/misc/EmailParams.kt, backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt
TolgeeEmailSender now delegates to EmailService; EmailParams fields changed (nullable text, header, locale, templateName, recipientName); TolgeeProperties adds backEndUrl; some senders updated to accept UserAccount and email body/header text adjusted.
Backend — Resolver / i18n plumbing
backend/data/src/main/kotlin/io/tolgee/email/EmailMessageResolver.kt, backend/data/src/main/kotlin/io/tolgee/email/EmailTemplateEngine.kt, backend/data/src/main/resources/I18n_en.properties
New message resolver post-processes ICU/XML fragments into Thymeleaf fragments; engine wiring added; notification template simplified to use dynamic placeholder.
Backend — Tests & fixtures
backend/data/src/test/kotlin/io/tolgee/email/*, backend/data/src/test/resources/email-i18n-test/*, backend/testing/.../EmailTestUtil.kt, backend/app/src/test/...
Unit tests for EmailService and EmailGlobalVariablesProvider; test i18n resources; EmailTestUtil sets backEndUrl; many tests updated to use retry/wait wrappers for email assertions.
Frontend — Email package (React + tooling)
email/**/*, email/.config/extractor.ts, email/.config/tolgeerc.json, email/package.json, email/tsconfig.json, email/tailwind.config.ts, email/HACKING.md
New email monorepo package: React Email components (If, For, Var, ImgResource, LocalizedText, translate, atoms, layouts), templates (default, registration, tests), extractor for i18n keys, Tolgee CLI config, lint/format/tailwind and documentation.
Gradle — email integration & build scripts
gradle/email.gradle, build.gradle, gradle/*.gradle, settings.gradle, gradle.properties
New gradle/email.gradle tasks (installEmailDeps, buildEmails, buildTestEmails, copyEmailTemplates, copyEmailLocaleData, ...); large migration to lazy task registration (tasks.named / tasks.register), repo/catalog additions (jitpack/vaadinJson) and gradle.properties tuning.
CI / Actions
.github/actions/*, .github/workflows/*
Composite actions updated with source/target dir inputs and Docker options; upload/download backend build actions include email artifact; test workflows add email-code-checks job and SKIP_EMAIL_BUILD flags; release workflow updated to metadata + docker/build-push-action; some workflows removed.
E2E / Cypress & SMTP
e2e/cypress/common/apiCalls/common.ts, e2e/cypress/e2e/**, e2e/docker-compose.yml
E2E email API refactor to per-email retrieval primitives (getLatestEmail, getEmail, fetchEmails); tests clear emails before runs; fake SMTP image switched to Mailpit and port updated.
Misc — modernizations & formatting
backend/app/src/main/kotlin/io/tolgee/Application.kt, backend/data/src/main/kotlin/io/tolgee/configuration/HibernateConfig.kt, DEVELOPMENT.md, .gitmodules, various tests
Exclude Thymeleaf auto-config, add Hibernate trace profile, add email HACKING reference in docs, remove demos submodule entry, and multiple stylistic/indentation updates across tests and build scripts.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Application / Sender
    participant Tolgee as TolgeeEmailSender
    participant Service as EmailService
    participant Engine as EmailTemplateEngine (Thymeleaf)
    participant Mailer as JavaMailSender

    Caller->>Tolgee: request send (EmailParams / template)
    Tolgee->>Service: sendEmailTemplate(recipient, template, locale, props...)
    Service->>Engine: process(template, context + global vars)
    Engine-->>Service: rendered HTML
    Service->>Mailer: send MimeMessage (attachments, bcc, replyTo)
    Mailer-->>Service: sent / error
    Note right of Service: method annotated @Async — returns immediately
sequenceDiagram
    participant NPM as npm (email package)
    participant Extractor as extractor.ts
    participant Gradle as gradle/email.gradle
    participant Backend as backend build

    NPM->>NPM: build/export templates (out/html)
    NPM->>Extractor: extract i18n keys from TSX
    Extractor-->>Gradle: i18n files & out/html produced
    Gradle->>Gradle: copyEmailTemplates & copyEmailLocaleData -> build/generated/resources
    Gradle-->>Backend: processResources includes generated templates/resources

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Areas needing extra attention:

  • EmailService: template rendering, subject extraction regex, attachment handling, async behavior and error paths.
  • EmailMessageResolver.postProcessMessage: regex/XML-to-fragment transformation edge cases.
  • gradle/email.gradle tasks and SKIP_EMAIL_BUILD gating (inputs/outputs, up-to-date behavior).
  • E2E email API changes and Mailpit integration (types, port, tests relying on HTML parsing).

Possibly related PRs

  • tolgee/tolgee-platform#3126 — modifies the same GitHub Actions composite actions and CI workflow artifacts; strongly related to action YAML edits here.
  • tolgee/tolgee-platform#3204 — touches mail-related dependencies and wiring that may intersect with the new EmailService and mail runtime.

Suggested reviewers

  • JanCizmar

Poem

🐇 I stitched templates with tiny hops of code,
Thymeleaf and React along the bunny road.
Gradle wakes tasks when emails need to bloom,
Mailpit catches letters, safe from doom.
A rabbit cheers: templates now find home!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The changeset includes two modifications that appear unrelated to the email infrastructure objectives: removal of the flagmoji submodule entry from .gitmodules, and addition of a new HibernateConfig.kt file implementing QueryStackTraceLogger for Hibernate SQL debugging under the hibernate-trace profile. These changes are not mentioned in the PR objectives, linked issues, or described functionality for the email feature and would benefit from clarification on their necessity or inclusion in a separate refactoring PR. The author should clarify whether the flagmoji submodule removal and HibernateConfig additions are intentional inclusions for this PR or accidentally included changes. If these changes are not necessary for the email feature implementation, they should be removed from this PR and submitted as separate changes. If they are supporting infrastructure (e.g., for profiling email rendering performance), this should be documented in the PR description for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: well designed transactional e-mails" is a clear, concise, single sentence that accurately summarizes the main change in the changeset. The title directly refers to the primary objective of implementing React Email-based transactional email infrastructure with Thymeleaf compilation, live preview tooling, localization support, and conditional/iterative rendering capabilities. The title is specific and meaningful enough that a teammate scanning the history would understand the significant infrastructure addition being introduced.
Linked Issues Check ✅ Passed The code changes address all primary objectives from linked issue #2707. The implementation handles FormatJS-style pseudo-XML tag processing through the translation system (translate.ts supporting tag rendering and composition), renders newlines as
elements via the addBrToTranslations function, enforces HTML security through proper escaping with ${#strings.escapeXml(variable)} in Var.ts and throughout the template rendering pipeline, and provides global variables via the EmailGlobalVariablesProvider component that supplies instance context and cloud detection to templates. All checked requirements from the linked issue are demonstrably implemented in the code changes.
✨ Finishing touches
  • [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch cynthia/react-email

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Jun 10 '25 07:06 coderabbitai[bot]

I assume Snyk is unhappy about my addition of JitPack repository. 😅

Requesting review from both Jan and Stepan, as there were some wishes expressed in #2710 😃

cyyynthia avatar Jun 17 '25 14:06 cyyynthia

Hi ^^ I had a quick look through the Snyk report: It looks like the new dependency com.github.transferwise:spring-icu:0.3.0 is pulling in org.springframework:spring-beans:@5.2.5.RELEASE which seems to be connected to a CVE (https://www.cve.org/CVERecord?id=CVE-2022-22965). According to Snyk, it is fixed in org.springframework:[email protected], @5.3.18.

Anty0 avatar Jun 17 '25 15:06 Anty0

Bleh, this explains that. I guess I'll just replace it with a custom impl, it seems there's barely any lib that does it, and it should be simple enough... Plus we already pull icu4j so it's just a matter of connecting the bits together... 😮‍💨

Thanks for the info 👍🏻

cyyynthia avatar Jun 17 '25 16:06 cyyynthia

Yee, or we might be able to pin the spring-beans library to a newer version. It's not like the spring-icu library will care if we use spring-beans:@5.2.5 or @5.2.20 with it. :3

Anty0 avatar Jun 17 '25 16:06 Anty0

Well doing it manually implies copy/pasting an entire class from Spring, so here we are with some exclusions to get rid of the problems 😅

I also tweaked (quite a bit) the Gradle scripts, using lazy constructs and fiddling with some flags. It doesn't seem to have an impact on CI, but I assume it'll start doing some wonders as soon as cache gets warm (PR is read-only cache). It has significantly improved build times on my machine.

cyyynthia avatar Jun 17 '25 21:06 cyyynthia

I adjusted GHA tasks here so they can be used in other repositories, and improved Docker image build (namely: better image tagging with support for vX, vX.Y, vX.Y.Z tags, and publishing provenance attestations, which aligns with best practices wrt supply chain security and integrity validation)

cyyynthia avatar Jun 20 '25 12:06 cyyynthia

@dkrizan can we close this one? Looks like you're working on it in separate PR, right?

JanCizmar avatar Oct 06 '25 17:10 JanCizmar

@dkrizan can we close this one? Looks like you're working on it in separate PR, right?

thats right, this is the PR, which you closed so I am reopening it.

dkrizan avatar Oct 13 '25 08:10 dkrizan