Aprende a enviar correos con archivos adjuntos en Spring Boot usando Gmail SMTP. Configura JavaMailSender, gestiona múltiples archivos con MultipartFile y mejora el rendimiento con envío asincrónico mediante @Async.
pom.xml
@Async
Enviar correos electrónicos con archivos adjuntos es una funcionalidad esencial en muchas aplicaciones modernas, ya sea para enviar facturas, reportes, documentos o imágenes. Spring Boot proporciona una forma sencilla de integrar el servicio de correo utilizando JavaMailSender.
En este artículo, exploraremos cómo configurar y usar este servicio para enviar correos con archivos adjuntos, además de algunas optimizaciones como envío asincrónico, uso de plantillas HTML y mejoras en seguridad.
Si quieres ver un tutorial rápido de cómo enviar correos, lo encuentras en el siguiente link.
Antes de comenzar, asegúrate de tener:
pom.xml
Spring Boot simplifica la integración con servicios de correo mediante el Starter Mail. Para comenzar, necesitamos agregar la dependencia en nuestro archivo pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
🔹 ¿Qué hace esta dependencia?
También es recomendable agregar la siguiente dependencia para poder probar el envío de correos desde un endpoint:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Configura las credenciales y propiedades de SMTP en application.properties
o application.yml
.
📌 Usando application.properties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=tu_email@gmail.com
spring.mail.password=tu_contraseña
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
📌 Usando application.yml
spring:
mail:
host: smtp.gmail.com
port: 587
username: tu_email@gmail.com
password: tu_contraseña
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
💡 Nota: Si tienes autenticación en dos pasos activada en Gmail, usa una contraseña de aplicación en lugar de tu contraseña normal, si no sabes como generar la contraseña de aplicación visita el siguiente link
Crea una clase EmailService
que use JavaMailSender
para enviar correos con múltiples adjuntos.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import java.io.File;
import java.util.List;
@Service
public class EmailService {
private final JavaMailSender mailSender;
@Autowired
public EmailService(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void sendEmailWithAttachment(String to, String subject, String text, String filePath) throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text);
helper.setFrom("amoelcodigotest@gmail.com");
FileSystemResource file = new FileSystemResource(new File(filePath));
helper.addAttachment(file.getFilename(), file);
mailSender.send(mimeMessage);
}
public void sendEmailWithMultipleAttachment(String to, String subject, String text, List<MultipartFile> files) throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text);
helper.setFrom("amoelcodigotest@gmail.com");
for (MultipartFile file: files){
helper.addAttachment(file.getOriginalFilename(), file);
}
mailSender.send(mimeMessage);
}
}
MultipartFile es una interfaz de Spring Boot utilizada para manejar archivos subidos desde una solicitud HTTP, generalmente a través de formularios web o clientes REST como Postman.
Spring Boot usa MultipartFile junto con Spring MVC para procesar archivos adjuntos sin necesidad de guardarlos físicamente en el sistema de archivos.
📌 Ventajas de MultipartFile:
Característica | FileSystemResource | MultipartFile |
---|---|---|
Origen del archivo | Ruta en el sistema de archivos del servidor | Se recibe en una solicitud HTTP |
Dependencia del sistema | Sí, requiere que el archivo ya exista en el servidor | No, el archivo viene del cliente |
Casos de uso | Enviar archivos preexistentes en el servidor | Permitir a los usuarios subir archivos |
Persistencia | El archivo debe existir en el servidor | Puede guardarse o procesarse en memoria |
Añade un controlador para exponer un endpoint que permita enviar correos con múltiples adjuntos.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import jakarta.mail.MessagingException;
import java.util.List;
@RestController
@RequestMapping("/email")
public class EmailController {
@Autowired
private EmailService emailService;
@PostMapping("/sendWithAttachment")
public String sendEmailWithAttachments(
@RequestParam String to,
@RequestParam String subject,
@RequestParam String text,
@RequestParam String filePaths) {
try {
emailService.sendEmailWithAttachments(to, subject, text, filePaths);
return "Correo enviado con éxito a " + to;
} catch (MessagingException e) {
return "Error al enviar el correo: " + e.getMessage();
}
}
@PostMapping("/sendWithMultipleAttachment")
public String sendEmailWithMultipleAttachment(
@RequestParam String to,
@RequestParam String subject,
@RequestParam String body,
@RequestParam List<MultipartFile> files) {
try {
emailService.sendEmailWithMultipleAttachment(to, subject, body, files);
return "Correo con adjunto enviado a " + to;
} catch (MessagingException e) {
return "Error al enviar el correo: " + e.getMessage();
}
}
}
Una vez que hemos configurado todo, es hora de probar si el sistema de envío de correos con archivos adjuntos esta funcionando correctamente.
Postman:
Abre Postman y crea una nueva solicitud POST
a la URL de tu endpoint. Por ejemplo, si estás ejecutando la aplicación localmente, la URL podría ser http://localhost:8080/email/sendWithAttachments
.
En el cuerpo de la solicitud, agrega los parámetros necesarios: to
, subject
, text
, y filePaths
(una lista de rutas de archivos en tu sistema).
Puedes utilizar el formato form-data
para enviar los archivos adjuntos. Asegúrate de que el cuerpo de la solicitud sea algo como esto:
to=correo_destino@gmail.com
subject=Asunto del Correo
text=Este es el contenido del correo
filePaths=/ruta/a/tu/archivo1.pdf
Para multiples archivos hacemos lo siguiente Ve a la pestaña Body y selecciona form-data.
Añade los siguientes campos:
to → (correo de destino)
subject → Prueba con múltiples archivos
text → Este correo tiene varios archivos adjuntos
files → Selecciona múltiples archivos en formato File (puedes subir PDF, JPG, etc.).
Envía la solicitud y verifica el correo.
Usando cURL:
Si prefieres usar cURL
para probar, puedes hacer algo similar desde la terminal. Aquí te dejo un ejemplo de cómo enviar la solicitud:
curl -X POST "http://localhost:8080/email/sendWithAttachments" \
-d "to=correo_destino@gmail.com" \
-d "subject=Asunto del Correo" \
-d "text=Este es el contenido del correo" \
-d "filePaths=/ruta/a/tu/archivo1.pdf"
@Async
@Async
?La anotación @Async
en Spring permite ejecutar métodos de forma asincrónica, lo que significa que se ejecutan en un hilo separado sin bloquear la ejecución del programa principal.
@Async
, Spring lo delega a un nuevo hilo en el pool, permitiendo que la ejecución principal continúe sin esperar la finalización del método asincrónico.@Async
Debes habilitar el soporte para métodos asincrónicos agregando @EnableAsync
en la clase de configuración principal de Spring Boot:
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AsyncConfig {
}
Ahora que hemos habilitado la ejecución asincrónica, vamos a modificar nuestro servicio de correo para enviar correos asincrónicamente:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import java.io.File;
import java.util.List;
@Service
public class AsyncEmailService {
private final JavaMailSender mailSender;
public AsyncEmailService(final JavaMailSender mailSender) {
this.mailSender = mailSender;
}
@Async
public void sendEmailWithAttachments(String to, String subject, String text, List<String> filePaths) throws MessagingException {
try {
System.out.println("Inicio del envío del correo... (hilo: " + Thread.currentThread().getName() + ")");
Thread.sleep(5000); // Simula un retraso de 5 segundos
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text);
helper.setFrom("tu_email@gmail.com");
for (String filePath : filePaths) {
FileSystemResource file = new FileSystemResource(new File(filePath));
helper.addAttachment(file.getFilename(), file);
}
mailSender.send(mimeMessage);
System.out.println("Correo enviado con éxito! (hilo: " + Thread.currentThread().getName() + "");
}
}
Para comprobar que los correos se están enviando de forma asincrónica, puedes activar logs adicionales en application.properties
:
logging.level.org.springframework.scheduling.annotation.Async=DEBUG
Luego, al ejecutar la aplicación, observa los logs. Si ves que el envío de correos no bloquea otras tareas, significa que @Async
está funcionando correctamente.
@Async
no funciona en métodos privados.@Async
se usa en la misma clase, Spring no lo interceptará (debe ser llamado desde otra clase gestionada por Spring).import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class AsyncExecutorConfig {
@Bean(name = "emailExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("AsyncEmail-");
executor.initialize();
return executor;
}
}
Ahora, puedes especificar el executor en @Async("emailExecutor")
para asignar la tarea a este pool de hilos personalizado.
🔗 Mira el video tutorial pinchando aquí
Puedes encontrar el código completo de este ejemplo en el siguiente repositorio de GitHub:
🔗 Repositorio en GitHub: smtp-java
En este artículo hemos explorado cómo enviar correos electrónicos con archivos adjuntos en una aplicación Spring Boot, utilizando JavaMailSender. Hemos cubierto la configuración del servidor SMTP, la implementación de un servicio de correo y la creación de un controlador REST para exponer esta funcionalidad.
Además, vimos cómo mejorar el rendimiento con envío asincrónico usando @Async, lo que permite que la aplicación continúe ejecutándose sin esperar a que se complete el envío del correo. También destacamos consideraciones importantes, como la necesidad de configurar un executor personalizado para controlar mejor la ejecución de tareas asincrónicas.
En resumen, la combinación de Spring Boot, JavaMailSender y @Async permite implementar un sistema de envío de correos robusto, eficiente y escalable, ideal para aplicaciones modernas que requieren esta funcionalidad sin comprometer el rendimiento.