poplib no Python: leia e-mails com POP3

Publicado em: 22/08/2026
Tempo de leitura: 6 minutos
High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.

O módulo poplib implementa um cliente POP3 na biblioteca padrão do Python. Ele permite consultar uma caixa, listar mensagens, baixar conteúdo e marcar itens para exclusão. POP3 ainda aparece em contas antigas, dispositivos e integrações simples que precisam retirar e-mails de um servidor.

O próprio manual do Python considera POP3 obsolescente e recomenda IMAP quando disponível. POP3 oferece uma visão mais simples da caixa, normalmente sem pastas, busca rica, sincronização de flags ou acesso parcial confiável. Use-o apenas quando o provedor ou sistema legado exigir.

Conexão segura com POP3_SSL

import poplib
import ssl

contexto = ssl.create_default_context()

cliente = poplib.POP3_SSL(
    "pop.example.com",
    port=995,
    timeout=15,
    context=contexto,
)

try:
    cliente.user("usuario@example.com")
    cliente.pass_("senha")
    print(cliente.stat())
finally:
    cliente.quit()

Use POP3_SSL ou STARTTLS antes de autenticar. FTP e e-mail compartilham um erro comum: proteger apenas parte do fluxo. No POP3 simples, usuário, senha e mensagens podem trafegar sem criptografia.

O contexto criado por ssl.create_default_context() valida certificado e hostname. Não desative essa verificação para contornar certificados expirados ou nomes divergentes. Consulte ssl no Python.

STARTTLS na porta 110

Quando o servidor usa POP3 explícito com upgrade TLS, conecte sem autenticar, consulte capabilities e chame stls() com um contexto verificado.

cliente = poplib.POP3("pop.example.com", timeout=15)
cliente.stls(context=contexto)
cliente.user(USER)
cliente.pass_(PASSWORD)

STARTTLS deve ocorrer antes de USER e PASS. Recuse a conexão quando o servidor não oferecer a capacidade esperada; não faça downgrade silencioso para texto claro.

Credenciais

Não fixe senha no código-fonte. Use variáveis de ambiente, gerenciador de segredos ou credenciais de aplicativo. Muitos provedores modernos exigem OAuth e podem não aceitar POP3 por senha.

Não use set_debuglevel(2) em produção. O debug registra comandos e respostas e pode revelar metadados, identificadores ou autenticação.

Consultando capabilities

capacidades = cliente.capa()
for nome, parametros in capacidades.items():
    print(nome, parametros)

Capabilities podem indicar STLS, UIDL, TOP, UTF8 e mecanismos de autenticação. Implementações POP3 variam bastante; a presença de um método no Python não garante que o servidor o suporte corretamente.

Status da caixa

quantidade, bytes_totais = cliente.stat()
print(quantidade, bytes_totais)

stat() retorna quantidade e tamanho total informado. Use isso como estimativa e aplique limites próprios. Uma caixa pode crescer entre a consulta e o download.

Listando mensagens

resposta, linhas, octetos = cliente.list()

mensagens = []
for linha in linhas:
    numero_texto, tamanho_texto = linha.split(maxsplit=1)
    mensagens.append((int(numero_texto), int(tamanho_texto)))

Valide cada linha. O servidor é remoto e pode enviar respostas inesperadas. Não tente baixar mensagens acima do limite definido pela aplicação.

UIDL para identificar mensagens

Números POP3 são posições temporárias da sessão. Use uidl() para obter um identificador estável fornecido pelo servidor e evitar processar novamente o mesmo e-mail.

resposta, linhas, octetos = cliente.uidl()

uids = {}
for linha in linhas:
    numero, uid = linha.split(maxsplit=1)
    uids[int(numero)] = uid.decode("ascii", errors="strict")

Armazene os UIDs já concluídos em banco ou arquivo transacional. O identificador depende do servidor e não substitui Message-ID; os dois podem ser úteis para deduplicação.

Baixando uma mensagem

resposta, linhas, octetos = cliente.retr(numero)

if octetos > LIMITE_MENSAGEM:
    raise ValueError("mensagem grande demais")

raw_email = b"\r\n".join(linhas) + b"\r\n"

retr() retorna linhas sem o terminador final. Ao reconstruir, use CRLF. A resposta já pode ocupar memória; verifique tamanho informado antes quando possível e limite quantidade de mensagens por execução.

Parseando com o pacote email

from email import policy
from email.parser import BytesParser

mensagem = BytesParser(policy=policy.default).parsebytes(raw_email)

print(mensagem.get("Subject"))
print(mensagem.get("From"))

Assuntos, remetentes, corpos HTML e nomes de anexos são conteúdo não confiável. Faça escape ao exibir e sanitize HTML quando realmente permitir renderização.

TOP para headers e amostra

top(numero, linhas) tenta buscar headers e algumas linhas do corpo sem marcar a mensagem como lida. Porém a documentação alerta que TOP é mal especificado e frequentemente quebrado em servidores alternativos.

resposta, linhas, octetos = cliente.top(numero, 0)
headers = b"\r\n".join(linhas) + b"\r\n\r\n"

Teste manualmente contra cada servidor antes de depender de TOP. Quando a implementação é inconsistente, pode ser necessário usar RETR com limites ou escolher IMAP.

Extraindo texto com limite

def partes_texto(mensagem, limite=1_000_000):
    total = 0
    for parte in mensagem.walk():
        if parte.get_content_maintype() == "multipart":
            continue
        if parte.get_content_disposition() == "attachment":
            continue
        dados = parte.get_payload(decode=True) or b""
        total += len(dados)
        if total > limite:
            raise ValueError("conteúdo excede limite")
        yield parte.get_content_type(), dados

Use o charset declarado com fallback. Para estratégias de decoding, veja codecs no Python.

Adjuntos seguros

Nunca use o filename MIME como caminho direto. Gere um nome interno, limite bytes, inspecione o formato e salve primeiro em área temporária. Use tempfile no Python e hashlib no Python.

Exclusão é confirmada no QUIT

dele(numero) apenas marca a mensagem durante a sessão. Em servidores conformes, a exclusão é efetivada quando quit() termina corretamente.

cliente.dele(numero)
# outras validações
cliente.quit()  # confirma mudanças

Isso torna o fluxo perigoso: um código pode marcar mensagens erradas e confirmá-las ao fechar. Separe download de exclusão, confirme UID, hash ou Message-ID e registre a decisão antes de chamar DELE.

Desfazendo marcações com RSET

cliente.rset()

rset() remove marcações de exclusão da sessão, desde que ainda não tenham sido confirmadas. Use-o ao detectar falha antes do QUIT.

Fechamento inesperado

A maioria dos servidores cancela exclusões se a conexão cair sem QUIT, mas a documentação menciona implementações históricas que violam essa regra. Não dependa do disconnect como rollback. Prefira evitar DELE até o último estágio.

Pipeline seguro de processamento

Um fluxo robusto pode seguir estas etapas:

  1. Conectar com TLS validado.
  2. Listar UIDL e tamanhos.
  3. Ignorar UIDs já concluídos.
  4. Baixar no máximo N mensagens e M bytes.
  5. Parsear e validar.
  6. Persistir o resultado e confirmar transação.
  7. Somente então, opcionalmente, marcar para exclusão.
  8. Executar QUIT e registrar conclusão.

Se a persistência falhar, não marque o e-mail.

UTF-8

O método utf8() solicita o modo definido no RFC 6856 quando o servidor suporta. Teste capabilities e trate error_proto. Mesmo com UTF-8 no protocolo, mensagens MIME podem usar vários charsets.

Keep-alive

noop() mantém a sessão ativa, mas não substitui um deadline total. Processamentos demorados devem limitar o tempo e reconectar quando necessário.

Tratando erros

poplib.error_proto representa respostas de protocolo. Falhas de socket e TLS podem chegar como OSError, TimeoutError ou ssl.SSLError.

try:
    cliente = poplib.POP3_SSL(HOST, context=contexto, timeout=15)
    cliente.user(USER)
    cliente.pass_(PASSWORD)
except poplib.error_proto as erro:
    raise RuntimeError("servidor POP3 rejeitou a operação") from erro
except OSError as erro:
    raise RuntimeError("falha de transporte POP3") from erro

Não registre a senha nem a resposta completa de autenticação.

POP3 ou IMAP

POP3 é orientado a baixar mensagens de uma caixa simples. IMAP oferece pastas, busca no servidor, UIDs com UIDVALIDITY, flags e sincronização. Para automações que precisam preservar estado ou trabalhar com várias caixas, consulte imaplib no Python.

Testes recomendados

Teste certificado inválido, STARTTLS ausente, login rejeitado, UIDL não suportado, TOP quebrado, mensagem grande, MIME malformado, anexo perigoso, DELE seguido de RSET, falha antes de QUIT, timeout, caixa vazia e deduplicação.

Erros comuns

Os erros frequentes são usar POP3 simples com senha, desativar TLS, confiar em TOP, identificar apenas pelo número, baixar a caixa inteira, guardar tudo em memória, salvar filenames externos, marcar para exclusão antes de persistir, confirmar DELE automaticamente no QUIT e registrar conteúdo sensível.

Conclusão

poplib oferece acesso direto a servidores POP3, mas o protocolo é limitado e obsoleto. Use POP3_SSL ou STARTTLS com contexto verificado, identifique mensagens com UIDL, imponha limites, parseie MIME como conteúdo não confiável e mantenha exclusão como uma etapa final e explicitamente autorizada.

Consulte a documentação oficial de poplib e o RFC 1939 do POP3. Quando houver opção, prefira IMAP para sincronização e controle mais previsíveis.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    A close-up of a laptop on a table, displaying a book on test-driven software with Python, set in a comfortable environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    imaplib no Python: leia e-mails com IMAP

    Aprenda imaplib no Python para acessar caixas IMAP com TLS, buscar por UID, ler mensagens sem marcá-las, usar flags e

    Ler mais

    Tempo de leitura: 5 minutos
    22/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ftplib no Python: FTP e FTPS seguros

    Aprenda ftplib no Python para listar, baixar e enviar arquivos por FTP ou FTPS com TLS, timeouts, limites, retomada e

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Rack de servidores representando um endpoint criado com xmlrpc.server no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.server: crie servidores XML-RPC

    Aprenda xmlrpc.server no Python para criar servidores XML-RPC, registrar funções, limitar métodos e caminhos e evitar exposição insegura.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Cabos conectados a servidor representando chamadas remotas com xmlrpc.client no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.client no Python: chamadas RPC

    Aprenda xmlrpc.client no Python para chamar serviços XML-RPC, tratar Fault e ProtocolError, usar TLS, tipos compatíveis e limites seguros.

    Ler mais

    Tempo de leitura: 4 minutos
    21/08/2026
    Código de erro sobre dados binários representando falhas tratadas com urllib.error no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    urllib.error no Python: trate falhas HTTP

    Aprenda urllib.error no Python para tratar URLError, HTTPError, downloads incompletos, retries seletivos e diagnósticos de rede mais claros.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Teclas com a palavra HTML representando entidades HTML no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    html.entities: converta entidades HTML

    Aprenda html.entities no Python para consultar entidades HTML, converter nomes e code points e evitar confundir decoding com sanitização.

    Ler mais

    Tempo de leitura: 7 minutos
    21/08/2026