Merge branch 'chore/sending-email-should-be-nonblocking' into 'release'
Sending emails is now done in a non-blocking way See merge request theappsmith/internal-tools-server!396
This commit is contained in:
commit
5ce19962a3
|
|
@ -5,11 +5,12 @@ import com.github.mustachejava.DefaultMustacheFactory;
|
|||
import com.github.mustachejava.Mustache;
|
||||
import com.github.mustachejava.MustacheFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
|
@ -25,22 +26,41 @@ import java.util.regex.Pattern;
|
|||
@Slf4j
|
||||
public class EmailSender {
|
||||
|
||||
@Autowired
|
||||
JavaMailSender emailSender;
|
||||
final JavaMailSender javaMailSender;
|
||||
|
||||
@Autowired
|
||||
EmailConfig emailConfig;
|
||||
final EmailConfig emailConfig;
|
||||
|
||||
private static final InternetAddress MAIL_FROM = makeFromAddress();
|
||||
|
||||
public static final Pattern VALID_EMAIL_ADDRESS_REGEX =
|
||||
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public EmailSender(JavaMailSender javaMailSender, EmailConfig emailConfig) {
|
||||
this.javaMailSender = javaMailSender;
|
||||
this.emailConfig = emailConfig;
|
||||
}
|
||||
|
||||
private static boolean validateEmail(String emailStr) {
|
||||
Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(emailStr);
|
||||
return matcher.find();
|
||||
}
|
||||
|
||||
public Mono<Void> sendMail(String to, String subject, String text, Map<String, String> params) {
|
||||
return Mono
|
||||
.fromSupplier(() -> {
|
||||
try {
|
||||
return replaceEmailTemplate(text, params);
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.propagate(e);
|
||||
}
|
||||
})
|
||||
.flatMap(emailBody -> sendMail(to, subject, emailBody));
|
||||
}
|
||||
|
||||
public Mono<Void> sendMail(String to, String subject, String text) {
|
||||
return Mono.fromRunnable(() -> sendMailSync(to, subject, text));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function sends an HTML email to the user from the default email address
|
||||
*
|
||||
|
|
@ -48,7 +68,7 @@ public class EmailSender {
|
|||
* @param subject Subject string.
|
||||
* @param text HTML Body of the message. This method assumes UTF-8.
|
||||
*/
|
||||
public void sendMail(String to, String subject, String text) {
|
||||
private void sendMailSync(String to, String subject, String text) {
|
||||
log.debug("Got request to send email to: {} with subject: {}", to, subject);
|
||||
// Don't send an email for local, dev or test environments
|
||||
if (!emailConfig.isEmailEnabled()) {
|
||||
|
|
@ -67,7 +87,7 @@ public class EmailSender {
|
|||
}
|
||||
|
||||
log.debug("Going to send email to {} with subject {}", to, subject);
|
||||
MimeMessage mimeMessage = emailSender.createMimeMessage();
|
||||
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, "utf-8");
|
||||
|
||||
try {
|
||||
|
|
@ -75,7 +95,7 @@ public class EmailSender {
|
|||
helper.setFrom(MAIL_FROM);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(text, true);
|
||||
emailSender.send(mimeMessage);
|
||||
javaMailSender.send(mimeMessage);
|
||||
} catch (MessagingException e) {
|
||||
log.error("Unable to create the mime message while sending an email to {} with subject: {}. Cause: ", to, subject, e);
|
||||
} catch (MailException e) {
|
||||
|
|
@ -89,16 +109,15 @@ public class EmailSender {
|
|||
* @param template The name of the template where the HTML text can be found
|
||||
* @param params A Map of key-value pairs with the key being the variable in the template & value being the actual
|
||||
* value with which it must be replaced.
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @return Template string with Mustache replacements applied.
|
||||
* @throws IOException bubbled from Mustache renderer.
|
||||
*/
|
||||
public String replaceEmailTemplate(String template, Map<String, String> params) throws IOException {
|
||||
private String replaceEmailTemplate(String template, Map<String, String> params) throws IOException {
|
||||
MustacheFactory mf = new DefaultMustacheFactory();
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
Mustache mustache = mf.compile(template);
|
||||
mustache.execute(stringWriter, params).flush();
|
||||
String emailTemplate = stringWriter.toString();
|
||||
return emailTemplate;
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
private static InternetAddress makeFromAddress() {
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -194,22 +194,28 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
|
||||
// Save the password reset link and send an email to the user
|
||||
Mono<Boolean> resetFlowMono = passwordResetTokenMono
|
||||
.flatMap(resetToken -> passwordResetTokenRepository.save(resetToken))
|
||||
.map(obj -> {
|
||||
String resetUrl = String.format(FORGOT_PASSWORD_CLIENT_URL_FORMAT,
|
||||
.flatMap(passwordResetTokenRepository::save)
|
||||
.flatMap(obj -> {
|
||||
String resetUrl = String.format(
|
||||
FORGOT_PASSWORD_CLIENT_URL_FORMAT,
|
||||
resetUserPasswordDTO.getBaseUrl(),
|
||||
URLEncoder.encode(token, StandardCharsets.UTF_8),
|
||||
URLEncoder.encode(email, StandardCharsets.UTF_8));
|
||||
URLEncoder.encode(email, StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
Map<String, String> params = Map.of("resetUrl", resetUrl);
|
||||
try {
|
||||
String emailTemplate = emailSender.replaceEmailTemplate(FORGOT_PASSWORD_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(email, "Appsmith Password Reset", emailTemplate);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to send email because the template replacement failed. Cause: ", e);
|
||||
}
|
||||
return Mono.empty();
|
||||
return emailSender.sendMail(
|
||||
email,
|
||||
"Appsmith Password Reset",
|
||||
FORGOT_PASSWORD_EMAIL_TEMPLATE,
|
||||
params
|
||||
);
|
||||
})
|
||||
.thenReturn(true);
|
||||
.thenReturn(true)
|
||||
.onErrorResume(error -> {
|
||||
log.error("Unable to send email because the template replacement failed. Cause: ", error);
|
||||
return Mono.just(true);
|
||||
});
|
||||
|
||||
// Connect the components to first find a valid user and then initiate the password reset flow
|
||||
return userMono.then(resetFlowMono);
|
||||
|
|
@ -474,21 +480,25 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
final String finalOriginHeader = originHeader;
|
||||
|
||||
return userCreate(user)
|
||||
.map(savedUser -> sendWelcomeEmail(savedUser, finalOriginHeader));
|
||||
.flatMap(savedUser -> sendWelcomeEmail(savedUser, finalOriginHeader));
|
||||
}
|
||||
|
||||
public User sendWelcomeEmail(User user, String originHeader) {
|
||||
try {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("firstName", user.getName());
|
||||
params.put("appsmithLink", originHeader);
|
||||
String emailBody = emailSender.replaceEmailTemplate(WELCOME_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(user.getEmail(), "Welcome to Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
// Catching and swallowing this exception because we don't want this to affect the rest of the flow
|
||||
log.error("Unable to send welcome email to the user {}. Cause: ", user.getEmail(), e);
|
||||
}
|
||||
return user;
|
||||
public Mono<User> sendWelcomeEmail(User user, String originHeader) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("firstName", user.getName());
|
||||
params.put("appsmithLink", originHeader);
|
||||
return emailSender
|
||||
.sendMail(user.getEmail(), "Welcome to Appsmith", WELCOME_USER_EMAIL_TEMPLATE, params)
|
||||
.thenReturn(user)
|
||||
.onErrorResume(error -> {
|
||||
// Swallowing this exception because we don't want this to affect the rest of the flow.
|
||||
log.error(
|
||||
"Ignoring error: Unable to send welcome email to the user {}. Cause: ",
|
||||
user.getEmail(),
|
||||
Exceptions.unwrap(error)
|
||||
);
|
||||
return Mono.just(user);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -612,7 +622,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
|
||||
|
||||
return Mono.zip(organizationWithUserAddedMono, userUpdatedWithOrgMono, currentUserMono)
|
||||
.map(tuple -> {
|
||||
.flatMap(tuple -> {
|
||||
// We reached here. This implies that both user and org got updated without any errors. Proceed forward
|
||||
// with communication (email) here.
|
||||
Organization updatedOrg = tuple.getT1();
|
||||
|
|
@ -628,39 +638,39 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
}
|
||||
params.put("inviter_org_name", updatedOrg.getName());
|
||||
|
||||
Mono<Void> emailMono;
|
||||
if (userExisted.get()) {
|
||||
|
||||
// If the user already existed, just send an email informing that the user has been added
|
||||
// to a new organization
|
||||
log.debug("Going to send email to user {} informing that the user has been added to new organization {}",
|
||||
updatedUser.getEmail(), updatedOrg.getName());
|
||||
try {
|
||||
String inviteUrl = originHeader;
|
||||
params.put("inviteUrl", inviteUrl);
|
||||
String emailBody = emailSender.replaceEmailTemplate(USER_ADDED_TO_ORGANIZATION_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(updatedUser.getEmail(), "Appsmith: You have been added to a new organization", emailBody);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to send invite user email to {}. Cause: ", updatedUser.getEmail(), e);
|
||||
}
|
||||
params.put("inviteUrl", originHeader);
|
||||
emailMono = emailSender.sendMail(updatedUser.getEmail(), "Appsmith: You have been added to a new organization", USER_ADDED_TO_ORGANIZATION_EMAIL_TEMPLATE, params);
|
||||
|
||||
} else {
|
||||
// The user was created and then added to the organization. Send an email to the user to sign
|
||||
// up on Appsmith platform with the token generated during create user.
|
||||
log.debug("Going to send email for invite user to {} with token {}", updatedUser.getEmail(), updatedUser.getInviteToken());
|
||||
try {
|
||||
String inviteUrl = String.format(INVITE_USER_CLIENT_URL_FORMAT, originHeader,
|
||||
URLEncoder.encode(updatedUser.getInviteToken(), StandardCharsets.UTF_8),
|
||||
URLEncoder.encode(updatedUser.getEmail(), StandardCharsets.UTF_8));
|
||||
params.put("token", updatedUser.getInviteToken());
|
||||
params.put("inviteUrl", inviteUrl);
|
||||
String emailBody = emailSender.replaceEmailTemplate(INVITE_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(updatedUser.getEmail(), "Invite for Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to send invite user email to {}. Cause: ", updatedUser.getEmail(), e);
|
||||
}
|
||||
String inviteUrl = String.format(
|
||||
INVITE_USER_CLIENT_URL_FORMAT,
|
||||
originHeader,
|
||||
URLEncoder.encode(updatedUser.getInviteToken(), StandardCharsets.UTF_8),
|
||||
URLEncoder.encode(updatedUser.getEmail(), StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
params.put("token", updatedUser.getInviteToken());
|
||||
params.put("inviteUrl", inviteUrl);
|
||||
emailMono = emailSender.sendMail(updatedUser.getEmail(), "Invite for Appsmith", INVITE_USER_EMAIL_TEMPLATE, params);
|
||||
|
||||
}
|
||||
|
||||
// We have sent out the emails. Just send back the saved user.
|
||||
return updatedUser;
|
||||
return emailMono
|
||||
.thenReturn(updatedUser)
|
||||
.onErrorResume(error -> {
|
||||
log.error("Unable to send invite user email to {}. Cause: ", updatedUser.getEmail(), error);
|
||||
return Mono.just(updatedUser);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user