El módulo poplib implementa un cliente POP3 en la biblioteca estándar de Python. Permite consultar un buzón, listar mensajes, descargar contenido y marcar elementos para borrado. POP3 todavía aparece en cuentas antiguas, dispositivos e integraciones sencillas que retiran correos de un servidor.
La propia documentación de Python considera POP3 obsoleto y recomienda IMAP cuando está disponible. POP3 suele ofrecer una vista simple del buzón sin carpetas, búsquedas ricas, sincronización de flags ni acceso parcial confiable. Úsalo únicamente cuando el proveedor o sistema heredado lo exija.
Conexión segura con 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_("contraseña")
print(cliente.stat())
finally:
cliente.quit()
Usa POP3_SSL o STARTTLS antes de autenticar. POP3 simple puede exponer usuario, contraseña y contenido de mensajes. El contexto creado con ssl.create_default_context() verifica certificado y hostname.
No desactives la verificación para corregir certificados caducados o nombres incorrectos. Consulta ssl en Python.
STARTTLS en el puerto 110
Cuando un servidor usa upgrade TLS explícito, conecta sin autenticar, revisa capabilities y llama stls() con un contexto verificado.
cliente = poplib.POP3("pop.example.com", timeout=15)
cliente.stls(context=contexto)
cliente.user(USER)
cliente.pass_(PASSWORD)
STARTTLS debe ocurrir antes de USER y PASS. Rechaza la conexión si falta la capability esperada; nunca hagas downgrade silencioso a texto claro.
Credenciales
No fijes contraseñas en el código. Usa variables de entorno, un gestor de secretos o credenciales de aplicación emitidas por el proveedor. Muchos servicios modernos exigen OAuth y pueden rechazar contraseñas POP3 normales.
No actives set_debuglevel(2) en producción. El debug del protocolo puede revelar metadatos, identificadores, respuestas y detalles de autenticación.
Consultar capabilities
capacidades = cliente.capa()
for nombre, parametros in capacidades.items():
print(nombre, parametros)
Las capabilities pueden anunciar STLS, UIDL, TOP, UTF8 y mecanismos de autenticación. Las implementaciones POP3 varían mucho. Que un método exista en Python no garantiza que el servidor lo soporte correctamente.
Estado del buzón
cantidad, bytes_totales = cliente.stat()
print(cantidad, bytes_totales)
stat() devuelve cantidad y tamaño total informado. Trátalos como estimaciones y aplica límites propios. El buzón puede cambiar entre la consulta y la descarga.
Listar mensajes
respuesta, lineas, octetos = cliente.list()
mensajes = []
for linea in lineas:
numero_texto, tamano_texto = linea.split(maxsplit=1)
mensajes.append((int(numero_texto), int(tamano_texto)))
Valida cada línea porque el servidor remoto puede enviar datos inesperados. Ignora mensajes superiores al límite de la aplicación y restringe cuántos se procesan por ejecución.
Usa UIDL para identificar mensajes
Los números POP3 son posiciones temporales de la sesión. Usa uidl() para obtener identificadores del servidor y evitar procesar el mismo mensaje repetidamente.
respuesta, lineas, octetos = cliente.uidl()
uids = {}
for linea in lineas:
numero, uid = linea.split(maxsplit=1)
uids[int(numero)] = uid.decode("ascii", errors="strict")
Guarda los UIDs completados en base de datos o archivo transaccional. El identificador depende del servidor y no sustituye Message-ID; ambos pueden ayudar a deduplicar.
Descargar un mensaje completo
respuesta, lineas, octetos = cliente.retr(numero)
if octetos > LIMITE_MENSAJE:
raise ValueError("mensaje demasiado grande")
raw_email = b"\r\n".join(lineas) + b"\r\n"
retr() devuelve líneas sin el terminador final. Reconstruye correctamente el stream de bytes. La respuesta puede ocupar memoria, por lo que conviene inspeccionar primero el tamaño de LIST y limitar bytes totales por ejecución.
Parsear con el paquete email
from email import policy
from email.parser import BytesParser
mensaje = BytesParser(policy=policy.default).parsebytes(raw_email)
print(mensaje.get("Subject"))
print(mensaje.get("From"))
Asuntos, remitentes, cuerpos HTML y nombres de adjuntos son datos no confiables. Escapa headers al mostrarlos y sanitiza HTML si realmente lo renderizas.
TOP para headers y vista previa
top(numero, lineas) intenta descargar headers y algunas líneas del cuerpo. La documentación advierte que TOP está mal especificado y suele estar roto en servidores alternativos.
respuesta, lineas, octetos = cliente.top(numero, 0)
headers = b"\r\n".join(lineas) + b"\r\n\r\n"
Prueba TOP manualmente con cada proveedor antes de depender de él. Cuando sea inconsistente, usa RETR con límites o elige IMAP.
Extraer texto con límites
def partes_texto(mensaje, limite=1_000_000):
total = 0
for parte in mensaje.walk():
if parte.get_content_maintype() == "multipart":
continue
if parte.get_content_disposition() == "attachment":
continue
datos = parte.get_payload(decode=True) or b""
total += len(datos)
if total > limite:
raise ValueError("el contenido supera el límite")
yield parte.get_content_type(), datos
Decodifica con el charset declarado y fallback seguro. Consulta codecs en Python.
Adjuntos seguros
Nunca uses un filename MIME como ruta directa. Genera un identificador interno, aplica límites por archivo y totales, inspecciona el formato real y almacena fuera de directorios ejecutables.
Usa tempfile en Python y hashlib en Python.
El borrado se confirma con QUIT
dele(numero) marca un mensaje durante la sesión. En servidores conformes, el borrado se hace permanente cuando quit() termina correctamente.
cliente.dele(numero)
# otras comprobaciones
cliente.quit() # confirma cambios
Esto introduce riesgo: el código puede marcar mensajes incorrectos y confirmar todo al cerrar. Separa descarga de borrado, confirma UID y estado local y registra una decisión durable antes de llamar DELE.
Deshacer marcas con RSET
cliente.rset()
rset() elimina marcas de borrado de la sesión antes de confirmarlas. Úsalo cuando falle el procesamiento tras uno o varios DELE.
Desconexiones inesperadas
La mayoría de servidores cancela borrados pendientes si la conexión termina sin QUIT, pero la documentación menciona implementaciones históricas que incumplen esa regla. No uses el disconnect como rollback garantizado. Retrasa DELE hasta la fase final.
Pipeline de procesamiento seguro
Un flujo robusto puede seguir estos pasos:
- Conectar con TLS verificado.
- Listar UIDL y tamaños.
- Ignorar UIDs ya completados.
- Descargar como máximo N mensajes y M bytes.
- Parsear y validar.
- Persistir resultado y confirmar estado local.
- Solo entonces marcar opcionalmente para borrado.
- Ejecutar QUIT y registrar finalización.
Si falla la persistencia local, deja el mensaje intacto.
Modo UTF-8
utf8() solicita el modo RFC 6856 cuando el servidor lo soporta. Revisa capabilities y trata error_proto. Incluso con UTF-8 en POP3, las partes MIME pueden declarar muchos charsets.
Keep-alive y deadlines
noop() puede mantener activa la sesión, pero no sustituye un plazo total. El procesamiento largo debe limitar duración y reconectar de forma segura.
Tratamiento de errores
poplib.error_proto representa respuestas POP3. Los fallos de socket y TLS pueden llegar como OSError, TimeoutError o ssl.SSLError.
try:
cliente = poplib.POP3_SSL(HOST, context=contexto, timeout=15)
cliente.user(USER)
cliente.pass_(PASSWORD)
except poplib.error_proto as error:
raise RuntimeError("el servidor POP3 rechazó la operación") from error
except OSError as error:
raise RuntimeError("fallo de transporte POP3") from error
Nunca incluyas contraseña ni respuesta completa de autenticación en logs.
POP3 frente a IMAP
POP3 se centra en descargar desde un buzón simple. IMAP ofrece carpetas, búsqueda en servidor, UIDs con UIDVALIDITY, flags y sincronización. Para flujos que preservan estado, consulta imaplib en Python.
Pruebas recomendadas
Prueba certificado inválido, STARTTLS ausente, login rechazado, UIDL no soportado, TOP roto, mensaje grande, MIME malformado, adjunto peligroso, DELE seguido de RSET, fallo antes de QUIT, timeout, buzón vacío y deduplicación.
Errores comunes
Los fallos frecuentes son usar POP3 simple con contraseña, desactivar TLS, confiar en TOP, identificar solo por número, descargar todo el buzón, guardar todo en memoria, confiar en filenames, marcar borrado antes de persistir, confirmar DELE automáticamente al cerrar y registrar contenido sensible.
Conclusión
poplib ofrece acceso directo a POP3, pero el protocolo es limitado y antiguo. Usa POP3_SSL o STARTTLS con contexto verificado, identifica mediante UIDL, impone límites, parsea MIME como contenido no confiable y convierte el borrado en una acción final explícitamente autorizada.
Consulta la documentación oficial de poplib y el RFC 1939 de POP3. Cuando sea posible, prefiere IMAP para sincronización y control más predecibles.







