Skip to content

Commit 6707d38

Browse files
JanCizmardkrizan
andauthored
feat: React Emails > default template (#3194)
Co-authored-by: Daniel Krizan <danyelkrizan@gmail.com>
1 parent add5c96 commit 6707d38

File tree

37 files changed

+408
-239
lines changed

37 files changed

+408
-239
lines changed

backend/api/src/main/kotlin/io/tolgee/controllers/resetPassword/ResetPasswordRequestHandler.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,12 @@ class ResetPasswordRequestHandler(
4747
EmailParams(
4848
to = request.email,
4949
subject = if (isInitial) "Initial password configuration" else "Password reset",
50-
text =
50+
header = "Password reset",
51+
text =
5152
"""
52-
Hello! 👋<br/><br/>
5353
${if (isInitial) "To set a password for your account, <b>follow this link</b>:<br/>" else "To reset your password, <b>follow this link</b>:<br/>"}
5454
<a href="$url">$url</a><br/><br/>
5555
If you have not requested this e-mail, please ignore it.<br/><br/>
56-
57-
Regards,<br/>
58-
Tolgee
5956
""".trimIndent(),
6057
)
6158

@@ -67,15 +64,12 @@ class ResetPasswordRequestHandler(
6764
EmailParams(
6865
to = request.email,
6966
subject = "Password reset - SSO managed account",
67+
header = "Password reset",
7068
text =
7169
"""
72-
Hello! 👋<br/><br/>
7370
We received a request to reset the password for your account. However, your account is managed by your organization and uses a single sign-on (SSO) service to log in.<br/><br/>
7471
To access your account, please use the "SSO Login" button on the Tolgee login page. No password reset is needed.<br/><br/>
7572
If you did not make this request, you may safely ignore this email.<br/><br/>
76-
77-
Regards,<br/>
78-
Tolgee
7973
""".trimIndent(),
8074
)
8175

backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2UserControllerTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ class V2UserControllerTest : AuthorizedControllerTest() {
131131
)
132132
performAuthPut("/v2/user", requestDTO).andIsOk
133133

134-
emailTestUtil.verifyEmailSent()
134+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
135+
emailTestUtil.verifyEmailSent()
136+
}
135137
assertThat(emailTestUtil.messageContents.single())
136138
.contains(tolgeeProperties.frontEndUrl.toString())
137139

backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/organizationController/OrganizationControllerInvitingTest.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ class OrganizationControllerInvitingTest : AuthorizedControllerTest() {
162162
val organization = prepareTestOrganization()
163163

164164
val code = inviteWithUserWithNameAndEmail(organization.id)
165-
emailTestUtil.verifyEmailSent()
165+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
166+
emailTestUtil.verifyEmailSent()
167+
}
166168

167169
val messageContent = emailTestUtil.messageContents.single()
168170
assertThat(messageContent).contains(code)
@@ -176,7 +178,9 @@ class OrganizationControllerInvitingTest : AuthorizedControllerTest() {
176178
val organization = prepareTestOrganization()
177179

178180
inviteWithUserWithNameAndEmail(organization.id)
179-
emailTestUtil.verifyEmailSent()
181+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
182+
emailTestUtil.verifyEmailSent()
183+
}
180184

181185
val messageContent = emailTestUtil.messageContents.single()
182186
assertThat(messageContent).doesNotContain("<a href='https://evil.local")

backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/ProjectsControllerInvitationTest.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ class ProjectsControllerInvitationTest : ProjectAuthControllerTest("/v2/projects
150150
@ProjectJWTAuthTestMethod
151151
fun `sends invitation e-mail`() {
152152
val code = inviteWithUserWithNameAndEmail()
153-
emailTestUtil.verifyEmailSent()
153+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
154+
emailTestUtil.verifyEmailSent()
155+
}
154156

155157
val messageContent = emailTestUtil.messageContents.single()
156158
assertThat(messageContent).contains(code)
@@ -162,7 +164,9 @@ class ProjectsControllerInvitationTest : ProjectAuthControllerTest("/v2/projects
162164
@ProjectJWTAuthTestMethod
163165
fun `uses frontEnd url when possible`() {
164166
inviteWithUserWithNameAndEmail()
165-
emailTestUtil.verifyEmailSent()
167+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
168+
emailTestUtil.verifyEmailSent()
169+
}
166170

167171
val messageContent = emailTestUtil.messageContents.single()
168172
assertThat(messageContent).contains("https://dummy-url.com")

backend/app/src/test/kotlin/io/tolgee/controllers/resetPassword/ResetPasswordControllerTest.kt

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package io.tolgee.controllers.resetPassword
22

3-
import com.posthog.java.PostHog
43
import io.tolgee.development.testDataBuilder.data.BaseTestData
54
import io.tolgee.dtos.request.auth.ResetPasswordRequest
65
import io.tolgee.fixtures.EmailTestUtil
76
import io.tolgee.fixtures.andIsOk
7+
import io.tolgee.fixtures.waitForNotThrowing
88
import io.tolgee.testing.AbstractControllerTest
99
import io.tolgee.testing.assert
1010
import org.junit.jupiter.api.AfterEach
1111
import org.junit.jupiter.api.BeforeEach
1212
import org.junit.jupiter.api.Test
1313
import org.springframework.beans.factory.annotation.Autowired
1414
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
15-
import org.springframework.boot.test.mock.mockito.MockBean
1615

1716
@AutoConfigureMockMvc
1817
class ResetPasswordControllerTest :
@@ -37,25 +36,25 @@ class ResetPasswordControllerTest :
3736
@Autowired
3837
private lateinit var emailTestUtil: EmailTestUtil
3938

40-
@MockBean
41-
@Autowired
42-
lateinit var postHog: PostHog
43-
4439
@Test
4540
fun `email contains correct callback url with frontend url provided`() {
4641
executePasswordChangeRequest()
47-
emailTestUtil.firstMessageContent.assert.contains("https://dummy-url.com/reset_password/")
48-
// We don't want double slashes
49-
emailTestUtil.firstMessageContent.assert.doesNotContain("reset_password//")
42+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
43+
emailTestUtil.firstMessageContent.assert.contains("https://dummy-url.com/reset_password/")
44+
// We don't want double slashes
45+
emailTestUtil.firstMessageContent.assert.doesNotContain("reset_password//")
46+
}
5047
}
5148

5249
@Test
5350
fun `email contains correct callback url without frontend url provided`() {
5451
tolgeeProperties.frontEndUrl = null
5552
executePasswordChangeRequest()
56-
emailTestUtil.firstMessageContent.assert.contains("https://hello.com/aa/")
57-
// We don't want double slashes
58-
emailTestUtil.firstMessageContent.assert.doesNotContain("aa//")
53+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
54+
emailTestUtil.firstMessageContent.assert.contains("https://hello.com/aa/")
55+
// We don't want double slashes
56+
emailTestUtil.firstMessageContent.assert.doesNotContain("aa//")
57+
}
5958
}
6059

6160
private fun executePasswordChangeRequest() {

backend/app/src/test/kotlin/io/tolgee/service/notification/NotificationServiceTest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.tolgee.service.notification
22

33
import io.tolgee.AbstractSpringTest
44
import io.tolgee.development.Base
5+
import io.tolgee.fixtures.waitForNotThrowing
56
import io.tolgee.model.notifications.Notification
67
import io.tolgee.model.notifications.NotificationChannel
78
import io.tolgee.model.notifications.NotificationType
@@ -81,8 +82,10 @@ class NotificationServiceTest : AbstractSpringTest() {
8182
private fun assertInAppNotificationNotExists() = notificationTestUtil.assertNoInAppNotifications()
8283

8384
private fun assertEmailNotificationExists() {
84-
notificationTestUtil.newestEmailNotification().also {
85-
assertThat(it).contains("Password has been changed for your account")
85+
waitForNotThrowing(timeout = 2000, pollTime = 25) {
86+
notificationTestUtil.newestEmailNotification().also {
87+
assertThat(it).contains("Password has been changed for your account")
88+
}
8689
}
8790
}
8891

backend/data/src/main/kotlin/io/tolgee/component/email/EmailVerificationSender.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,31 @@
11
package io.tolgee.component.email
22

33
import io.tolgee.dtos.misc.EmailParams
4+
import io.tolgee.model.UserAccount
45
import org.springframework.stereotype.Component
56

67
@Component
78
class EmailVerificationSender(
89
private val tolgeeEmailSender: TolgeeEmailSender,
910
) {
1011
fun sendEmailVerification(
11-
userId: Long,
12+
user: UserAccount,
1213
email: String,
1314
resultCallbackUrl: String?,
1415
code: String,
1516
isSignUp: Boolean = true,
1617
) {
17-
val url = "$resultCallbackUrl/$userId/$code"
18+
val url = "$resultCallbackUrl/${user.id}/$code"
1819
val params =
1920
EmailParams(
2021
to = email,
2122
subject = "Tolgee e-mail verification",
22-
text =
23-
"""
24-
Hello! 👋<br/><br/>
23+
header = "Verify your e-mail",
24+
text = """
2525
${if (isSignUp) "Welcome to Tolgee. Thanks for signing up. \uD83C\uDF89<br/><br/>" else ""}
2626
2727
To verify your e-mail, <b>follow this link</b>:<br/>
2828
<a href="$url">$url</a><br/><br/>
29-
30-
Regards,<br/>
31-
Tolgee
3229
""".trimIndent(),
3330
)
3431
tolgeeEmailSender.sendEmail(params)

backend/data/src/main/kotlin/io/tolgee/component/email/InvitationEmailSender.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,10 @@ class InvitationEmailSender(
2323
subject = "Invitation to Tolgee",
2424
text =
2525
"""
26-
Hello! 👋<br/><br/>
2726
Good news. ${getInvitationSentence(invitation)}<br/><br/>
2827
2928
To accept the invitation, <b>follow this link</b>:<br/>
3029
<a href="$url">$url</a><br/><br/>
31-
32-
Regards,<br/>
33-
Tolgee
3430
""".trimIndent(),
3531
)
3632
tolgeeEmailSender.sendEmail(params)
Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,28 @@
11
package io.tolgee.component.email
22

3-
import io.tolgee.configuration.tolgee.TolgeeProperties
43
import io.tolgee.dtos.misc.EmailParams
5-
import org.springframework.core.io.ClassPathResource
6-
import org.springframework.mail.javamail.JavaMailSender
4+
import io.tolgee.email.EmailService
75
import org.springframework.stereotype.Component
6+
import java.util.Locale
87

98
@Component
109
class TolgeeEmailSender(
11-
private val tolgeeProperties: TolgeeProperties,
12-
private val mailSender: JavaMailSender,
13-
private val mimeMessageHelperFactory: MimeMessageHelperFactory,
10+
private val emailService: EmailService,
1411
) {
1512
fun sendEmail(params: EmailParams) {
16-
validateProps()
17-
val helper = mimeMessageHelperFactory.create()
18-
helper.setFrom(params.from ?: tolgeeProperties.smtp.from!!)
19-
helper.setTo(params.to)
20-
params.replyTo?.let {
21-
helper.setReplyTo(it)
22-
}
23-
if (!params.bcc.isNullOrEmpty()) {
24-
helper.setBcc(params.bcc!!)
25-
}
26-
helper.setSubject(params.subject)
27-
val content =
28-
"""
29-
<html>
30-
<body style="font-size: 15px">
31-
${params.text}<br/><br/>
32-
<img style="max-width: 100%; width:120px" src="cid:logo.png" />
33-
</body>
34-
</html>
35-
""".trimIndent()
36-
helper.setText(content, true)
37-
38-
params.attachments.forEach {
39-
helper.addAttachment(it.name, it.inputStreamSource)
40-
}
41-
42-
helper.addInline(
43-
"logo.png",
44-
{ ClassPathResource("tolgee-logo.png").inputStream },
45-
"image/png",
13+
val properties = mapOf<String, Any>()
14+
.let { if (params.text != null) it.plus("content" to params.text!!) else it }
15+
.let { if (params.header != null) it.plus("header" to params.header!!) else it }
16+
.let { if (params.recipientName != null) it.plus("recipientName" to params.recipientName!!) else it }
17+
emailService.sendEmailTemplate(
18+
recipient = params.to,
19+
subject = params.subject,
20+
template = params.templateName ?: "default",
21+
locale = Locale.ENGLISH,
22+
properties = properties,
23+
attachments = params.attachments,
24+
bcc = params.bcc,
25+
replyTo = params.replyTo,
4626
)
47-
48-
mailSender.send(helper.mimeMessage)
49-
}
50-
51-
private fun validateProps() {
52-
if (tolgeeProperties.smtp.from.isNullOrEmpty()) {
53-
throw IllegalStateException(
54-
"""tolgee.smtp.from property not provided.
55-
|You have to configure smtp properties to send an e-mail.
56-
""".trimMargin(),
57-
)
58-
}
59-
}
60-
61-
fun getSignature(): String {
62-
return """
63-
<br /><br />
64-
Best regards,
65-
<br />
66-
Tolgee Team
67-
""".trimIndent()
6827
}
6928
}

backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,10 @@ open class TolgeeProperties(
135135
description = "LLM Providers configuration",
136136
)
137137
var llmProperties: LlmProperties = LlmProperties(),
138+
@DocProperty(
139+
description = "Public URL of the Tolgee API endpoint. While this typically matches the 'frontEndUrl', " +
140+
"it should be set separately when running the backend on a different URL." +
141+
"\n\n"
142+
)
143+
var backEndUrl: String? = null,
138144
)

0 commit comments

Comments
 (0)