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.
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.
Create directory
mkdir emailscd emailsnpm initInstall dependencies
npm install react-email -D -Enpm install @react-email/components react react-dom -EAdd scripts to your package.json
{ "scripts": { "dev": "email dev", "export": "email export --outDir ../src/main/resources/templates/html", }}Add node-gradle to your build.gradle.kts
plugins { // ... id("com.github.node-gradle.node") version "7.1.0"}Add spring-boot-starter-mail and spring-boot-starter-thymeleaf dependencies
dependencies { // ... implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.springframework.boot:spring-boot-starter-thymeleaf")}Configure node-gradle and register the exportEmail task
import com.github.gradle.node.npm.task.NpmTask
node { npmInstallCommand = "ci" download = true version = "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 processResources task to depend on exportEmails
tasks.processResources { dependsOn("exportEmails")
// ...}Add the generated html folder to .gitignore
/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 { @Bean fun 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: String val to: String val subject: String val 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) }}Create a new email in emails/emails. For example your-email.tsx
<span> Hello <span th:text="${inviteeName}">Placeholder invitee name</span> ,</span>
<button th:href="@{{host}/teams/join/{token}(host=${host}, token=${token})}" th:text="${joinButtonText}"> Join the team</button>Develop your new email. Run ./gradlew npm_start and open localhost:3000 to preview it.
Learn more about react email in their documentation.
Export the HTML. Once finished, run ./gradlew exportEmails to generate your new HTML email template.
Locate the result. A your-email.html file should now appear in src/main/resources/templates/html.
Add a matching text email. In src/main/resources/templates/txt, create your-email.txt
Hello [(${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" //IMPORTANT override 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!