In this guide, you’ll learn how to integrate React Email into your Kotlin Spring project using Gradle. With React Email, you can benefit from React’s component-based architecture to build email templates more efficiently.
Why opt for JavaScript when I already have Thymeleaf? (and still need Thymeleaf)
Although Thymeleaf is a powerful and essential templating engine—particularly for our purposes here—its developer experience (DX) for designing email templates can be less than ideal. This is where React Email shines. By leveraging React’s component composability, you can create and maintain sophisticated email designs in a far more streamlined way than with Thymeleaf alone.
Getting started
-
Create directory
Terminal window mkdir emailscd emailsnpm init -
Install dependencies
Terminal window npm install react-email -D -Enpm install @react-email/components react react-dom -E -
Add scripts to your
package.jsonTerminal window {"scripts": {"dev": "email dev","export": "email export --outDir ../src/main/resources/templates/html",}} -
Add
node-gradleto yourbuild.gradle.ktsplugins {// ...id("com.github.node-gradle.node") version "7.1.0"} -
Add
spring-boot-starter-mailandspring-boot-starter-thymeleafdependenciesdependencies {// ...implementation("org.springframework.boot:spring-boot-starter-mail")implementation("org.springframework.boot:spring-boot-starter-thymeleaf")} -
Configure
node-gradleand register theexportEmailtaskimport com.github.gradle.node.npm.task.NpmTasknode {npmInstallCommand = "ci"download = trueversion = "22.0.0"workDir = rootDir.resolve(".gradle/nodejs")npmWorkDir = rootDir.resolve(".gradle/npm")nodeProjectDir = rootDir.resolve("emails")}tasks.register<NpmTask>("exportEmails") {inputs.dir(rootDir.resolve("emails/emails"))inputs.files(rootDir.resolve("emails/package.json"), rootDir.resolve("emails/package-lock.json"))outputs.dir(projectDir.resolve("src/main/resources/templates/html"))dependsOn("npmInstall")npmCommand.addAll("run", "export")} -
Configure the
processResourcestask to depend onexportEmailstasks.processResources {dependsOn("exportEmails")// ...} -
Add the generated html folder to
.gitignoreTerminal window /src/main/resources/templates/html/ -
Create functionality for sending emails
import org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.stereotype.Serviceimport org.thymeleaf.TemplateEngineimport org.thymeleaf.spring6.SpringTemplateEngineimport org.thymeleaf.TemplateEngineimport org.thymeleaf.context.Context@Configurationclass ThymeleafConfiguration {@Beanfun templateEngine(): TemplateEngine = SpringTemplateEngine()}@Serviceclass EmailTemplateService(private val templateEngine: TemplateEngine,) {fun getRenderedMail(email: Email): EmailTemplateResponse {val context = email.context.applyDefaultContext(email.subject)return EmailTemplateResponse(plain = templateEngine.process("txt/${email.templateName}.txt",context,),html = templateEngine.process("html/${email.templateName}.html",context,),)}private fun Context.applyDefaultContext(subject: String) = this.apply {setVariable("metaTitle", subject)// Here can be added any default values for the context you want to have available in every email}}interface Email {val templateName: Stringval to: Stringval subject: Stringval context: Context}data class EmailTemplateResponse(val plain: String, val html: String)@Serviceclass EmailService(private val emailTemplateService: EmailTemplateService,) {fun send(email: Email) {val template = emailTemplateService.getRenderedMail(email)val mailSender = //...val mimeMessage: MimeMessage = mailSender.createMimeMessage()val helper = MimeMessageHelper(mimeMessage, true, "UTF-8")helper.setTo(email.to)helper.setSubject(email.subject)if (emailDto.html !== null) {helper.setText(template.plain, template.html)} else {helper.setText(template.plain)}helper.setFrom("your service <${email@your-service.com}>")mailSender.send(mimeMessage)}}
Adding a new mail
-
Create a new email in
emails/emails. For exampleyour-email.tsx<span>Hello<span th:text="${inviteeName}">Placeholder invitee name</span>,</span><buttonth:href="@{{host}/teams/join/{token}(host=${host}, token=${token})}"th:text="${joinButtonText}">Join the team</button> -
Develop your new email. Run
./gradlew npm_startand openlocalhost:3000to preview it.Learn more about react email in their documentation.
-
Export the HTML. Once finished, run
./gradlew exportEmailsto generate your new HTML email template. -
Locate the result. A
your-email.htmlfile should now appear insrc/main/resources/templates/html. -
Add a matching text email. In
src/main/resources/templates/txt, createyour-email.txtHello [(${inviteeName})],[(${joinButtonText})]: "[(${host})]/teams/invite/[(${token})]" -
Create your Kotlin email representation.
data class YourEmail(inviteeName: String, host: String, token: String) : Email {override val templateName = "your-email" //IMPORTANToverride val to = listOf(email)override val subject = "Your email subject"override val context = Context().apply {setVariable("inviteeName", inviteeName) // All variables you need in your template.setVariable("joinButtonText", "Join the team $inviteeName")setVariable("host", host)setVariable("token", token)}} -
Use your new email anywhere in your code.
@Serviceclass UserService(val emailService: EmailService,)emailService.send(YourEmail(inviteeName = "Max",host = "https://abc.xyz",token = "1234"))Remember that you should not commit the generated HTML to version control—those files will be automatically generated at build time. However, you do need to commit your text email templates.
Happy emailing!
