La implementación de funcionalidades de envío de correos electrónicos es una necesidad común en muchas aplicaciones empresariales. Ya sea para notificaciones de usuarios, restablecimiento de contraseñas, o informes automáticos, la capacidad de enviar correos es crucial. Este artículo detalla cómo integrar de manera eficiente el servicio de envío de correos en una aplicación Spring Boot, utilizando el módulo Spring Mail y plantillas HTML con FreeMarker para correos enriquecidos.
Consideraciones Previas para el Envío de Correos
Antes de sumergirnos en el código, es fundamental comprender los conceptos básicos y obtener la información necesaria para configurar el servicio de correo.
Protocolos de Correo
Existen varios protocolos de correo, pero para el envío, el más relevante es el SMTP (Simple Mail Transfer Protocol). Mientras que POP3 e IMAP se utilizan para recibir y gestionar correos en el cliente, SMTP es el estándar para enviar mensajes de un servidor a otro.
Servidor SMTP y Credenciales de Acceso
Cada proveedor de correo (Gmail, Outlook, servidores corporativos) tiene su propio servidor SMTP. Necesitarás la dirección del servidor (ej. smtp.gmail.com), el puerto (comúnmente 465 o 587 para SSL/TLS) y, crucialmente, las credenciales de acceso. Es importante destacar que muchos proveedores modernos requieren una contraseña de aplicación o código de autorización específico en lugar de tu contraseña principle de correo electrónico por motivos de seguridad. Asegúrate de generar esta contraseña de aplicación desde la configuración de seguridad de tu cuenta de correo si tu proveedor lo requiere.
A continuación, algunos ejemplos de configuración de servidor SMTP para proveedores comunes:
- Gmail:
- Servidor SMTP:
smtp.gmail.com - Puerto:
465(SSL) o587(TLS) - Requiere contraseña de aplicación (generar en la configuración de seguridad de Google).
- Servidor SMTP:
- Outlook/Office 365:
- Servidor SMTP:
smtp.office365.com - Puerto:
587(TLS/STARTTLS)
- Servidor SMTP:
- Servidores Corporativos:
- La configuración variará. Consulta a tu administrador de TI para obtener la dirección del servidor SMTP, el puerto y si se requiere una contraseña de aplicación.
Dependencias Necesarias
Para integrar el servicio de correo, añadiremos las siguientes dependencias a nuestro archivo pom.xml:
<!-- Para la funcionalidad de envío de correos -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Para renderizar plantillas HTML con FreeMarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
Configuración del Archivo application.yml
Configura las propiedades del correo y FreeMarker en tu archivo application.yml:
spring:
# Configuración del servicio de correo
mail:
default-encoding: UTF-8 # Codificación de caracteres predeterminada
host: smtp.tu-servidor-email.com # Dirección del servidor SMTP (ej. smtp.gmail.com)
port: 465 # Puerto del servidor SMTP (ej. 465 para SSL, 587 para TLS)
protocol: smtps # Protocolo: smtps si usas SSL en el puerto 465, smtp para TLS en 587
username: tu-correo@dominio.com # Tu dirección de correo electrónico remitente
password: tu-contrasena-aplicacion # Contraseña de aplicación o código de autorización
properties:
mail.smtp.auth: true # Habilitar autenticación SMTP
mail.smtp.starttls.enable: true # Habilitar STARTTLS (si usas puerto 587)
mail.smtp.starttls.required: true # Requerir STARTTLS
mail.smtp.ssl.enable: true # Habilitar SSL (si usas puerto 465)
# Configuración de FreeMarker para plantillas HTML
freemarker:
cache: false # Deshabilitar caché en desarrollo
suffix: .ftlh # Sufijo de las plantillas (puede ser .html o .ftlh)
charset: UTF-8 # Codificación de los archivos de plantilla
template-loader-path: classpath:/templates/ # Ruta donde se encuentran las plantillas
Clases de Utilidad y Configuración
Vamos a estructurar nuestro código con una clase para las propiedades de configuración y otra para el servicio de envío.
Clase de Propiedades de Correo
Esta clase mapeará las propiedades de spring.mail desde application.yml.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "spring.mail")
public class ConfiguracionCorreo {
private String defaultEncoding;
private String host;
private String username;
private String password;
private int port;
private String protocol;
// Getters y Setters
public String getDefaultEncoding() { return defaultEncoding; }
public void setDefaultEncoding(String defaultEncoding) { this.defaultEncoding = defaultEncoding; }
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getProtocol() { return protocol; }
public void setProtocol(String protocol) { this.protocol = protocol; }
}
Servicio de Envío de Correo
Este servicio encapsulará la lógica para enviar diferentes tipos de correos (texto plano, HTML con plantilla, con archivos adjuntos).
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.Map;
@Service
public class ServicioEmail {
private final JavaMailSender remitenteCorreo;
private final ConfiguracionCorreo configCorreo;
private final Configuration freeMarkerConfig; // Usamos Configuration directamente
public ServicioEmail(JavaMailSender remitenteCorreo, ConfiguracionCorreo configCorreo,
org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer freeMarkerConfigurer) {
this.remitenteCorreo = remitenteCorreo;
this.configCorreo = configCorreo;
this.freeMarkerConfig = freeMarkerConfigurer.getConfiguration();
}
/**
* Envía un correo electrónico de texto plano.
* @param asunto Asunto del correo.
* @param contenido Cuerpo del mensaje.
* @param destinatarios Direcciones de correo de los destinatarios.
*/
public void enviarTextoPlano(String asunto, String contenido, String... destinatarios) {
enviarTextoPlano(asunto, contenido, null, destinatarios);
}
/**
* Envía un correo electrónico de texto plano con copia (CC).
* @param asunto Asunto del correo.
* @param contenido Cuerpo del mensaje.
* @param cc Dirección de correo para copia (CC), puede ser null.
* @param destinatarios Direcciones de correo de los destinatarios.
*/
public void enviarTextoPlano(String asunto, String contenido, String cc, String... destinatarios) {
SimpleMailMessage mensaje = new SimpleMailMessage();
mensaje.setFrom(configCorreo.getUsername());
mensaje.setTo(destinatarios);
mensaje.setSubject(asunto);
mensaje.setText(contenido);
if (StringUtils.hasText(cc)) {
mensaje.setCc(cc);
}
remitenteCorreo.send(mensaje);
}
/**
* Envía un correo electrónico HTML usando una plantilla FreeMarker.
* @param asunto Asunto del correo.
* @param nombrePlantilla Nombre del archivo de plantilla (ej. "bienvenida.ftlh").
* @param modelo Datos para la plantilla.
* @param destinatarios Direcciones de correo de los destinatarios.
* @throws MessagingException Si ocurre un error de mensajería.
* @throws IOException Si la plantilla no se puede cargar.
* @throws TemplateException Si ocurre un error al procesar la plantilla.
*/
public void enviarHtmlConPlantilla(String asunto, String nombrePlantilla, Map<String, Object> modelo, String... destinatarios)
throws MessagingException, IOException, TemplateException {
String contenidoHtml = procesarPlantillaHtml(nombrePlantilla, modelo);
enviarHtml(asunto, contenidoHtml, null, destinatarios);
}
/**
* Envía un correo electrónico HTML directamente.
* @param asunto Asunto del correo.
* @param contenidoHtml Contenido HTML del correo.
* @param destinatarios Direcciones de correo de los destinatarios.
* @throws MessagingException Si ocurre un error de mensajería.
*/
public void enviarHtml(String asunto, String contenidoHtml, String... destinatarios)
throws MessagingException {
enviarHtml(asunto, contenidoHtml, null, destinatarios);
}
/**
* Envía un correo electrónico HTML directamente con copia (CC).
* @param asunto Asunto del correo.
* @param contenidoHtml Contenido HTML del correo.
* @param cc Dirección de correo para copia (CC), puede ser null.
* @param destinatarios Direcciones de correo de los destinatarios.
* @throws MessagingException Si ocurre un error de mensajería.
*/
public void enviarHtml(String asunto, String contenidoHtml, String cc, String... destinatarios)
throws MessagingException {
MimeMessage mensajeMime = remitenteCorreo.createMimeMessage();
MimeMessageHelper ayudante = new MimeMessageHelper(mensajeMime, true, configCorreo.getDefaultEncoding());
ayudante.setFrom(configCorreo.getUsername());
ayudante.setTo(destinatarios);
ayudante.setSubject(asunto);
ayudante.setText(contenidoHtml, true); // true indica que es contenido HTML
if (StringUtils.hasText(cc)) {
ayudante.setCc(cc);
}
remitenteCorreo.send(mensajeMime);
}
/**
* Envía un correo electrónico con archivos adjuntos.
* @param asunto Asunto del correo.
* @param contenido Cuerpo del mensaje.
* @param adjuntos Un mapa donde la clave es el nombre del archivo y el valor es la ruta al archivo.
* @param destinatarios Direcciones de correo de los destinatarios.
* @throws MessagingException Si ocurre un error de mensajería.
*/
public void enviarConAdjuntos(String asunto, String contenido, Map<String, String> adjuntos, String... destinatarios)
throws MessagingException {
enviarConAdjuntos(asunto, contenido, null, adjuntos, destinatarios);
}
/**
* Envía un correo electrónico con archivos adjuntos y copia (CC).
* @param asunto Asunto del correo.
* @param contenido Cuerpo del mensaje.
* @param cc Dirección de correo para copia (CC), puede ser null.
* @param adjuntos Un mapa donde la clave es el nombre del archivo y el valor es la ruta al archivo.
* @param destinatarios Direcciones de correo de los destinatarios.
* @throws MessagingException Si ocurre un error de mensajería.
*/
public void enviarConAdjuntos(String asunto, String contenido, String cc, Map<String, String> adjuntos, String... destinatarios)
throws MessagingException {
MimeMessage mensajeMime = remitenteCorreo.createMimeMessage();
MimeMessageHelper ayudante = new MimeMessageHelper(mensajeMime, true, configCorreo.getDefaultEncoding());
ayudante.setFrom(configCorreo.getUsername());
ayudante.setTo(destinatarios);
ayudante.setSubject(asunto);
ayudante.setText(contenido);
for (Map.Entry<String, String> entradaAdjunto : adjuntos.entrySet()) {
FileSystemResource archivo = new FileSystemResource(new File(entradaAdjunto.getValue()));
ayudante.addAttachment(entradaAdjunto.getKey(), archivo);
}
if (StringUtils.hasText(cc)) {
ayudante.setCc(cc);
}
remitenteCorreo.send(mensajeMime);
}
/**
* Procesa una plantilla FreeMarker y devuelve el contenido HTML resultante.
* @param nombrePlantilla Nombre del archivo de plantilla.
* @param modelo Datos para la plantilla.
* @return Contenido HTML generado.
* @throws IOException Si la plantilla no se puede cargar.
* @throws TemplateException Si ocurre un error al procesar la plantilla.
*/
private String procesarPlantillaHtml(String nombrePlantilla, Map<String, Object> modelo)
throws IOException, TemplateException {
Template plantilla = freeMarkerConfig.getTemplate(nombrePlantilla);
return FreeMarkerTemplateUtils.processTemplateIntoString(plantilla, modelo);
}
}
Nota sobre los adjuntos: El método enviarConAdjuntos espera un mapa donde el valor es la ruta absoluta o relativa de un archivo en el sistema de ficheros. Puedes adaptarlo para usar ClassPathResource para archivos incluidos en tu JAR, o InputStreamSource para flujos de entrada.
Plantilla HTML con FreeMarker
Crea un archivo bienvenida.ftlh (o .html si así lo configuraste) en src/main/resources/templates/.
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>¡Bienvenido a Nuestra Plataforma!</title>
<style>
body { font-family: 'Arial', sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 20px auto; background-color: #ffffff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); }
.header { background-color: #007bff; color: #ffffff; padding: 10px 20px; text-align: center; border-radius: 5px 5px 0 0; }
.content { padding: 20px; line-height: 1.6; color: #333333; }
.button-container { text-align: center; margin-top: 20px; }
.button { background-color: #28a745; color: #ffffff; padding: 10px 20px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block; }
.footer { text-align: center; margin-top: 30px; font-size: 0.8em; color: #777777; border-top: 1px solid #eeeeee; padding-top: 20px; }
.logo { max-width: 150px; margin-bottom: 15px; } /* Espacio para un logo */
</style>
</head>
<body>
<div class="container">
<div class="header">
<!-- <img src="URL_DE_TU_LOGO" alt="Logo de la Empresa" class="logo"> -->
<h1>¡Bienvenido a [Nombre de tu Plataforma]!</h1>
</div>
<div class="content">
<p>Estimado(a) <strong>${nombreUsuario}</strong>,</p>
<p>¡Nos complace darte la bienvenida a nuestra comunidad! Gracias por registrarte en [Nombre de tu Plataforma].</p>
<p>Para activar tu cuenta y comenzar a utilizar nuestros servicios, por favor haz clic en el botón de abajo:</p>
<div class="button-container">
<a href="${urlActivacion}" class="button">Activar Mi Cuenta</a>
</div>
<p>Si el botón no funciona, puedes copiar y pegar el siguiente enlace en tu navegador:</p>
<p><a href="${urlActivacion}">${urlActivacion}</a></p>
<p>Si no solicitaste este registro, por favor ignora este correo.</p>
<p>Saludos cordiales,<br>El equipo de [Nombre de tu Plataforma]</p>
</div>
<div class="footer">
<p>Este es un mensaje automático. Por favor, no respondas a este correo.</p>
<!-- <p>Puedes seguirnos en nuestras redes sociales: [Iconos/Enlaces]</p> -->
</div>
</div>
</body>
</html>
Probando el Envío de Correos
Puedes crear un controlador simple para probar la funcionalidad de envío de correos:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation; // Si usas OpenAPI/Swagger
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/correo")
public class ControladorPruebaCorreo {
private final ServicioEmail servicioEmail;
public ControladorPruebaCorreo(ServicioEmail servicioEmail) {
this.servicioEmail = servicioEmail;
}
/**
* Endpoint para probar el envío de un correo HTML con plantilla.
*/
@Operation(summary = "Prueba el envío de un correo HTML desde plantilla")
@GetMapping("/enviar-plantilla")
public String enviarCorreoPlantilla() {
String asunto = "¡Bienvenido a Nuestra Aplicación!";
String nombrePlantilla = "bienvenida.ftlh"; // Asegúrate que coincide con el nombre del archivo
String destinatario = "destinatario@ejemplo.com"; // Cambia por un correo real para pruebas
Map<String, Object> modeloDatos = new HashMap<>();
modeloDatos.put("nombreUsuario", "Usuario de Prueba");
modeloDatos.put("urlActivacion", "https://tu-aplicacion.com/activar/xyz123");
try {
servicioEmail.enviarHtmlConPlantilla(asunto, nombrePlantilla, modeloDatos, destinatario);
return "Correo HTML enviado exitosamente a " + destinatario;
} catch (Exception e) {
e.printStackTrace();
return "Error al enviar el correo: " + e.getMessage();
}
}
/**
* Endpoint para probar el envío de un correo de texto plano.
*/
@Operation(summary = "Prueba el envío de un correo de texto plano")
@GetMapping("/enviar-texto")
public String enviarCorreoTexto() {
String asunto = "Notificación Importante";
String contenido = "Este es un correo de prueba en texto plano enviado desde Spring Boot.";
String destinatario = "destinatario@ejemplo.com"; // Cambia por un correo real para pruebas
try {
servicioEmail.enviarTextoPlano(asunto, contenido, destinatario);
return "Correo de texto plano enviado exitosamente a " + destinatario;
} catch (Exception e) {
e.printStackTrace();
return "Error al enviar el correo: " + e.getMessage();
}
}
/**
* Endpoint para probar el envío de un correo con adjuntos.
* Asegúrate de tener un archivo 'ejemplo.pdf' o 'ejemplo.txt' en una ruta accesible.
*/
@Operation(summary = "Prueba el envío de un correo con archivos adjuntos")
@GetMapping("/enviar-adjuntos")
public String enviarCorreoConAdjuntos() {
String asunto = "Informe Mensual y Archivo Adjunto";
String contenido = "Adjunto encontrarás el informe mensual de tu cuenta.";
String destinatario = "destinatario@ejemplo.com"; // Cambia por un correo real para pruebas
Map<String, String> adjuntos = new HashMap<>();
// Asegúrate de que esta ruta sea válida en tu sistema.
// Por ejemplo, crea un archivo 'documento.pdf' en la raíz de tu proyecto para probar.
adjuntos.put("Informe_Abril.pdf", "/ruta/absoluta/a/tu/documento.pdf");
// adjuntos.put("notas.txt", "/ruta/absoluta/a/tus/notas.txt");
try {
servicioEmail.enviarConAdjuntos(asunto, contenido, adjuntos, destinatario);
return "Correo con adjuntos enviado exitosamente a " + destinatario;
} catch (Exception e) {
e.printStackTrace();
return "Error al enviar el correo con adjuntos: " + e.getMessage();
}
}
}