Dada a grande necessidade de manter usuários de um sistema informados periodicamente com atualizações sobre relatórios, avisos importantes (estoque, notas fiscais, compras e etc.), surge então um propósito para envio de e-mails automatizados para estes tipos de usuários. Imagine, por exemplo, um sistema de gerenciamento de estoque que tem por finalidade gerir todo o estoque de uma empresa, avisando a determinados usuários (por e-mail ou SMS) quais produtos devem ser comprados afim de atender a demanda solicitada.

Vamos muito além disso, atualmente já existem sistemas que realizam todo o pedido de compra quando um estoque atinge o mínimo configurado, enviando um e-mail direto ao fornecedor solicitando a compra de produtos.

Enfim, nosso artigo não vai tratar de estoque, nota fiscais ou quaisquer outras regras específicas de negócio. Iremos criar um módulo eficaz para envio de e-mail em Java.

Antes de iniciarmos vamos apresentar as técnicas que utilizaremos para criar nosso módulo.

Criação de Helpers

Um Helper, como o próprio nome já sugere, é uma classe que irá nos auxiliar nas tarefas comumente utilizadas durante todo o sistema, como por exemplo: Formatação de Datas, Formatação de Números, Validação de valores (CPF, Cartão, CNPJ e etc.) e entre outros.

Confira nossos cursos de java ou se preferir pode acompanhar nossa formação Java.

Em nosso artigo faremos o uso de dois Helpers:

  1. O primeiro Helper será chamado de ParametroSistemaHelper que será responsável por armazenar informações relativas a configurações do sistema, que em nosso caso, serão configurações relativas ao nosso módulo de e-mail, tais como: porta, e-mail de autenticação, usuário, senha, servidor SMTP e etc.
  2. O segundo Helper é o foco principal do nosso artigo, será o EmailHelper. Este terá como principal objetivo prover todos os métodos e funcionalidades necessárias para envio de e-mail de forma simples, rápida e ágil. Sem que precisemos ficar codificando parâmetros enormes e difíceis para enviar um simples e-mail, toda a complexidade do envio ficará acoplada nessa classe. Será como um “caixa preta” para quem está enviando um e-mail através do “EmailHelper”.
  • Log para monitoramento: Faremos uso do log4j para monitorar os passos do envio de nosso e-mail. Não iremos explicar neste artigo como configurar o log4j, apenas estamos alertando que utilizaremos o mesmo para criar logs da nossa aplicação e realizar posteriores auditorias caso necessário. Você pode optar por ignorar tais recursos ou mesmo utilizar outro de sua preferência.
  • SpringFramework para injeção de dependências: Como Framework para injeção de dependências utilizaremos o SpringFramework, que nos ajudará a manipular nossos Helpers e classes responsáveis por consultas no banco de dados.
  • JPA e Hibernate: Utilizamos como framework para persistência de dados o JPA juntamente com o Hibernate. Este nos ajudará os parâmetros de sistema onde iremos armazenar os dados para conexão do e-mail.

Estruturando nosso Módulo

Dada as apresentações anteriores, onde mostramos as principais tecnologias e recursos utilizados em nosso artigo, iremos começar a estruturas nosso módulo com a construção dos “Beans”, que nada mais são do que nossas classes Java que irão relacionar-se com nossa tabela no banco de dados e serão utilizadas em toda a aplicação.

Nesse módulo utilizaremos apenas uma classe chamada “ParametroSistema”, onde armazenaremos todas as informações necessárias para conexão com o servidor de e-mail, conforme a Listagem 1.


import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;


@Entity
@NamedQueries(value = {
           @NamedQuery(name = "ParametroSistema.findByCodigo", query = 
           "SELECT c FROM ParametroSistema c WHERE c.codigo = :codigo"),
           @NamedQuery(name = "ParametroSistema.findAll", query = 
           "SELECT c FROM ParametroSistema c") })
@Table(name = "parametro_sistema")
public class ParametroSistema {
  

     public static final String FIND_BY_CODIGO = "ParametroSistema.findByCodigo";
     public static final String FIND_ALL = "ParametroSistema.findAll";
     
     @Id
     @GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
     private Integer id;

     public Integer getId() {
           return id;
     }

     public void setId(Integer id) {
           this.id = id;
     }

     @Override
     public int hashCode() {
           final int prime = 31;
           int result = 1;
           result = prime * result + ((id == null) ? 0 : id.hashCode());
           return result;
     }

     @Override
     public boolean equals(Object obj) {
           return (obj instanceof AbstractBean) && (this.id != null) ? id
               .equals(((AbstractBean) obj).getId()) : (obj == this);
     }

     @Column(length = 255, nullable = false)
     private String codigo;
     
     @Column(length = 255)
     private String descricao;

     @Column(length = 255, nullable = false)
     private String descricao_campo1;

     @Column(length = 255, nullable = false)
     private String valor_campo1;

     @Column(length = 255)
     private String descricao_campo2;

     @Column(length = 255)
     private String valor_campo2;

     @Column(length = 255)
     private String descricao_campo3;

     @Column(length = 255)
     private String valor_campo3;

     public String getCodigo() {
           return codigo;
     }

     public void setCodigo(String codigo) {
           this.codigo = codigo.toUpperCase().trim();
     }      

     public String getDescricao() {
           return descricao;
     }

     public void setDescricao(String descricao) {
           this.descricao = descricao;
     }

     public String getDescricao_campo1() {
           return descricao_campo1;
     }

     public void setDescricao_campo1(String descricao_campo1) {
           this.descricao_campo1 = descricao_campo1;
     }

     public String getValor_campo1() {
           return valor_campo1;
     }

     public void setValor_campo1(String valor_campo1) {
           this.valor_campo1 = valor_campo1;
     }

     public String getDescricao_campo2() {
           return descricao_campo2;
     }

     public void setDescricao_campo2(String descricao_campo2) {
           this.descricao_campo2 = descricao_campo2;
     }

     public String getValor_campo2() {
           return valor_campo2;
     }

     public void setValor_campo2(String valor_campo2) {
           this.valor_campo2 = valor_campo2;
     }

     public String getDescricao_campo3() {
           return descricao_campo3;
     }

     public void setDescricao_campo3(String descricao_campo3) {
           this.descricao_campo3 = descricao_campo3;
     }

     public String getValor_campo3() {
           return valor_campo3;
     }

     public void setValor_campo3(String valor_campo3) {
           this.valor_campo3 = valor_campo3;
     }
}
Listagem 1. Classe ParametroSistema

No classe acima, o campo CODIGO nos ajudará a achar este parâmetro no banco de dados, sem precisar guardar o ID ou algo mais complexo e difícil de lembrar, além disso temos o valor do campo com a sua descrição, podendo contem até três valores no mesmo parâmetro.

Ex: Codigo = 'PORTA_CONEXAO', Valor Campo 1 = 451, Descricao Campo 1 = Porta de Conexão no Servidor SMTP

Então poderíamos buscar qual a porta de conexão no servidor SMTP apenas sabendo que o código do parâmetro é PORTA_CONEXAO. Veremos mais a frente como fazer isso, através do Helper.

Criando ParametroSistemaHelper

Iremos pular etapas de configuração de conexões com o banco de dados, método de busca, execução de queries ou quaisquer outros recursos que não estejam nesse artigo. É importante apenas saber que temo um classe responsável por realizar todo e qualquer tipo de operação com o banco de dados, deixando nosso módulo mais simples e elegante (Listagem 2).


import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
@Component(value = "parametroSistemaHelper")
public class ParametroSistemaHelper {
 
       @Autowired
       private BasicDAO dao;
 
       public List<ParametroSistema> getParametroByCodigo(String codigo) {
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("codigo", codigo);

         List<ParametroSistema> parametros = (List<ParametroSistema>) dao
             .findByQuery(
                          "SELECT c FROM ParametroSistema c WHERE c.codigo = :codigo",
                          map);

         if (parametros.size() > 0)
                return parametros;
         else
                return null;
       }
 
       public ParametroSistema getUniqueParametroByCodigo(String codigo) {
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("codigo", codigo);

         List<ParametroSistema> parametros = (List<ParametroSistema>) dao
             .findByQuery(
                    "SELECT c FROM ParametroSistema c WHERE c.codigo = :codigo",
                    map);

         if (parametros.size() > 0)
                return parametros.get(0);
         else
                return null;
       }
 
       public BasicDAO getDao() {
             return dao;
       }
 
       public void setDao(BasicDAO dao) {
             this.dao = dao;
       }
}
Listagem 2. Classe ParametroSistemaHelper

Como nossa classe “DAO” que realiza as operações com o banco de dados é injetada e gerenciada pelo Spring, então precisamos deixar que o Spring gerencie também nosso Helper, por isso anotamos o mesmo com a anotação @Component.

Nosso Helper é bem simples mas muito eficaz, ele é composto por 2 métodos básicos que são explicados abaixo:

  1. public List getParametroByCodigo(String codigo): Este método nos retorna todos os parâmetros que contém determinado CODIGO, podendo ser 1 ou mais.
  2. public ParametroSistema getUniqueParametroByCodigo(String codigo): Sabendo que 1 CODIGO é único no sistema, então podemos usar esse método que já nos retorna apenas 1 Parâmetro de Sistema.

Nosso Helper criado nessa seção, será utilizado em nosso EmailHelper e você entenderá, se ainda ficou alguma dúvida, como utilizar tal recurso.

Helper Principal – EmailHelper

Chegamos ao ponto principal do nosso artigo. A criação do EmailHelper que será utilizado em qualquer parte do sistema.

Como trata-se de nossa classe principal, iremos detalhar a mesma de forma distinta das mostradas acima, mostrando primeiro a estrutura desta sem nenhum conteúdo nos métodos. Veja a Listagem 3.


@Component(value = "emailHelper")
public class EmailHelper {
       
       private static Logger logger = Logger.getLogger(EmailHelper.class);
       
       @Autowired
       private ParametroSistemaHelper parametroSistemaHelper;
       
       public void enviarEmail(String email, String assunto, String msg)
       
       private Session criarSessionMail()
 
      public ParametroSistemaHelper getParametroSistemaHelper() {
             return parametroSistemaHelper;
       }
 
       public void setParametroSistemaHelper(
                    ParametroSistemaHelper parametroSistemaHelper) {
             this.parametroSistemaHelper = parametroSistemaHelper;
       } 
}
Listagem 3. Estrutura da Classe EmailHelper

Deixamos a cargo do Spring, novamente, para gerenciar a instância da nossa classe EmailHelper, através da anotação @Component. Criamos um objeto “logger” para utilizar dentro dos métodos que serão explicados mais a frente em detalhes.

O @Autowired na propriedade parametroSistemaHelper nos garante que o Spring injete a instancia atual da nossa classe ParametroSistemaHelper dentro do nosso EmailHelper, assim poderemos utilizar os métodos deste sem correr o risco de lançar um NullPointerException.

Os métodos get e set são apenas para que o Spring consiga realizar a injeção de dependência de forma correta.

Vamos agora explicar o uso de cada método, começando pelo de mais baixa hierarquia que será utilizado apenas dentro do próprio Helper, o criarSessionMail.

Método criarSessionMail()

Antes de iniciar a explicação desses métodos, você precisa definir alguns parâmetros de sistema para que nosso módulo conecte-se de forma correta ao servidor SMTP. Para isso utilizaremos o já conhecido ParametroSistema, que criamos nas seções anteriores.

O código do nosso parâmetro será SMTP_CONFIG, ou seja, toda vez que buscarmos a lista de parâmetros que possuem o código SMTP_CONFIG, teremos acesso a todas as configurações necessárias para conexão correta no Servidor SMTP. Segue na Listagem 4 as configurações que precisaremos.


SMTP_CONFIG|smtp.gmail.com|mail.smtp.host
SMTP_CONFIG|465|mail.smtp.socketFactory.port
SMTP_CONFIG|javax.net.ssl.SSLSocketFactory|mail.smtp.socketFactory.class
SMTP_CONFIG|true|mail.smtp.auth
SMTP_CONFIG|465|mail.smtp.port
Listagem 4. Configurações do SMTP_CONFIG

Na Listagem 4 temos lista de configurações que devem ser adicionadas ao ParametroSistema no banco de dados, sendo a coluna 1 o campo CODIGO, a coluna 2 o campo valor_campo1 e a coluna 3 o campo descricao_campo1.

Estamos usando configurações para conexão utilizando o SMTP do Gmail, mas você pode optar por configurar um outra de sua escolha.

Após a configuração da Listagem 4, podemos iniciar a demonstração do método criarSessionMail que fará uso destas. Veja a Listagem 5.


private Session criarSessionMail() {
 
 /*
  * Criação do objeto Properties requerido pelo método 'getDefaultInstance' 
  do objeto Session.
  * O nosso objeto 'props' irá armazenar todos os parâmetros de conexão ao 
  servidor SMTP. Estes parâmetros
  * serão recuperados diretamento do banco de dados, como explicamos nas 
  seções anteriores.
  * */
 Properties props = new Properties();

 /*
  * São retornados todos os parâmetros do banco de dados e inseridos no 
  nosso objeto 'props'.
  * Como se fizemos o código abaixo de forma manual, com todos os 
  parâmetros necessários:
  *
  * props.put('mail.smtp.host','smtp.gmail.com');
  * */
 List<ParametroSistema> params = parametroSistemaHelper
 .getParametroByCodigo("SMTP_CONFIG");
 for (ParametroSistema param : params) {
     props.put(param.getDescricao_campo1(), param.getValor_campo1());
 }

 /*
  * Aqui é onde toda a complexidade se concentra e a mágica acontece.
  * O objeto Session tem um método chamado 'getDefaultInstance()' que 
  retorna a instancia padrão do Session
  * recebendo como parâmetro 2 argumentos:
  *  1 - O primeiro argumento é um objeto Properties que contém a 
  lista de propriedades para conexão no servidor SMTP
  *  2 - O segundo argumento é um objeto Authenticator que contém a 
  autenticação padrão que será utilizada para conexão no servidor SMTP.
  *  
  * Criamos um objeto Authenticator com a implementação do método 
  'getPasswordAuthentication()' retornando um objeto PasswordAuthentication.
  * Isto é, toda vez que for chamado o método getPasswordAuthentication 
  do objeto Authenticator, ele irá criar um novo objeto
  * PasswordAuthentication com os parâmetros de login e senha de acesso 
  ao servidor SMTP.       *  
  * */
 Session session = Session.getDefaultInstance(props,
   new javax.mail.Authenticator() {

     protected PasswordAuthentication getPasswordAuthentication() {
         ParametroSistema paramAuthMail = parametroSistemaHelper
                 .getUniqueParametroByCodigo("EMAIL_AUTENTICACAO");
         return new PasswordAuthentication(paramAuthMail.getValor_campo1(),
                 paramAuthMail.getValor_campo2());
     }
   });

 //Criada nossa Session, ativamos o modo de DEBUG para poder ver o que está 
 sendo feito em detalhes no envio do email
 session.setDebug(true);

 //Retornamos a session que será usada para conexão SMTP
 return session;
}
Listagem 5. Método criarSessionMail comentado

Optamos por colocar comentários em todo o método acima para melhorar a explicação do mesmo, visto que ele é um pouco mais complexo do que os outros. Perceba também que temos um parâmetro de código EMAIL_AUTENTICACAO que nada mais é do que um outro parâmetro de sistema que contém o 'e-mail' e senha de conexão ao servidor SMTP.

Onde, valor 1 = e-mail de conexão e valor 2 = senha de conexão.

Com nosso objeto Session criado, vamos iniciar o método responsável por enviar o e-mail.

Método enviarEmail

Enfim chegamos ao “coração” do nosso módulo de envio de e-mails. Onde o método enviarEmail() irá realizar se encarregar de trabalhar com todos os recursos necessários para envio de e-mail de forma adequada. Este irá chamar o método criarSessionMail() explicado na seção anterior. Veja a Listagem 6.


/*
 * Devem ser passados 3 argumentos para esse método:
 * 1 - email: Email para onde será enviada a mensagem
 * 2 - assunto: Qual assunto do email
 * 3 - msg: Mensagem completa do email com a formatação desejada (pode conter HTML)
 * */
public void enviarEmail(String email, String assunto, String msg) 
throws AddressException,
       MessagingException {
   
   /*
    * Parametrizamos no sistema o Email do Remetente, mas você pode 
    ficar a vontade para fixar um email direto no código.
    * */
   String remetente = parametroSistemaHelper
   .getUniqueParametroByCodigo("EMAIL_REMETENTE")
           .getValor_campo1();
   
   //Trabalhamos usando o objeto 'logger' para identificar que o 
   nosso método está fazendo
   logger.info("__________________________________________________");
   logger.info("Enviando email DE: " + remetente + " PARA: " + email);
   logger.info("Assunto: " + assunto);

   /*
    * Chamamos o método 'criarSessionMail()' que retrona um objeto 
    Session para criarmos um outro objeto do tipo Message
    * */
   Message message = new MimeMessage(criarSessionMail());
   
   //Configuramos o remetente do email
   message.setFrom(new InternetAddress(remetente));

   //Configuramos o destinatario do email, que podem ser 1 ou mais
   Address[] toUser = InternetAddress.parse(email.trim().toLowerCase());
   
   //Adicionamos os destinatarios ao objeto 'message'
   message.setRecipients(Message.RecipientType.TO, toUser);
   
   //adicionamos o assunto da mensagem
   message.setSubject(assunto);
   
   //configuramos que a mensagem poderá conter HTML
   message.setContent(msg, "text/html");
   
   //envia a mensagem
   Transport.send(message);

   logger.info("Email enviado com sucesso !");
   logger.info("__________________________________________________");

}
Listagem 6. Método enviarEmail comentado

Agora temos nosso módulo implementando e criado. Lembre-se que para utilização da nossa classe EmailHelper é necessário a injeção da mesma via Spring no local que você deseja utilizá-la.