Leitura de e-mails com JavaMail
Tags: java, tutorial, spring
Published: 2021-11-30 19:02:51

Surgiu aqui na empresa a necessidade de automatizar a leitura da caixa de entrada, de tempos em tempos, de um endereço XPTO e que apenas os novos e-mails fossem processados.

O foco deste artigo é mostrar como eu fiz a leitura da caixa de entrada usando JavaMail. O projeto completo está disponível no meu [GitHub](https://github.com/leilasoliveira/email-verifier) no meu GitHub e explicarei em artigos futuros as outras tecnologias envolvidas no projeto.

## Estrutura do projeto final

![Estrutura de pacotes do projeto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nemrv2uy62ohgu8vmvtp.png)
 
Antes de irmos direto ao assunto deste artigo, queria explicar como ficou o projeto final.

O projeto final ficou com a estrutura de pacotes acima, mas não comecei o projeto assim não! Primeiro, criei uma classe de serviço para testar a funcionalidade e só depois de pronto e minimamente testado, fiz a organização de código (tentando colocar em prática os ensinamentos do Uncle Bob).

Considerei como um Util a classe que faz a leitura dos e-mails. Criei um service para incluir a annotation de agendamento do Spring. No pacote `com.example.emailverifier.model.vo` coloquei um Bean para retornar objetos do tipo EmailVO com o assunto, corpo do e-mail e endereço de quem enviou o e-mail. No pacote `com.example.emailverifier.configuration`, criei uma classe que representa as properties que o Spring lê ao iniciar a aplicação. Essas properties configurei como variáveis de ambiente no Intellij e são o usuário e senha de acesso à caixa de entrada que deve ser lida.

### Dependências

Abaixo, vão algumas dependências que incluí para a leitura de mensagens.

``` gradle
// JavaMail
implementation group: 'javax.mail', name: 'mail', version: '1.4.1'

// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

```

### Código fonte

No meu caso específico, trabalhei com o protocolo IMAP que é responsável pelo recebimento dos e-mails. Usei ele por causa do requisito de ler somente e-mails novos, então teria que marcar quem foi processado como lido. POP3 não dispõe desta funcionalidade. Tem um artigo interessante que explica a diferença entre os protocolos IMAP, POP3 e SMTP [aqui](https://king.host/blog/2018/02/o-que-e-smtp-imap-e-pop-3-e-qual-a-diferenca/).

#### Classe EmailVerifierUtil.java

No pacote `com.example.emailverifier.utils`, criei a classe `EmailVerifierUtils.java` que implementa a leitura da caixa de entrada a partir do método público `getNewMessages()`.

Este método possui 3 linhas importantes:

``` java
connectEmail();
inbox = getFolder(FOLDER_INBOX);
List<EmailVO> emailVOS = readMessagesFromFolder(inbox);
```

A primeira linha, chama o método `connectEmail` que faz exatamente isso: obtém a instância padrão de uma sessão de e-mail a partir de algumas propriedades (que no meu caso, não tem); cria uma `Store` a partir da sessão, de acordo com o protocolo IMAP e; finalmente faz a conexão com essa Store passando as credenciais, host e porta.

``` java
private void connectEmail() throws MessagingException {
        Session emailSession = Session.getDefaultInstance(new Properties());

        imapStore = (IMAPStore) emailSession.getStore(IMAP_PROTOCOL);
        imapStore.connect(HOST, PORT, emailCredentials.getUsername(), emailCredentials.getPassword());
    }
```

O método `getFolder` recebe o nome da pasta que queremos ler e usa a instância Store criada anteriormente para obter uma instância de `Folder` e abrir no modo desejado: `READ_WRITE` ou  `READ_ONLY`.

``` java
private Folder getFolder(String folderName) throws MessagingException {
        Folder inbox = imapStore.getFolder(folderName);

        if (inbox != null) inbox.open(Folder.READ_WRITE);

        return inbox;
    }
```

Com e-mail conetado e pasta prontinha para ser lida, vamos para o método `readMessagesFromFolder` ler as mensagens. Passamos a instância de `Folder`, checamos se ela existe e depois obtemos as mensagens não lidas por meio do método `getUnseenMessages` que retorna um array de objetos Message. Após isso, percorremos este array e criamos objetos do tipo EmailVO para retornar ao service bonitinho: com o endereço remetente, assunto e conteúdo do e-mail. É neste ponto que adicionamos uma flag à mensagem para que ela seja considerada lida.

``` java
private List<EmailVO> readMessagesFromFolder(Folder folder) throws MessagingException, IOException {
        if (folder == null) {
            logger.info("No folder found.");
            return null;
        }

        Message[] messages = getUnseenMessages(folder);

        if (messages.length == 0) logger.info("No messages found.");

        List<EmailVO> emails = new ArrayList<>();
        for (Message message : messages) {
            Address[] from = message.getFrom();

            emails.add(EmailVO.builder()
                    .from(((InternetAddress) from[0]).getAddress())
                    .subject(message.getSubject())
                    .content(message.getContent().toString())
                    .build());

            message.setFlag(Flags.Flag.SEEN, true);
        }

        return emails;
    }
```

No `finally` do método `getNewMessages`, encerramos as conexões com a pasta Inbox e com a caixa de entrada.

**ATENÇÃO!** Para executar o projeto, precisamos configurar as variáveis de ambiente `email.username` e `email.password` com as credenciais do e-mail.

Rodando o projeto via Intellij, temos sucesso! :D

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.1)

2021-12-01 18:11:12.869  INFO 87412 --- [           main] c.e.e.EmailVerifierApplication           : Starting EmailVerifierApplication using Java 15.0.2 on leila-note with PID 87412 (/home/leila/projetos/pocs/email-verifier/build/classes/java/main started by leila in /home/leila/projetos/pocs/email-verifier)
2021-12-01 18:11:12.870  INFO 87412 --- [           main] c.e.e.EmailVerifierApplication           : No active profile set, falling back to default profiles: default
2021-12-01 18:11:13.296  INFO 87412 --- [           main] c.e.e.EmailVerifierApplication           : Started EmailVerifierApplication in 0.747 seconds (JVM running for 0.995)
2021-12-01 18:11:30.001  INFO 87412 --- [   scheduling-1] c.e.e.utils.EmailVerifierUtil            : Verifying new messages inbox...
2021-12-01 18:11:33.341  INFO 87412 --- [   scheduling-1] c.e.e.utils.EmailVerifierUtil            : No messages found.
2021-12-01 18:11:33.341  INFO 87412 --- [   scheduling-1] c.e.e.utils.EmailVerifierUtil            : Success on verifying new messages inbox!
2021-12-01 18:11:33.628  INFO 87412 --- [   scheduling-1] c.e.emailverifier.service.EmailService   : Found 0 new message(s)!
2021-12-01 18:12:00.000  INFO 87412 --- [   scheduling-1] c.e.e.utils.EmailVerifierUtil            : Verifying new messages inbox...
2021-12-01 18:12:01.930  INFO 87412 --- [   scheduling-1] c.e.e.utils.EmailVerifierUtil            : Success on verifying new messages inbox!
2021-12-01 18:12:02.213  INFO 87412 --- [   scheduling-1] c.e.emailverifier.service.EmailService   : Found 1 new message(s)!
```

Bem tranquilo, né? Só que não! XD
Percorri alguns sites, tutoriais, posts do Stackoverflow para chegar neste resultado que FUNCIONA.

Portanto, caso você também precise fazer algo parecido com isso, **SALVE** este artigo, fique à vontade para copiar, modificar e melhorar o código aqui apresentado.

Como falei no início do artigo, farei mais alguns explicando as tecnologias que utilizei no projeto, tais como o agendamento de tarefas com Spring Boot e Junit com Mockito para os testes. 

Curtiu?
O que poderia melhorar?
Tem algum termo/assunto que deseja ler por aqui? Me conta!

Até a próxima! :)