EmailService.java
package com.edtech.service;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/** Sends account emails through the Resend HTTPS API in production and SMTP locally. */
@Service
public class EmailService {
private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
private static final URI RESEND_EMAILS_URI = URI.create("https://api.resend.com/emails");
@Autowired private JavaMailSender mailSender;
@Value("${SMTP_FROM:noreply@edtechacademic.com.br}")
private String fromAddress;
@Value("${SMTP_PASSWORD:}")
private String resendApiKey;
/** Sends the password recovery code. */
@Async
public void sendRecoveryEmail(String toEmail, String otpCode) {
sendEmail(
toEmail,
"Código de Recuperação de Senha - EdTech",
"Olá,\n\n"
+ "Você solicitou a recuperação da sua senha na EdTech.\n"
+ "Seu código de segurança (OTP) de 6 dígitos é: "
+ otpCode
+ "\n\n"
+ "Este código é válido por 15 minutos.\n"
+ "Se você não solicitou isso, ignore este e-mail.\n\n"
+ "Equipe EdTech");
}
/** Sends the registration verification code. */
@Async
public void sendVerificationEmail(String toEmail, String otpCode) {
sendEmail(
toEmail,
"Código de Verificação de Conta - EdTech",
"Olá,\n\n"
+ "Bem-vindo à EdTech!\n"
+ "Seu código de verificação (OTP) de 6 dígitos é: "
+ otpCode
+ "\n\n"
+ "Este código é válido por 15 minutos.\n"
+ "Se você não solicitou isso, ignore este e-mail.\n\n"
+ "Equipe EdTech");
}
private void sendEmail(String toEmail, String subject, String text) {
String apiKey = resendApiKey == null ? "" : resendApiKey.trim();
if (apiKey.startsWith("re_")) {
sendWithResendApi(apiKey, toEmail, subject, text);
return;
}
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(resolveFromAddress());
message.setTo(toEmail);
message.setSubject(subject);
message.setText(text);
try {
mailSender.send(message);
logger.info("Email dispatched through SMTP to {}.", toEmail);
} catch (MailException ex) {
logger.warn("SMTP email delivery failed for {}.", toEmail, ex);
}
}
private void sendWithResendApi(String apiKey, String toEmail, String subject, String text) {
String payload =
"{\"from\":\""
+ escapeJson(resolveFromAddress())
+ "\",\"to\":[\""
+ escapeJson(toEmail)
+ "\"],\"subject\":\""
+ escapeJson(subject)
+ "\",\"text\":\""
+ escapeJson(text)
+ "\"}";
HttpRequest request =
HttpRequest.newBuilder(RESEND_EMAILS_URI)
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload, StandardCharsets.UTF_8))
.build();
try {
HttpResponse<String> response =
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 200 && response.statusCode() < 300) {
logger.info("Email dispatched through Resend API to {}.", toEmail);
} else {
logger.warn(
"Resend API rejected email for {} with HTTP status {}.",
toEmail,
response.statusCode());
}
} catch (IOException ex) {
logger.warn("Resend API delivery failed for {}.", toEmail, ex);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
logger.warn("Resend API delivery interrupted for {}.", toEmail, ex);
}
}
private String escapeJson(String value) {
return value
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r");
}
private String resolveFromAddress() {
return fromAddress == null || fromAddress.isBlank()
? "noreply@edtechacademic.com.br"
: fromAddress;
}
}