Spring Boot: Desenvolva Integrações com Email Sem Configurar SMTP em Dev

Testar envio de emails em desenvolvimento é complicado: você precisa de um servidor SMTP configurado, corre o risco de enviar emails reais por acidente, os testes são lentos, há chance de disparar muitas requisições sem querer durante o desenvolvimento, e ainda existe o risco de vazar credenciais sensíveis em commits ou configurações temporárias usadas durante o desenvolvimento.

Neste tutorial, você vai criar um bean substituto para o JavaMailSender que loga os emails no console ao invés de enviá-los, sem precisar alterar nenhuma linha do restante do seu código.

Analisando a implementação do bean padrão do JavaMailSender podemos notar que qualquer bean que definirmos irá substituir o bean padrão.

 @Bean
 @ConditionalOnMissingBean({JavaMailSender.class})
 JavaMailSenderImpl mailSender(
   MailProperties properties, 
   bjectProvider<SslBundles> sslBundles
 ) {
  JavaMailSenderImpl sender = new JavaMailSenderImpl();
  this.applyProperties(
    properties,
    sender,
    (SslBundles)sslBundles.getIfAvailable()
  );
  return sender;
}

Isso facilita nosso trabalho, visto que é só criarmos então um bean condicional que envia o conteúdo dos emails no console, dessa forma quando o bean condicional não existir então será usado o bean padrão do JavaMailSender, que é o realmente irá enviar os emails em produção.

Agora podemos criar o nosso bean condicional:

package kaiquebt.dev.example.config;

import java.util.Arrays;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import jakarta.mail.BodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;

@Configuration
public class MockMailSenderConfig {
    private static final Logger logger = LoggerFactory.getLogger(MockMailSenderConfig.class);

    @ConditionalOnProperty(prefix = "spring.mail", name = "mock.enabled", havingValue = "true", matchIfMissing = false)
    @Bean
    public JavaMailSender mockMailSender() {
        return new JavaMailSenderImpl() {
            @Override
            public void send(SimpleMailMessage simpleMessage) {
                logger.info("=== Mock Email Sent ===");
                logger.info("From: {}", simpleMessage.getFrom());
                logger.info("To: {}", Arrays.toString(simpleMessage.getTo()));
                logger.info("Subject: {}", simpleMessage.getSubject());
                logger.info("Text: {}", simpleMessage.getText());
                logger.info("=======================");
            }

            @Override
            public void send(MimeMessage mimeMessage) throws MailException {
                try {
                    logger.info("=== Mock MIME Email Sent ===");
                    logger.info("From: {}", Arrays.toString(mimeMessage.getFrom()));
                    logger.info("To: {}", Arrays.toString(mimeMessage.getAllRecipients()));
                    logger.info("Subject: {}", mimeMessage.getSubject());
                    logger.info("--- Email Body ---");
                    printContent(mimeMessage.getContent(), 0);
                    logger.info("============================");
                } catch (Exception e) {
                    logger.error("Error sending MIME email", e);
                }
            }

            @Override
            public void send(MimeMessage...mimeMessages) throws MailException {
                for (MimeMessage msg: mimeMessages) {
                    send(msg);
                }
            }

            private void printContent(Object content, int level) throws Exception {
                String indent = "  ".repeat(level);
                if (content instanceof MimeMultipart) {
                    MimeMultipart multipart = (MimeMultipart) content;
                    for (int i = 0; i < multipart.getCount(); i++) {
                        BodyPart bodyPart = multipart.getBodyPart(i);
                        logger.info("{}Part {} [{}]:", indent, (i + 1), bodyPart.getContentType());
                        printContent(bodyPart.getContent(), level + 1);
                    }
                } else if (content instanceof String) {
                    logger.info("{}{}", indent, content);
                } else {
                    logger.info("{}Non-text content: {}", indent, content.getClass().getName());
                }
            }
        };
    }

}

E para ativarmos no nosso application.properties:

spring.application.name=treinomax
server.port=8080

spring.mail.username=your-email@gmail.com
spring.mail.mock.enabled=true

ou caso esteja usando yaml:

spring:
  application:
    name: treinomax
  mail:
    username: your-email@gmail.com
    mock:
      enabled: true

server:
  port: 8080

Agora em qualquer momento em que seu código disparar um email, como no exemplo abaixo, o conteúdo do email será mostrado no seu console.

private void enviarEmail(String to, String subject, String htmlContent) throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

    helper.setFrom(fromEmail);
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(htmlContent, true);

    mailSender.send(message);
}

Um exemplo de log de uma aplicação que usou este bean, os logs foram registrados ao tentar criar uma nova conta – no momento em que é disparado um email de verificação de conta.

treinomax-backend   | 2025-11-14T07:52:32.929Z  INFO 90 --- [treinomax] [nio-8080-exec-2] s.t.api.auth.service.TokenService        : Token de verificação de email gerado para usuário: testeemail@gmail.com
treinomax-backend   | === Mock MIME Email Sent ===
treinomax-backend   | From: [your-email@gmail.com]
treinomax-backend   | To: [testeemail@gmail.com]
treinomax-backend   | Subject: Verifique seu email - TreinoMax
treinomax-backend   | --- Email Body ---
treinomax-backend   | Part 1 [text/plain]:
treinomax-backend   |   Part 1 [text/plain]:
treinomax-backend   |     <!DOCTYPE html>
treinomax-backend   | <html>
treinomax-backend   | <head>
treinomax-backend   |     ...
treinomax-backend   |     <style>
treinomax-backend   |         ...
treinomax-backend   |     </style>
treinomax-backend   | </head>
treinomax-backend   | <body>
treinomax-backend   |     <div class="container">
treinomax-backend   |         <div class="header">
treinomax-backend   |             <h1>Bem-vindo ao TreinoMax!</h1>
treinomax-backend   |         </div>
treinomax-backend   |
treinomax-backend   |         <div class="content">
treinomax-backend   |             <p>Olá, <strong>testeemail</strong>!</p>
treinomax-backend   |
treinomax-backend   |             <p>Obrigado por se cadastrar no TreinoMax - sua plataforma completa para gestão de academia e acompanhamento de treinos!</p>
treinomax-backend   |
treinomax-backend   |             <p>Para começar a usar todos os recursos da nossa plataforma, precisamos que você verifique seu endereço de email.</p>
treinomax-backend   |
treinomax-backend   |             <p style="text-align: center;">
treinomax-backend   |                 <a href="http://localhost:8080/verify-email?token=a2955c26-f566-4673-85ab-75b7d5e89fa6" class="button">Verificar Email</a>
treinomax-backend   |             </p>
treinomax-backend   |
treinomax-backend   |             <p>Se o botão acima não funcionar, copie e cole o seguinte link no seu navegador:</p>
treinomax-backend   |
treinomax-backend   |             <div class="code">http://localhost:8080/verify-email?token=a2955c26-f566-4673-85ab-75b7d5e89fa6</div>
treinomax-backend   |
treinomax-backend   |             <p>Ou use o código abaixo para verificar manualmente:</p>
treinomax-backend   |
treinomax-backend   |             <div class="code">a2955c26-f566-4673-85ab-75b7d5e89fa6</div>
treinomax-backend   |
treinomax-backend   |             <p><strong>Atenção:</strong> Este link expirará em 24 horas.</p>
treinomax-backend   |
treinomax-backend   |             <p>Se você não se cadastrou em nossa plataforma, por favor ignore este email.</p>
treinomax-backend   |         </div>
treinomax-backend   |
treinomax-backend   |         <div class="footer">
treinomax-backend   |             ...
treinomax-backend   |         </div>
treinomax-backend   |     </div>
treinomax-backend   | </body>
treinomax-backend   | </html>
treinomax-backend   | ============================
treinomax-backend   | 2025-11-14T07:52:33.143Z  INFO 90 --- [treinomax] [nio-8080-exec-2] s.t.api.auth.service.EmailService        : Email de verificação enviado para: testeemail@gmail.com

Neste exemplo, o desenvolvedor já teria à disposição facilmente o link de confirmação para caso ele queira testar a confirmação de conta, além da confirmação de que caso o JavaMailSender estivesse configurado, este email teria sido lançado.

Agora você tem uma solução simples e eficaz para testar emails durante o desenvolvimento.