Skip to content

Commit 01561e8

Browse files
authoredDec 17, 2024
Merge pull request #27 from APSfurizon/email_sender
Email sender
2 parents 0bfcb11 + aad4258 commit 01561e8

File tree

9 files changed

+197
-1
lines changed

9 files changed

+197
-1
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@ build/
3838
### Configurations ###
3939
.env
4040
.env.local
41+
42+
/jte-classes

‎application/pom.xml

+10-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,16 @@
112112
<artifactId>bcpkix-jdk18on</artifactId>
113113
<version>1.79</version>
114114
</dependency>
115-
115+
<dependency>
116+
<groupId>gg.jte</groupId>
117+
<artifactId>jte-spring-boot-starter-3</artifactId>
118+
<version>3.1.15</version>
119+
</dependency>
120+
<dependency>
121+
<groupId>gg.jte</groupId>
122+
<artifactId>jte</artifactId>
123+
<version>3.1.15</version>
124+
</dependency>
116125
</dependencies>
117126
<dependencyManagement>
118127
<dependencies>

‎application/src/main/java/net/furizon/backend/infrastructure/configuration/ThreadConfig.java

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
66
import org.springframework.context.annotation.Bean;
77
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.context.annotation.Primary;
89
import org.springframework.core.task.AsyncTaskExecutor;
910
import org.springframework.core.task.support.TaskExecutorAdapter;
1011
import org.springframework.scheduling.annotation.EnableAsync;
@@ -19,6 +20,7 @@
1920
)
2021
public class ThreadConfig {
2122
@Bean
23+
@Primary
2224
public AsyncTaskExecutor applicationTaskExecutor() {
2325
final var executor = new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
2426
executor.setTaskDecorator(new MdcTaskDecorator());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package net.furizon.backend.infrastructure.email;
2+
3+
import jakarta.mail.MessagingException;
4+
import net.furizon.backend.infrastructure.email.model.MailRequest;
5+
import org.jetbrains.annotations.Blocking;
6+
import org.jetbrains.annotations.NonBlocking;
7+
import org.springframework.mail.MailException;
8+
9+
public interface EmailSender {
10+
@Blocking
11+
void send(MailRequest request) throws MessagingException, MailException;
12+
13+
@NonBlocking
14+
void fireAndForget(MailRequest request);
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package net.furizon.backend.infrastructure.email;
2+
3+
import gg.jte.TemplateEngine;
4+
import gg.jte.TemplateOutput;
5+
import gg.jte.output.StringOutput;
6+
import jakarta.mail.MessagingException;
7+
import jakarta.mail.internet.MimeMessage;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import net.furizon.backend.infrastructure.email.model.MailRequest;
11+
import org.springframework.beans.factory.annotation.Value;
12+
import org.springframework.core.task.AsyncTaskExecutor;
13+
import org.springframework.mail.MailException;
14+
import org.springframework.mail.javamail.JavaMailSender;
15+
import org.springframework.mail.javamail.MimeMessageHelper;
16+
import org.springframework.stereotype.Service;
17+
18+
@Service
19+
@RequiredArgsConstructor
20+
@Slf4j
21+
public class EmailSenderService implements EmailSender {
22+
private final JavaMailSender mailSender;
23+
24+
private final TemplateEngine templateEngine;
25+
26+
private final AsyncTaskExecutor asyncTaskExecutor;
27+
28+
@Value("${spring.mail.username}")
29+
private String from;
30+
31+
@Override
32+
public void send(MailRequest request) throws MessagingException, MailException {
33+
MimeMessage mimeMessage = mailSender.createMimeMessage();
34+
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
35+
36+
mimeMessageHelper.setFrom(from);
37+
mimeMessageHelper.setTo(request.getTo());
38+
mimeMessageHelper.setSubject(request.getSubject());
39+
40+
if (request.getTemplateMessage() != null) {
41+
final var message = request.getTemplateMessage();
42+
TemplateOutput output = new StringOutput();
43+
templateEngine.render(message.getTemplate(), message.getParams(), output);
44+
mimeMessageHelper.setText(output.toString(), true);
45+
} else if (request.getMessage() != null) {
46+
mimeMessageHelper.setText(request.getMessage(), false);
47+
} else {
48+
throw new RuntimeException("message or template is required");
49+
}
50+
51+
mailSender.send(mimeMessage);
52+
}
53+
54+
@Override
55+
public void fireAndForget(MailRequest request) {
56+
asyncTaskExecutor.execute(() -> {
57+
try {
58+
log.debug("Sending email in async mode");
59+
send(request);
60+
} catch (MessagingException ex) {
61+
log.error("Couldn't send message", ex);
62+
}
63+
});
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.furizon.backend.infrastructure.email.model;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
import lombok.RequiredArgsConstructor;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
9+
@Data
10+
@Builder
11+
@RequiredArgsConstructor
12+
public class MailRequest {
13+
@NotNull
14+
private final String to;
15+
16+
@NotNull
17+
private final String subject;
18+
19+
@Nullable
20+
private final String message;
21+
22+
@Nullable
23+
private final TemplateMessage templateMessage;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package net.furizon.backend.infrastructure.email.model;
2+
3+
import lombok.Data;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
10+
@Data
11+
public class TemplateMessage {
12+
@NotNull
13+
private final String template;
14+
15+
@NotNull
16+
private final Map<String, Object> params;
17+
18+
public TemplateMessage addParam(@NotNull String key, @NotNull Object value) {
19+
params.put(key, value.toString());
20+
return this;
21+
}
22+
23+
public static TemplateMessage of(@NotNull String templatePath, @NotNull Map<String, Object> params) {
24+
return new TemplateMessage(templatePath, params);
25+
}
26+
27+
public static TemplateMessage of(@NotNull String templatePath) {
28+
return of(templatePath, new HashMap<>());
29+
}
30+
}

‎application/src/main/resources/application.yml

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ spring:
1717
driver-class-name: org.postgresql.Driver
1818
jooq:
1919
sql-dialect: postgres
20+
mail:
21+
host: ${MAIL_SENDER_PROVIDER_HOST:stmp.example.com}
22+
port: ${MAIL_SENDER_PROVIDER_PORT:465}
23+
username: ${MAIL_SENDER_PROVIDER_USERNAME:test@example.com}
24+
password: ${MAIL_SENDER_PROVIDER_PASSWORD:changeme}
25+
protocol: smtps
26+
properties:
27+
"mail.smtp.auth": true
28+
"mail.smtp.starttls.enable": true
29+
"mail.smtp.starttls.required": true
30+
"mail.debug": ${MAIL_SENDER_PROVIDER_USE_DEBUG_PROP:true}
31+
32+
gg:
33+
jte:
34+
development-mode: ${JTE_DEV_MODE:true}
35+
templateLocation: templates/jte
36+
templateSuffix: .jte
37+
usePrecompiledTemplates: ${JTE_PROD_MODE:false}
2038

2139
logbook:
2240
format:

‎templates/jte/old_template.jte

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@param String body
2+
@param String title
3+
4+
<!DOCTYPE html>
5+
<html lang="en">
6+
<head>
7+
<meta charset="UTF-8">
8+
<title>${title}</title>
9+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
10+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
11+
<meta name="supported-color-schemes" content="light dark">
12+
<style media="all" type="text/css">
13+
* { color: #bbc6ce; }
14+
.body { width: 100%; margin: 0; background-color: #11191f; }
15+
.container { max-width: 40em; padding: 1em; margin: 0 auto; }
16+
.title { font-size: 1.75em; margin-bottom: 1.2em; color: #e1e6eb; margin-top: 0; font-family: sans-serif; }
17+
.main-content { margin-top: 0; font-style: normal; font-weight: 400; font-family: sans-serif;}
18+
.con-logo { height:3em;}
19+
.link { text-decoration: none; background-color: #1095c1; color: #fff; padding: 1em; border-radius: 5px; font-weight: 600; }
20+
</style>
21+
</head>
22+
<div class="body">
23+
<div class="container">
24+
<img src="https://reg.furizon.net/res/furizon.png" alt="con_logo" title="con_logo" class="con-logo">
25+
<div>
26+
<h2 class="title">${title}</h2>
27+
<p class="main-content">${body}</p>
28+
</div>
29+
</div>
30+
</div>
31+
</html>

0 commit comments

Comments
 (0)
Please sign in to comment.