Enviar Correos con Archivos Adjuntos en Spring Boot usando Gmail SMTP

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.

Enviar Correos con Archivos Adjuntos en Spring Boot usando Gmail SMTP

Tabla de contenidos

Introducción

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.

Requisitos Previos

Antes de comenzar, asegúrate de tener:

  • Java 17+
  • Spring Boot 3+
  • Cuenta de Gmail
  • Postman o cURL para probar los endpoints

Paso 1: Agregar Dependencias en 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?

  • Agrega las clases necesarias para enviar correos electrónicos en Spring Boot.
  • Nos permite configurar fácilmente un servidor SMTP.

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>

Paso 2: Configuración del Servidor SMTP

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

Paso 3: Crear el Servicio de Correo

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);
    }
}

¿Qué es MultipartFile en Spring Boot?

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:

  • ✅ Permite manejar archivos directamente desde el cliente sin depender del sistema de archivos del servidor.
  • ✅ Facilita el envío de múltiples archivos en una sola solicitud.
  • ✅ Se puede combinar con almacenamiento en bases de datos o sistemas de archivos en la nube.

Diferencias entre FileSystemResource y MultipartFile**

CaracterísticaFileSystemResourceMultipartFile
Origen del archivoRuta en el sistema de archivos del servidorSe recibe en una solicitud HTTP
Dependencia del sistemaSí, requiere que el archivo ya exista en el servidorNo, el archivo viene del cliente
Casos de usoEnviar archivos preexistentes en el servidorPermitir a los usuarios subir archivos
PersistenciaEl archivo debe existir en el servidorPuede guardarse o procesarse en memoria

Paso 4: Crear un Controlador REST

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();
        }
    }
}

Paso 5: Probar el Envío de Correos

Una vez que hemos configurado todo, es hora de probar si el sistema de envío de correos con archivos adjuntos esta funcionando correctamente.

Usando Postman o cURL

  1. 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.

  1. 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"
    

Envío Asincrónico de Correos con @Async

¿Qué es @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.

🔹 ¿Cómo funciona?

  1. Spring administra un pool de hilos para ejecutar tareas asincrónicas.
  2. Cuando se llama a un método con @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.
  3. Esto es útil para operaciones que pueden tomar tiempo, como enviar correos o hacer llamadas a APIs externas.
  4. Esto libera el hilo principal y mejora la experiencia del usuario, ya que la aplicación puede seguir procesando otras tareas mientras se ejecuta el envío del correo

📌 Configuración necesaria para @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 {
}

🔹 Envío Asincrónico con @Async

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() + "");

    }
}

📊 Verificación del Envío Asincrónico

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.

⚠️ Consideraciones y Posibles Problemas

  • @Async no funciona en métodos privados.
  • Si @Async se usa en la misma clase, Spring no lo interceptará (debe ser llamado desde otra clase gestionada por Spring).
  • Es recomendable configurar un executor personalizado para mayor control del pool de hilos:
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.


Video Tutorial

🔗 Mira el video tutorial pinchando aquí


Repositorio con el Ejemplo

Puedes encontrar el código completo de este ejemplo en el siguiente repositorio de GitHub:

🔗 Repositorio en GitHub: smtp-java


Conclusión

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.