MailBox.java
package access.mail;
import access.model.*;
import access.manage.Contact;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.MustacheFactory;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.SneakyThrows;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.StringWriter;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unchecked")
public class MailBox {
private final JavaMailSender mailSender;
private final String clientUrl;
private final String emailFrom;
private final String serviceDeskEmail;
private final String supportEmail;
private final String jiraErrorEmail;
private final String environment;
private final Map<String, Map<String, String>> subjects;
private final MustacheFactory mustacheFactory = new DefaultMustacheFactory("templates");
public MailBox(
JavaMailSender mailSender,
String emailFrom,
String serviceDeskEmail,
String supportEmail,
String jiraErrorEmail,
String clientUrl,
String environment,
ObjectMapper objectMapper) throws IOException {
this.mailSender = mailSender;
this.emailFrom = emailFrom;
this.serviceDeskEmail = serviceDeskEmail;
this.supportEmail = supportEmail;
this.jiraErrorEmail = jiraErrorEmail;
this.clientUrl = clientUrl;
this.environment = environment;
this.subjects = objectMapper.readValue(new ClassPathResource("/templates/subjects.json").getInputStream(), new TypeReference<>() {
});
}
@SneakyThrows
public void sendInviteMail(Invitation invitation) {
Language language = invitation.getLanguage();
String title = String.format(subjects.get(language.name()).get("newInvitation"),
invitation.getOrganization().getName());
Map<String, Object> variables = new HashMap<>();
variables.put("invitation", invitation);
variables.put("title", title);
if (StringUtils.hasText(invitation.getMessage())) {
variables.put("message", invitation.getMessage().replaceAll("\n", "<br/>"));
}
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
variables.put("url", String.format("%s/accept?hash=%s", clientUrl, invitation.getHash()));
sendMail(String.format("invitation_%s", language.name()),
title,
variables,
invitation.getEmail());
}
@SneakyThrows
public void sendConnectionRequest(User requester,
List<User> recipients,
Organization organization,
String serviceProviderName,
String message,
String deepLink) {
Language language = Language.valueOf(preferredLanguage());
String title = String.format(subjects.get(language.name()).get("connectionRequest"),
serviceProviderName);
Map<String, Object> variables = new HashMap<>();
if (StringUtils.hasText(message)) {
variables.put("message", message.replaceAll("\n", "<br/>"));
}
variables.put("title", title);
variables.put("serviceProviderName", serviceProviderName);
variables.put("organization", organization);
variables.put("requester", requester);
variables.put("deepLink", String.format("%s%s", clientUrl, deepLink));
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
sendMail(String.format("connection_request_%s", language.name()),
title,
variables,
recipients.stream().map(user -> user.getEmail()).toList().toArray(new String[]{}));
}
@SneakyThrows
public void sendNewConnectionCreated(User institutionAdmin,
List<String> recipients,
String identityProviderName,
String serviceProviderName,
String serviceProviderEntityId) {
Language language = Language.valueOf(preferredLanguage());
String title = String.format(subjects.get(language.name()).get("newConnectionMade"),
serviceProviderName,
identityProviderName);
Map<String, Object> variables = new HashMap<>();
variables.put("title", title);
variables.put("identityProviderName", identityProviderName);
variables.put("serviceProviderName", serviceProviderName);
variables.put("serviceProviderEntityId", serviceProviderEntityId);
variables.put("institutionAdmin", institutionAdmin);
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
sendMail(String.format("new_connection_made_%s", language.name()),
title,
variables,
recipients.toArray(new String[]{}));
}
@SneakyThrows
public void sendJoinRequestMail(JoinRequest joinRequest) {
Language language = joinRequest.getLanguage();
Organization organization = joinRequest.getOrganization();
String title = String.format(subjects.get(language.name()).get("newJoinRequest"),
joinRequest.getUser().getName(), organization.getName());
Map<String, Object> variables = new HashMap<>();
variables.put("joinRequest", joinRequest);
variables.put("title", title);
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
variables.put("url", String.format("%s/users/%s/joins", clientUrl, organization.getId()));
List<String> emails = organization.getOrganizationMemberships().stream()
.filter(organizationMembership -> organizationMembership.getAuthority().equals(Authority.ADMIN))
.map(organizationMembership -> organizationMembership.getUser().getEmail())
.toList();
sendMail(String.format("join_request_%s", language.name()),
title,
variables,
emails.toArray(new String[0]));
}
@SneakyThrows
public void sendJoinRequestAcceptedMail(JoinRequest joinRequest) {
Language language = joinRequest.getLanguage();
Organization organization = joinRequest.getOrganization();
String title = String.format(subjects.get(language.name()).get("acceptJoinRequest"),
organization.getName());
Map<String, Object> variables = new HashMap<>();
variables.put("joinRequest", joinRequest);
variables.put("title", title);
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
variables.put("url", String.format("%s/organization/%s/applications", clientUrl, organization.getId()));
sendMail(String.format("join_request_accepted_%s", language.name()),
title,
variables,
joinRequest.getUser().getEmail());
}
@SneakyThrows
public void sendJoinRequestDeniedMail(JoinRequest joinRequest) {
Language language = joinRequest.getLanguage();
Organization organization = joinRequest.getOrganization();
String title = String.format(subjects.get(language.name()).get("deniedJoinRequest"),
organization.getName());
Map<String, Object> variables = new HashMap<>();
variables.put("joinRequest", joinRequest);
variables.put("title", title);
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
sendMail(String.format("join_request_denied_%s", language.name()),
title,
variables,
joinRequest.getUser().getEmail());
}
@SneakyThrows
public void sendFeedbackMail(User user, String message) {
sendFeedbackMail(user, message, null, null, null, null);
}
@SneakyThrows
public void sendFeedbackMail(User user, String message, String url, byte[] screenshotBytes,
String screenshotName, String screenshotContentType) {
Map<String, Object> variables = new HashMap<>();
variables.put("user", user);
variables.put("title", "SURF Access feedback form");
String now = LocalDate.now().format(DateTimeFormatter.ofPattern("dd-MM-yyyy"));
variables.put("date", now);
variables.put("message", message.replaceAll("\n", "<br/>"));
variables.put("url", url);
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
variables.put("env", environment);
MailAttachment attachment = null;
if (screenshotBytes != null && screenshotBytes.length > 0) {
String filename = screenshotName == null ? "feedback.png" : screenshotName;
String contentType = screenshotContentType == null ? "image/png" : screenshotContentType;
attachment = new MailAttachment(filename, screenshotBytes, contentType);
}
sendMail("feedback_en",
"Feedback",
variables,
attachment,
supportEmail);
}
@SneakyThrows
public void sendOrgContactReminder(Organization organization, Contact contact, String language) {
String lang = language != null ? language : preferredLanguage();
String title = String.format(subjects.get(lang).get("orgContactReminder"), organization.getName());
Map<String, Object> variables = new HashMap<>();
variables.put("organization", organization);
variables.put("contact", contact);
variables.put("title", title);
variables.put("url", String.format("%s/organization/%s", clientUrl, organization.getId()));
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
sendMail(String.format("org_contact_reminder_%s", lang), title, variables, contact.getEmail());
}
@SneakyThrows
public void sendUserInactivityWarning(User user, Instant deletionDate) {
String lang = preferredLanguage();
String title = String.format(subjects.get(lang).get("userInactivityWarning"), environment);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy").withZone(ZoneId.systemDefault());
Map<String, Object> variables = new HashMap<>();
variables.put("user", user);
variables.put("title", title);
variables.put("lastActivity", user.getLastActivity() != null ? formatter.format(user.getLastActivity()) : "unknown");
variables.put("deletionDate", formatter.format(deletionDate));
variables.put("clientUrl", clientUrl);
if (!environment.equalsIgnoreCase("prod")) {
variables.put("environment", environment);
}
sendMail(String.format("user_inactivity_warning_%s", lang), title, variables, user.getEmail());
}
@SneakyThrows
public void sendJiraError(String operation, String requestPayload, String errorMessage, String responseBody) {
String title = String.format(subjects.get("en").get("jiraError"), environment, operation);
Map<String, Object> variables = new HashMap<>();
variables.put("title", title);
variables.put("operation", operation);
variables.put("requestPayload", requestPayload);
variables.put("errorMessage", errorMessage);
variables.put("responseBody", responseBody);
variables.put("environment", environment);
sendMail("jira_error_en", title, variables, jiraErrorEmail);
}
private String preferredLanguage() {
return LocaleContextHolder.getLocale().getLanguage();
}
private String sendMail(String templateName, String subject, Map<String, Object> variables, String... to) throws MessagingException, IOException {
return sendMail(templateName, subject, variables, null, to);
}
private String sendMail(String templateName, String subject, Map<String, Object> variables,
MailAttachment attachment, String... to) throws MessagingException, IOException {
String htmlText = this.mailTemplate(templateName + ".html", variables);
String plainText = this.mailTemplate(templateName + ".txt", variables);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setSubject(subject);
helper.setText(plainText, htmlText);
helper.setTo(to);
helper.setFrom(emailFrom);
if (attachment != null && attachment.bytes != null) {
helper.addAttachment(attachment.filename, new ByteArrayResource(attachment.bytes), attachment.contentType);
}
new Thread(() -> mailSender.send(message)).start();
return htmlText;
}
private static class MailAttachment {
private final String filename;
private final byte[] bytes;
private final String contentType;
private MailAttachment(String filename, byte[] bytes, String contentType) {
this.filename = filename;
this.bytes = bytes;
this.contentType = contentType;
}
}
private String mailTemplate(String templateName, Map<String, Object> context) {
return mustacheFactory.compile(templateName).execute(new StringWriter(), context).toString();
}
}