Skip to content

Commit ac6a678

Browse files
codebase/sending-emails-in-spring-boot-using-sendgrid [BAEL-8333] (eugenp#17190)
* adding module skeleton * adding sendgrid configurations * adding service layer communicating with sendgrid * adding integration tests * adding template id in configuration properties * adding method to send HTML emails * adding custom personalization implementation * adding integration test for templated email * adding HTML template * refactoring service layer * minifying CSS * updating validation annotation * adding test scope to test dependencies * updating variable name * fix: code smell and indentation * specifying UTF-8 charset for attachments
1 parent 6532cf9 commit ac6a678

File tree

12 files changed

+598
-0
lines changed

12 files changed

+598
-0
lines changed

saas-modules/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<modules>
1919
<module>discord4j</module>
2020
<module>jira-rest-integration</module>
21+
<module>sendgrid</module>
2122
<module>sentry-servlet</module>
2223
<module>slack</module>
2324
<module>stripe</module>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<html>
2+
<head>
3+
<style>
4+
body { font-family: Arial; line-height: 2; text-align: Center; }
5+
h2 { color: DeepSkyBlue; }
6+
.alert { background: Red; color: White; padding: 1rem; font-size: 1.5rem; font-weight: bold; }
7+
.message { border: .3rem solid DeepSkyBlue; padding: 1rem; margin-top: 1rem; }
8+
.status { background: LightCyan; padding: 1rem; margin-top: 1rem; }
9+
</style>
10+
</head>
11+
<body>
12+
<div class="alert">⚠️ URGENT HYDRATION ALERT ⚠️</div>
13+
<div class="message">
14+
<h2>It's time to drink water!</h2>
15+
<p>Hey {{name}}, this is your friendly reminder to stay hydrated. Your body will thank you!</p>
16+
<div class="status">
17+
<p><strong>Last drink:</strong> {{lastDrinkTime}}</p>
18+
<p><strong>Hydration status:</strong> {{hydrationStatus}}</p>
19+
</div>
20+
</div>
21+
</body>
22+
</html>

saas-modules/sendgrid/pom.xml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<artifactId>sendgrid</artifactId>
7+
<version>0.0.1</version>
8+
<packaging>jar</packaging>
9+
<name>sendgrid</name>
10+
<description>codebase to demonstrate sending emails using SendGrid</description>
11+
12+
<parent>
13+
<groupId>com.baeldung</groupId>
14+
<artifactId>saas-modules</artifactId>
15+
<version>1.0.0-SNAPSHOT</version>
16+
</parent>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>org.springframework.boot</groupId>
21+
<artifactId>spring-boot-starter-web</artifactId>
22+
</dependency>
23+
<dependency>
24+
<groupId>org.springframework.boot</groupId>
25+
<artifactId>spring-boot-starter-validation</artifactId>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.springframework.boot</groupId>
29+
<artifactId>spring-boot-configuration-processor</artifactId>
30+
<optional>true</optional>
31+
</dependency>
32+
<dependency>
33+
<groupId>com.sendgrid</groupId>
34+
<artifactId>sendgrid-java</artifactId>
35+
<version>${sendgrid.version}</version>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.mock-server</groupId>
39+
<artifactId>mockserver-netty</artifactId>
40+
<version>${mockserver.version}</version>
41+
<scope>test</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.mock-server</groupId>
45+
<artifactId>mockserver-spring-test-listener</artifactId>
46+
<version>${mockserver.version}</version>
47+
<scope>test</scope>
48+
</dependency>
49+
</dependencies>
50+
51+
<properties>
52+
<java.version>17</java.version>
53+
<sendgrid.version>4.10.2</sendgrid.version>
54+
<mockserver.version>5.15.0</mockserver.version>
55+
</properties>
56+
57+
</project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.baeldung.sendgrid;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class Application {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(Application.class, args);
11+
}
12+
13+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.baeldung.sendgrid;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import com.sendgrid.helpers.mail.objects.Personalization;
7+
8+
public class DynamicTemplatePersonalization extends Personalization {
9+
10+
private final Map<String, Object> dynamicTemplateData = new HashMap<>();
11+
12+
public void add(String key, String value) {
13+
dynamicTemplateData.put(key, value);
14+
}
15+
16+
@Override
17+
public Map<String, Object> getDynamicTemplateData() {
18+
return dynamicTemplateData;
19+
}
20+
21+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.baeldung.sendgrid;
2+
3+
import java.io.IOException;
4+
import java.nio.charset.StandardCharsets;
5+
import java.util.Base64;
6+
import java.util.List;
7+
8+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.web.multipart.MultipartFile;
11+
12+
import com.sendgrid.Method;
13+
import com.sendgrid.Request;
14+
import com.sendgrid.SendGrid;
15+
import com.sendgrid.helpers.mail.Mail;
16+
import com.sendgrid.helpers.mail.objects.Attachments;
17+
import com.sendgrid.helpers.mail.objects.Content;
18+
import com.sendgrid.helpers.mail.objects.Email;
19+
20+
@Service
21+
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
22+
public class EmailDispatcher {
23+
24+
private static final String EMAIL_ENDPOINT = "mail/send";
25+
26+
private final SendGrid sendGrid;
27+
private final Email fromEmail;
28+
private final SendGridConfigurationProperties sendGridConfigurationProperties;
29+
30+
public EmailDispatcher(SendGrid sendGrid, Email fromEmail,
31+
SendGridConfigurationProperties sendGridConfigurationProperties) {
32+
this.sendGrid = sendGrid;
33+
this.fromEmail = fromEmail;
34+
this.sendGridConfigurationProperties = sendGridConfigurationProperties;
35+
}
36+
37+
public void dispatchEmail(String emailId, String subject, String body) throws IOException {
38+
Email toEmail = new Email(emailId);
39+
Content content = new Content("text/plain", body);
40+
Mail mail = new Mail(fromEmail, subject, toEmail, content);
41+
42+
sendRequest(mail);
43+
}
44+
45+
public void dispatchEmail(String emailId, String subject, String body, List<MultipartFile> files)
46+
throws IOException {
47+
Email toEmail = new Email(emailId);
48+
Content content = new Content("text/plain", body);
49+
Mail mail = new Mail(fromEmail, subject, toEmail, content);
50+
51+
if (files != null && !files.isEmpty()) {
52+
for (MultipartFile file : files) {
53+
Attachments attachment = createAttachment(file);
54+
mail.addAttachments(attachment);
55+
}
56+
}
57+
58+
sendRequest(mail);
59+
}
60+
61+
public void dispatchHydrationAlert(String emailId, String username) throws IOException {
62+
Email toEmail = new Email(emailId);
63+
String templateId = sendGridConfigurationProperties.getHydrationAlertNotification().getTemplateId();
64+
65+
DynamicTemplatePersonalization personalization = new DynamicTemplatePersonalization();
66+
personalization.add("name", username);
67+
personalization.add("lastDrinkTime", "Way too long ago");
68+
personalization.add("hydrationStatus", "Thirsty as a camel");
69+
personalization.addTo(toEmail);
70+
71+
Mail mail = new Mail();
72+
mail.setFrom(fromEmail);
73+
mail.setTemplateId(templateId);
74+
mail.addPersonalization(personalization);
75+
76+
sendRequest(mail);
77+
}
78+
79+
private void sendRequest(Mail mail) throws IOException {
80+
Request request = new Request();
81+
request.setMethod(Method.POST);
82+
request.setEndpoint(EMAIL_ENDPOINT);
83+
request.setBody(mail.build());
84+
85+
sendGrid.api(request);
86+
}
87+
88+
private Attachments createAttachment(MultipartFile file) throws IOException {
89+
byte[] encodedFileContent = Base64.getEncoder().encode(file.getBytes());
90+
Attachments attachment = new Attachments();
91+
attachment.setDisposition("attachment");
92+
attachment.setType(file.getContentType());
93+
attachment.setFilename(file.getOriginalFilename());
94+
attachment.setContent(new String(encodedFileContent, StandardCharsets.UTF_8));
95+
return attachment;
96+
}
97+
98+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.baeldung.sendgrid;
2+
3+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
8+
import com.sendgrid.SendGrid;
9+
import com.sendgrid.helpers.mail.objects.Email;
10+
11+
@Configuration
12+
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
13+
public class SendGridConfiguration {
14+
15+
private final SendGridConfigurationProperties sendGridConfigurationProperties;
16+
17+
public SendGridConfiguration(SendGridConfigurationProperties sendGridConfigurationProperties) {
18+
this.sendGridConfigurationProperties = sendGridConfigurationProperties;
19+
}
20+
21+
@Bean
22+
@ConditionalOnMissingBean
23+
public SendGrid sendGrid() {
24+
String apiKey = sendGridConfigurationProperties.getApiKey();
25+
return new SendGrid(apiKey);
26+
}
27+
28+
@Bean
29+
public Email fromEmail() {
30+
String fromEmail = sendGridConfigurationProperties.getFromEmail();
31+
String fromName = sendGridConfigurationProperties.getFromName();
32+
return new Email(fromEmail, fromName);
33+
}
34+
35+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.baeldung.sendgrid;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
import org.springframework.validation.annotation.Validated;
5+
6+
import jakarta.validation.Valid;
7+
import jakarta.validation.constraints.Email;
8+
import jakarta.validation.constraints.NotBlank;
9+
import jakarta.validation.constraints.Pattern;
10+
11+
@Validated
12+
@ConfigurationProperties(prefix = "com.baeldung.sendgrid")
13+
public class SendGridConfigurationProperties {
14+
15+
@NotBlank(message = "SendGrid API key must be configured")
16+
@Pattern(regexp = "^SG[0-9a-zA-Z._]{67}$", message = "Invalid SendGrid API key format")
17+
private String apiKey;
18+
19+
@NotBlank(message = "SendGrid from email must be configured")
20+
@Email(message = "Invalid email format")
21+
private String fromEmail;
22+
23+
@NotBlank
24+
private String fromName;
25+
26+
@Valid
27+
private HydrationAlertNotification hydrationAlertNotification = new HydrationAlertNotification();
28+
29+
public String getApiKey() {
30+
return apiKey;
31+
}
32+
33+
public void setApiKey(String apiKey) {
34+
this.apiKey = apiKey;
35+
}
36+
37+
public String getFromEmail() {
38+
return fromEmail;
39+
}
40+
41+
public void setFromEmail(String fromEmail) {
42+
this.fromEmail = fromEmail;
43+
}
44+
45+
public String getFromName() {
46+
return fromName;
47+
}
48+
49+
public void setFromName(String fromName) {
50+
this.fromName = fromName;
51+
}
52+
53+
public HydrationAlertNotification getHydrationAlertNotification() {
54+
return hydrationAlertNotification;
55+
}
56+
57+
public void setHydrationAlertNotification(HydrationAlertNotification hydrationAlertNotification) {
58+
this.hydrationAlertNotification = hydrationAlertNotification;
59+
}
60+
61+
class HydrationAlertNotification {
62+
63+
@NotBlank(message = "Template-id must be configured")
64+
@Pattern(regexp = "^d-[a-f0-9]{32}$", message = "Invalid template ID format")
65+
private String templateId;
66+
67+
public String getTemplateId() {
68+
return templateId;
69+
}
70+
71+
public void setTemplateId(String templateId) {
72+
this.templateId = templateId;
73+
}
74+
75+
}
76+
77+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
com:
2+
baeldung:
3+
sendgrid:
4+
api-key: ${SENDGRID_API_KEY}
5+
from-email: ${SENDGRID_FROM_EMAIL}
6+
from-name: ${SENDGRID_FROM_NAME}
7+
hydration-alert-notification:
8+
template-id: ${HYDRATION_ALERT_TEMPLATE_ID}

0 commit comments

Comments
 (0)