maz

Como Implementar Envio de E-mails Estilizados Usando Amazon SES e Spring Boot

Eduardo Vinicius Frohlich
7 min readMay 7, 2024

--

No mundo digital de hoje, onde a comunicação é vital, o envio de e-mails continua sendo uma das formas mais eficazes de alcançar usuários e clientes. Seja para confirmação de cadastro, notificações de eventos ou mensagens de marketing personalizadas, os e-mails são uma ponte direta para o público. Neste artigo, exploraremos como integrar o Amazon Simple Email Service (SES), um serviço de envio de e-mails escalável e custo-efetivo, com o Spring Boot, um dos frameworks mais populares para a criação de aplicações Java. O projeto a seguir foi baseado em uma resolução de desafio para uma vaga da Uber feito pela Fernanda Kipper | Dev disponível em https://www.youtube.com/watch?v=eFgeO9M9lLw, o qual me inspirei para construir minha aplicação.

Disponível em GitHub

🇬🇧 Versão inglesa:

Amazon Simple Email Service (SES)

Tecnologias utilizadas

  • Java 17
  • Spring Boot 3.2.5
  • Postman
  • Thymeleaf: Um motor de template para Java, usado para processar HTML no lado do servidor.
  • BEE Free: Uma ferramenta online (https://beefree.io) que nos permite criar designs de e-mail responsivos e visualmente atraentes sem necessidade de conhecimento profundo em HTML e CSS.
  • AWS Java SDK: O Software Development Kit da Amazon para Java permite integrar facilmente qualquer aplicação Java com os serviços da AWS, incluindo o Amazon Simple Email Service (SES).

Crie seu endereço de email na Amazon SES

É por meio desse email que você irá disparar os emails para seus destinatários.

Previamente você deve ter uma conta já registrada no console da AWS. Verifique em seu e-mail a confirmação da AWS para utilização do recurso.

Estrutura do projeto na IDE

pom.xml (adapte para suas versões ou packages)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.eduardo.frohlich</groupId>
<artifactId>aws-ses</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aws-ses</name>
<description>AWS Simple Email Service application</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.12.712</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

application.properties (insira suas credenciais do seu email verificado dentro da AWS SES).

aws.region=<YOUR-REGION>
aws.accessKeyId=<YOUR-ACCESS-KEY>
aws.secretKey=<YOUR-SECRET-KEY>

Copiando o html do nosso template de emails pelo beefree

Clique em copy the html e copie o conteúdo para dentro de um arquivo html dentro da pasta src/main/resources/templates

Configurando variáveis dentro do HTML com o Thymeleaf

Use [[${var}]] para indicar que o texto do html se trata de uma variável.
Para mais informações sobre notações do Thyemeleaf, consulte aqui.

account-password.html (arquivo com o template HTML gerado pelo Beefree)

Disponível em github

Construindo nossa aplicação SpringBoot

Utilizando a base de uma a arquitetura limpa, criamos os seguintes packages com seus conteúdos:

Camada core: contém as entidades, regras de negócio centrais e casos de uso (define o que nosso app faz).

  • EmailRequest — DTO para disparos de email sem HTML.
package com.eduardo.frohlich.emailservice.core;

public record EmailRequest(String to, String subject, String body) {
}
  • HtmlEmailRequest — DTO para disparos de email com template HTML.
package com.eduardo.frohlich.emailservice.core;

public record HtmlEmailRequest(String subject, String to, String name, String templateName) {
public HtmlEmailRequest {
templateName = "account-password";
}
}
  • EmailSenderUseCase — caso de uso das entidades que será implementado dentro da camada application
package com.eduardo.frohlich.emailservice.core;

public interface EmailSenderUseCase {
void sendEmail(String to, String subject, String body);
void sendHtmlEmail(String to, String subject, String body);
}

Configurando Exceções

Cria-se o package exceptions dentro de core

package com.eduardo.frohlich.emailservice.core.exceptions;

public class EmailServiceException extends RuntimeException {
public EmailServiceException(String message, Throwable cause) {
super(message, cause);
}
}

Camada application: onde implementaremos o core da nossa aplicação.

  • EmailSenderService — serviço onde implementamos a interface do nosso caso de uso e chamamos o nosso interface adapter.
package com.eduardo.frohlich.emailservice.application;

import com.eduardo.frohlich.emailservice.adapters.EmailSenderGateway;
import com.eduardo.frohlich.emailservice.core.EmailSenderUseCase;
import com.eduardo.frohlich.emailservice.core.HtmlEmailRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;
import java.util.Map;
@Service
public class EmailSenderService implements EmailSenderUseCase {
private static final Logger LOGGER = LoggerFactory.getLogger(EmailSenderService.class);
@Autowired
private SpringTemplateEngine templateEngine;
private final EmailSenderGateway emailSenderGateway;
@Autowired
public EmailSenderService(EmailSenderGateway emailSenderGateway) {
this.emailSenderGateway = emailSenderGateway;
}
@Override
public void sendEmail(String to, String subject, String body) {
emailSenderGateway.sendEmail(to, subject, body);
}
@Override
public void sendHtmlEmail(String to, String subject, String body) {
emailSenderGateway.sendHtmlEmail(to, subject, body);
}
@Async
public void htmlSend(HtmlEmailRequest HTMLrequest, Map<String, Object> model) {
Context context = new Context();
context.setVariables(model);
String templateName = HTMLrequest.templateName();
String to = HTMLrequest.to();
String subject = HTMLrequest.subject();
String processedHtml = templateEngine.process(templateName, context);
LOGGER.debug("Processed HTML: {}", processedHtml);
sendHtmlEmail(to, subject, processedHtml);
}
}

Camada adapters: onde faremos contato direto com o provedor de serviços da aws por uma interface.

  • EmailSenderGateway — usaremos essa interface para implementá-la ao serviço de envio de email da AWS SES.
package com.eduardo.frohlich.emailservice.adapters;

public interface EmailSenderGateway {
void sendEmail(String to, String subject, String body);
void sendHtmlEmail(String to, String subject, String body);
}

Camada infra: contém a implementação das interfaces definidas nos adapters para o serviço AWS SES.

Separa-se em pacotes para caso você precise criar outra API de envio de emails. Cria-se o pacote ses dentro de infra.

  • AwsSesConfig — serviço da AmazonSimpleEmailService. Declaramos nossas credenciais da AWS que definimos no application.properties (você poderá usar outro método para armazenar suas credenciais de maneira segura).
package com.eduardo.frohlich.emailservice.infra.ses;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AwsSesConfig {
@Value("${aws.region}")
private String awsRegion;
@Value("${aws.accessKeyId}")
private String accessKey;
@Value("${aws.secretKey}")
private String secretKey;
@Bean
public AmazonSimpleEmailService amazonSimpleEmailService() {
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonSimpleEmailServiceClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(awsRegion)
.build();
}
}
  • SesEmailSender — implementa nossa interface adapter EmailSenderGateway para enviar emails ao usuário por meio da nossa conta configurada para disparos de email. Chamamos os metodos de sendEmail e sendHtmlEmail.
package com.eduardo.frohlich.emailservice.infra.ses;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.model.*;
import com.eduardo.frohlich.emailservice.adapters.EmailSenderGateway;
import com.eduardo.frohlich.emailservice.core.exceptions.EmailServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SesEmailSender implements EmailSenderGateway {
private static final Logger LOGGER = LoggerFactory.getLogger(SesEmailSender.class);
private final AmazonSimpleEmailService amazonSimpleEmailService;
@Autowired
public SesEmailSender(AmazonSimpleEmailService amazonSimpleEmailService) {
this.amazonSimpleEmailService = amazonSimpleEmailService;
}
@Override
public void sendEmail(String toEmail, String subject, String body) {
SendEmailRequest request = new SendEmailRequest()
.withSource("<YOUR-AWS-MAIL>.com")
.withDestination(new Destination().withToAddresses(toEmail))
.withMessage(new Message()
.withSubject(new Content(subject))
.withBody(new Body().withText(new Content(body)))
);
try {
amazonSimpleEmailService.sendEmail(request);
} catch (AmazonServiceException ex) {
throw new EmailServiceException("Email sending failed", ex);
}
}
@Override
public void sendHtmlEmail(String to, String subject, String body) {
SendEmailRequest request = new SendEmailRequest()
.withSource("<YOUR-AWS-MAIL>.com")
.withDestination(new Destination().withToAddresses(to))
.withMessage(new Message()
.withSubject(new Content(subject))
.withBody(new Body().withHtml(new Content(body)))
);
try {
amazonSimpleEmailService.sendEmail(request);
LOGGER.info("Email sent successfully to {}", to);
} catch (AmazonServiceException ex) {
LOGGER.error("Error sending HTML email", ex);
throw new EmailServiceException("Email sending failed", ex);
}
}
}

Camada config: configuração do Thymeleaf para renderização de templates de e-mail.

  • ThymeleafTemplateConfig — configuramos a pasta onde está nosso template HTML do email para que o Thymeleaf gere a página (por convenção os arquivos HTML ficam dentro da pasta templates dentro da resources do projeto)
package com.eduardo.frohlich.emailservice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import java.nio.charset.StandardCharsets;
@Configuration
public class ThymeleafTemplateConfig {
@Bean
public SpringTemplateEngine springTemplateEngine() {
SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.addTemplateResolver(emailTemplateResolver());
return springTemplateEngine;
}
public ClassLoaderTemplateResolver emailTemplateResolver() {
ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
emailTemplateResolver.setPrefix("/templates/");
emailTemplateResolver.setSuffix(".html");
emailTemplateResolver.setTemplateMode(TemplateMode.HTML);
emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
emailTemplateResolver.setCacheable(false);
return emailTemplateResolver;
}
}

Camada controllers: controladores que gerenciam as requisições HTTP.

  • EmailSenderController — requisições HTTP para envio de email padrão ou estilizado com HTML.
package com.eduardo.frohlich.emailservice.controllers;

import com.eduardo.frohlich.emailservice.application.EmailSenderService;
import com.eduardo.frohlich.emailservice.core.EmailRequest;
import com.eduardo.frohlich.emailservice.core.HtmlEmailRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/email")
public class EmailSenderController {
private final EmailSenderService emailSenderService;
@Autowired
public EmailSenderController(EmailSenderService emailSenderService) {
this.emailSenderService = emailSenderService;
}
@PostMapping
public ResponseEntity<String> sendEmail(@RequestBody EmailRequest emailRequest) {
try {
emailSenderService.sendEmail(emailRequest.to(), emailRequest.subject(), emailRequest.body());
return ResponseEntity.ok("Email sent successfully");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error sending email");
}
}
@PostMapping("/sendHtmlEmail")
public ResponseEntity<String> sendHtmlEmail(@RequestBody HtmlEmailRequest htmlEmailRequest) {
try {
Map<String, Object> model = new HashMap<>();
model.put("name", htmlEmailRequest.name());
model.put("password", "123456");
emailSenderService.htmlSend(htmlEmailRequest, model);
return ResponseEntity.ok("Email sent successfully");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error sending email: " + e.getMessage());
}
}
}

Testando com Postman

sendEmail: Testando email para o http://localhost:8080/api/email (sem HTML implementado)

{
"to": "<YOUR-AWS-MAIL>@gmail.com",
"subject": "Olá! Copie sua senha",
"body": "Sua senha é 123456"
}

Resultado no email

sendHtmlEmail: Testando email para o http://localhost:8080/api/email/sendEmailRequest. Configuramos no controller do método sendHtmlEmail para passar o parâmetro “name” que definimos na variável name dentro do account-password.html.

{
"to": "<YOUR-AWS-MAIL>@gmail.com",
"subject": "Olá! Copie sua senha",
"name": "Nome de Usuário"
}

Resultado no email

Muito obrigado por dedicar seu tempo para ler este artigo. Espero que as informações compartilhadas sejam úteis em seus projetos futuros. Agradeço qualquer feedback e estou à disposição para discutir mais sobre o tema. Até a próxima!

--

--

Eduardo Vinicius Frohlich

Enthusiastic and detail-oriented Systems Analyst in training, specializing in backend development with echnologies including Spring Boot, JSF, REST APIs, etc.