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 emails
cd emails
npm init
Install dependencies
npm install react-email -D -E
npm install @react-email/components react react-dom -E
Add 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.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Service
import org.thymeleaf.TemplateEngine
import org.thymeleaf.spring6.SpringTemplateEngine
import org.thymeleaf.TemplateEngine
import org.thymeleaf.context.Context
@Configuration
class ThymeleafConfiguration {
@Bean
fun templateEngine(): TemplateEngine = SpringTemplateEngine()
}
@Service
class 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)
@Service
class 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.
@Service
class 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!