Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package br.com.springnoobs.reminderapi.mail.engine;

import br.com.springnoobs.reminderapi.mail.exception.EmailSendException;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import java.io.IOException;
Expand All @@ -22,7 +23,7 @@ public MailEngine(JavaMailSender mailSender) {
this.mailSender = mailSender;
}

public void sendEmail(Map<String, String> variables) {
public MimeMessage createEmailMessage(Map<String, String> variables) {
try {
MimeMessage message = mailSender.createMimeMessage();

Expand All @@ -36,9 +37,19 @@ public void sendEmail(Map<String, String> variables) {

helper.setText(mailTemplate, true);

mailSender.send(message);
return message;
} catch (IOException | MessagingException e) {
logger.error("Error at send email: {}", e.getMessage());
logger.error("Error at create email: {}", e.getMessage());
}

return null;
}

public void sendEmail(MimeMessage message) throws EmailSendException {
try {
mailSender.send(message);
} catch (Exception e) {
throw new EmailSendException(e.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package br.com.springnoobs.reminderapi.mail.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.Instant;

@Entity
@Table(name = "email_send_failures")
public class EmailSendFailure {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String email;

private String subject;

private String name;

private String title;

private String remindAt;

private String dueDate;

private String disableNotificationUrl;

private Instant failedAt;

private int retryCount;

private String errorMessage;

public EmailSendFailure() {}

public EmailSendFailure(
Long id,
String email,
String subject,
String name,
String title,
String remindAt,
String dueDate,
String disableNotificationUrl,
Instant failedAt,
int retryCount,
String errorMessage) {
this.id = id;
this.email = email;
this.subject = subject;
this.name = name;
this.title = title;
this.remindAt = remindAt;
this.dueDate = dueDate;
this.disableNotificationUrl = disableNotificationUrl;
this.failedAt = failedAt;
this.retryCount = retryCount;
this.errorMessage = errorMessage;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getSubject() {
return subject;
}

public void setSubject(String subject) {
this.subject = subject;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getRemindAt() {
return remindAt;
}

public void setRemindAt(String remindAt) {
this.remindAt = remindAt;
}

public String getDueDate() {
return dueDate;
}

public void setDueDate(String dueDate) {
this.dueDate = dueDate;
}

public String getDisableNotificationUrl() {
return disableNotificationUrl;
}

public void setDisableNotificationUrl(String disableNotificationUrl) {
this.disableNotificationUrl = disableNotificationUrl;
}

public Instant getFailedAt() {
return failedAt;
}

public void setFailedAt(Instant failedAt) {
this.failedAt = failedAt;
}

public int getRetryCount() {
return retryCount;
}

public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}

public String getErrorMessage() {
return errorMessage;
}

public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package br.com.springnoobs.reminderapi.mail.exception;

public class EmailSendException extends RuntimeException {
public EmailSendException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package br.com.springnoobs.reminderapi.mail.repository;

import br.com.springnoobs.reminderapi.mail.entity.EmailSendFailure;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmailSendFailureRepository extends JpaRepository<EmailSendFailure, Long> {
List<EmailSendFailure> findTop20ByOrderByFailedAtAsc();
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
package br.com.springnoobs.reminderapi.mail.service;

import br.com.springnoobs.reminderapi.mail.engine.MailEngine;
import br.com.springnoobs.reminderapi.mail.entity.EmailSendFailure;
import br.com.springnoobs.reminderapi.mail.exception.EmailSendException;
import br.com.springnoobs.reminderapi.mail.repository.EmailSendFailureRepository;
import br.com.springnoobs.reminderapi.reminder.entity.Reminder;
import br.com.springnoobs.reminderapi.user.entity.Contact;
import jakarta.mail.internet.MimeMessage;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.springframework.stereotype.Service;

@Service
public class EmailService {

Logger logger = org.slf4j.LoggerFactory.getLogger(EmailService.class);

private final MailEngine mailEngine;

private final EmailSendFailureRepository emailSendFailureRepository;

private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("dd/MM/yyyy").withZone(ZoneId.of("America/Sao_Paulo"));

public EmailService(MailEngine mailEngine) {
public EmailService(MailEngine mailEngine, EmailSendFailureRepository emailSendFailureRepository) {
this.mailEngine = mailEngine;
this.emailSendFailureRepository = emailSendFailureRepository;
}

public void send(Reminder reminder) {
Expand All @@ -30,7 +41,44 @@ public void send(Reminder reminder) {

Map<String, String> variables = buildEmailVariables(contact, reminder, "#");

mailEngine.sendEmail(variables);
MimeMessage mimeMessage = mailEngine.createEmailMessage(variables);

if (mimeMessage == null) {
return;
}

sendEmailWithFailureHandling(reminder, mimeMessage, variables);
}

private void dispatchEmail(MimeMessage mimeMessage) throws EmailSendException {
mailEngine.sendEmail(mimeMessage);
}

private void sendEmailWithFailureHandling(
Reminder reminder, MimeMessage mimeMessage, Map<String, String> variables) {
try {
dispatchEmail(mimeMessage);
} catch (EmailSendException e) {
logger.error("Error at send email: {}, to reminder: {}", e.getMessage(), reminder.getId());

registerEmailFailure(variables, e.getMessage());
}
}

private void registerEmailFailure(Map<String, String> variables, String errorMessage) {
EmailSendFailure emailSendFailure = new EmailSendFailure();

emailSendFailure.setName(variables.get("name"));
emailSendFailure.setEmail(variables.get("email"));
emailSendFailure.setTitle(variables.get("title"));
emailSendFailure.setRemindAt(variables.get("remind_at"));
emailSendFailure.setDueDate(variables.get("due_date"));
emailSendFailure.setDisableNotificationUrl(variables.get("disable_notification_url"));
emailSendFailure.setSubject(variables.get("subject"));
emailSendFailure.setErrorMessage(errorMessage);
emailSendFailure.setFailedAt(Instant.now());

emailSendFailureRepository.save(emailSendFailure);
}

private Map<String, String> buildEmailVariables(Contact contact, Reminder reminder, String disableUrl) {
Expand All @@ -50,4 +98,35 @@ private Map<String, String> buildEmailVariables(Contact contact, Reminder remind

return map;
}

public void retryEmailSendFailure(EmailSendFailure emailSendFailure) {

Map<String, String> variables = buildEmailParametersFromEmailFailure(emailSendFailure);

MimeMessage mimeMessage = mailEngine.createEmailMessage(variables);

if (mimeMessage == null) {
return;
}

dispatchEmail(mimeMessage);
}

private static Map<String, String> buildEmailParametersFromEmailFailure(EmailSendFailure emailSendFailure) {
Map<String, String> variables = new HashMap<>();

variables.put("name", emailSendFailure.getName());
variables.put("email", emailSendFailure.getEmail());
variables.put("title", emailSendFailure.getTitle());
variables.put(
"remind_at",
emailSendFailure.getRemindAt() != null
? emailSendFailure.getRemindAt()
: emailSendFailure.getDueDate());
variables.put("due_date", emailSendFailure.getDueDate());
variables.put("disable_notification_url", emailSendFailure.getDisableNotificationUrl());
variables.put("subject", "Lembrete" + " - " + emailSendFailure.getTitle());

return variables;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package br.com.springnoobs.reminderapi.schedule.job.mail;

import br.com.springnoobs.reminderapi.mail.entity.EmailSendFailure;
import br.com.springnoobs.reminderapi.mail.exception.EmailSendException;
import br.com.springnoobs.reminderapi.mail.repository.EmailSendFailureRepository;
import br.com.springnoobs.reminderapi.mail.service.EmailService;
import java.util.List;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class RetryFailedEmailsJob implements Job {

private static final Logger logger = LoggerFactory.getLogger(RetryFailedEmailsJob.class);

private final EmailSendFailureRepository repository;
private final EmailService emailService;

public RetryFailedEmailsJob(EmailSendFailureRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
List<EmailSendFailure> failureList = repository.findTop20ByOrderByFailedAtAsc();

if (failureList.isEmpty()) {
return;
}

for (EmailSendFailure failure : failureList) {
retryFailedEmailsSent(failure);
}
}

private void retryFailedEmailsSent(EmailSendFailure failure) {
try {
emailService.retryEmailSendFailure(failure);

repository.delete(failure);

logger.info("Email resend successful to {}", failure.getEmail());

} catch (EmailSendException e) {

logger.error("Failed to resend email ID {}: {}", failure.getId(), e.getMessage());

failure.setRetryCount(failure.getRetryCount() + 1);
failure.setErrorMessage(e.getMessage());
repository.save(failure);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package br.com.springnoobs.reminderapi.schedule.job;
package br.com.springnoobs.reminderapi.schedule.job.reminder;

import br.com.springnoobs.reminderapi.mail.service.EmailService;
import br.com.springnoobs.reminderapi.reminder.repository.ReminderRepository;
Expand Down
Loading