EmailService.java
package com.archiweb.service;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.BarcodeFormat;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.List;
@Service
@Slf4j
public class EmailService {
private final JavaMailSender mailSender;
public EmailService(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
// ---------------------------------------------------
// 1. Envoi simple (texte)
// ---------------------------------------------------
public void sendEmail(String to, String subject, String text) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
log.info("Email envoyé à {}", to);
} catch (Exception e) {
log.error("Erreur envoi email simple: {}", e.getMessage());
}
}
// ---------------------------------------------------
// 2. Envoi groupé
// ---------------------------------------------------
public int sendBulkEmail(String subject, String text, List<String> recipients) {
int sent = 0;
for (String email : recipients) {
sendEmail(email, subject, text);
sent++;
}
return sent;
}
// ---------------------------------------------------
// 2b. Envoi groupé HTML
// ---------------------------------------------------
public int sendBulkEmailHtml(String subject, String htmlContent, List<String> recipients) {
int sent = 0;
for (String email : recipients) {
sendEmailHtml(email, subject, htmlContent);
sent++;
}
return sent;
}
// ---------------------------------------------------
// 1b. Envoi simple HTML
// ---------------------------------------------------
public void sendEmailHtml(String to, String subject, String htmlContent) {
try {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
helper.setTo(to);
helper.setSubject(subject);
// Définir explicitement le contenu HTML
helper.setText(htmlContent, true); // true = HTML content
// Définir explicitement le Content-Type
mimeMessage.setContent(htmlContent, "text/html; charset=UTF-8");
mailSender.send(mimeMessage);
log.info("✅ Email HTML envoyé à {} avec succès (Content-Type: text/html)", to);
} catch (Exception e) {
log.error("❌ Erreur envoi email HTML à {}: {}", to, e.getMessage(), e);
throw new RuntimeException("Erreur lors de l'envoi de l'email HTML", e);
}
}
// ---------------------------------------------------
// 3. Email avec QR Code intégré
// ---------------------------------------------------
public void sendPrizeEmail(String to, String codeValue, String prizeType, String storeAddress) {
try {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setTo(to);
helper.setSubject("Félicitations ! Vous avez gagné un lot TipTop");
// HTML email
String htmlContent = """
<p>Bonjour,</p>
<p>Félicitations, vous avez gagné un lot :</p>
<ul>
<li><strong>Lot :</strong> %s</li>
<li><strong>Code :</strong> %s</li>
</ul>
<p>Veuillez vous rendre en boutique :</p>
<p><strong>%s</strong></p>
<p>Présentez ce QR Code à l'accueil :</p>
<p><img src="cid:qrcode"></p>
<br>
<p>Cordialement,<br>L'équipe TipTop ArchiWeb</p>
""".formatted(prizeType, codeValue, storeAddress);
helper.setText(htmlContent, true);
// Génération QR Code
byte[] qrBytes = generateQRCodeImage(codeValue, 400, 400);
// Ajout inline pour affichage dans email
helper.addInline("qrcode", new ByteArrayResource(qrBytes), "image/png");
// Ajout en pièce jointe
helper.addAttachment("QRCode_" + codeValue + ".png", new ByteArrayResource(qrBytes));
mailSender.send(mimeMessage);
log.info("Email de gain envoyé à {}", to);
} catch (Exception e) {
log.error("Erreur envoi email avec QR code: {}", e.getMessage());
}
}
// ---------------------------------------------------
// 4. Génération QR Code PNG
// ---------------------------------------------------
private byte[] generateQRCodeImage(String text, int width, int height) throws Exception {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, width, height);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
boolean black = bitMatrix.get(x, y);
image.setRGB(x, y, black ? 0x000000 : 0xFFFFFF);
}
}
ByteArrayOutputStream pngOutput = new ByteArrayOutputStream();
ImageIO.write(image, "png", pngOutput);
return pngOutput.toByteArray();
}
// ---------------------------------------------------
// 5. Email reset password
// ---------------------------------------------------
public void sendResetPasswordEmail(String to, String token) {
try {
String resetLink = "http://localhost:4200/reset-password?token=" + token;
String body = "Bonjour,\n\nCliquez pour réinitialiser votre mot de passe :\n"
+ resetLink + "\n\nCe lien expirera dans 30 minutes.\n\nL'équipe ArchiWeb.";
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject("Réinitialisation du mot de passe");
message.setText(body);
mailSender.send(message);
} catch (Exception e) {
log.error("Erreur envoi email reset password: {}", e.getMessage());
}
}
// ---------------------------------------------------
// 6. Email Newsletter avec template HTML
// ---------------------------------------------------
public void sendNewsletterEmail(String to, String subject, String content) {
try {
String htmlContent = buildNewsletterTemplate(subject, content);
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlContent, true);
mailSender.send(mimeMessage);
log.info("✅ Email newsletter envoyé à {}", to);
} catch (Exception e) {
log.error("❌ Erreur envoi email newsletter à {}: {}", to, e.getMessage(), e);
throw new RuntimeException("Erreur lors de l'envoi de l'email newsletter", e);
}
}
// ---------------------------------------------------
// 7. Template HTML pour newsletter (public pour prévisualisation)
// ---------------------------------------------------
public String buildNewsletterTemplate(String subject, String content) {
// Convertir les retours à la ligne en <br> et préserver les paragraphes
String htmlContent = content
.replace("\n\n", "</p><p style=\"margin: 0 0 1.25em 0;\">")
.replace("\n", "<br>");
// Si le contenu ne commence pas par <p>, l'ajouter
if (!htmlContent.trim().startsWith("<p>") && !htmlContent.trim().startsWith("<div>")) {
htmlContent = "<p style=\"margin: 0 0 1.25em 0;\">" + htmlContent + "</p>";
}
return """
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>%s</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
p { margin: 0 0 1.25em 0; line-height: 1.7; }
p:last-child { margin-bottom: 0; }
a { color: #4CAF50; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width: 100%%; border-collapse: collapse; background-color: #f5f5f5; padding: 0;">
<tr>
<td align="center" style="padding: 40px 20px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="max-width: 600px; width: 100%%; background-color: #ffffff; border-radius: 20px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); overflow: hidden; border: 1px solid #e8e8e8;">
<!-- Header -->
<tr>
<td style="background: linear-gradient(135deg, #4CAF50 0%%, #45a049 100%%); padding: 50px 40px; text-align: center; position: relative; overflow: hidden;">
<div style="position: absolute; top: -50px; right: -50px; width: 200px; height: 200px; background: rgba(255, 255, 255, 0.1); border-radius: 50%%;"></div>
<div style="position: absolute; bottom: -30px; left: -30px; width: 150px; height: 150px; background: rgba(255, 255, 255, 0.08); border-radius: 50%%;"></div>
<div style="position: relative; z-index: 1;">
<h1 style="margin: 0; color: #ffffff; font-size: 36px; font-weight: 700; letter-spacing: -1px; line-height: 1.2;">
🍵 Thé Tip Top
</h1>
<p style="margin: 16px 0 0 0; color: #e8f5e9; font-size: 16px; font-weight: 400; letter-spacing: 0.3px;">
Votre newsletter exclusive
</p>
</div>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 60px 50px; background-color: #ffffff;">
<div style="color: #212121; font-size: 17px; line-height: 1.75; font-weight: 400;">
%s
</div>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background: linear-gradient(180deg, #f8f9fa 0%%, #ffffff 100%%); padding: 40px 50px; text-align: center; border-top: 1px solid #e8e8e8;">
<p style="margin: 0 0 16px 0; color: #424242; font-size: 16px; font-weight: 600; letter-spacing: 0.2px;">
Thé Tip Top
</p>
<p style="margin: 0 0 20px 0; color: #757575; font-size: 14px; line-height: 1.6;">
Votre destination pour les meilleurs thés
</p>
<div style="padding-top: 20px; border-top: 1px solid #e8e8e8; margin-top: 20px;">
<p style="margin: 0 0 12px 0; color: #9e9e9e; font-size: 12px; line-height: 1.6;">
Vous recevez cet email car vous êtes abonné à notre newsletter.
</p>
<a href="#" style="color: #4CAF50; text-decoration: none; font-weight: 500; font-size: 13px; border-bottom: 1px solid #4CAF50; padding-bottom: 2px; transition: all 0.2s ease;">Se désabonner</a>
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
""".formatted(subject, htmlContent);
}
}