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

    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python em tela representando inspeção de módulos e pacotes
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    inspect.ispackage: identifique pacotes Python

    Aprenda inspect.ispackage no Python para identificar pacotes, explorar módulos e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 5 minutos
    05/10/2026