imaplib en Python: lee correos con IMAP

Publicado el: 22/08/2026
Tempo de leitura: 6 minutos
A developer typing code on a laptop with a Python book beside in an office.

El módulo imaplib implementa un cliente IMAP en la biblioteca estándar de Python. Permite listar buzones, buscar mensajes, obtener headers y cuerpos, copiar mensajes, cambiar flags y seguir nuevas notificaciones. Es útil para automatizaciones de soporte, procesamiento de adjuntos, archivado e integraciones corporativas.

IMAP trabaja directamente con un buzón real. Un comando incorrecto puede marcar mensajes como leídos, añadir \Deleted o eliminarlos permanentemente después de EXPUNGE. Empieza con una cuenta de prueba aislada, selecciona buzones en modo solo lectura y usa UIDs estables en lugar de números de secuencia cambiantes.

Conexión segura con IMAP4_SSL

import imaplib
import ssl

contexto = ssl.create_default_context()

with imaplib.IMAP4_SSL(
    "imap.example.com",
    port=993,
    ssl_context=contexto,
    timeout=15,
) as cliente:
    cliente.login("usuario@example.com", "contraseña")
    print(cliente.noop())

La documentación de Python indica que el contexto SSL interno por defecto puede cifrar sin verificar necesariamente certificado y hostname. Pasa explícitamente un contexto creado con ssl.create_default_context(). No envíes una contraseña por IMAP simple en el puerto 143 antes de STARTTLS.

Para autoridades certificadoras privadas y versiones mínimas, consulta ssl en Python.

Credenciales y autenticación

No fijes contraseñas en el código. Carga credenciales desde variables de entorno, un gestor de secretos o un flujo OAuth cuando el proveedor lo exija. El método authenticate() soporta mecanismos SASL anunciados por el servidor.

Muchos proveedores desactivan el login tradicional por contraseña. Inspecciona capabilities y sigue la documentación del servicio. Nunca imprimas tokens, contraseñas, retos de autenticación ni transcripciones completas del protocolo.

Comprobar capabilities

print(cliente.capabilities)

Las capabilities pueden incluir IMAP4REV1, IDLE, UIDPLUS, MOVE o mecanismos de autenticación. No supongas que todos los servidores implementan las mismas extensiones. Añade fallback explícito o rechaza la operación si falta una capacidad obligatoria.

Listar buzones

estado, lineas = cliente.list()
if estado != "OK":
    raise RuntimeError("no fue posible listar buzones")

for linea in lineas or []:
    print(linea.decode("utf-8", errors="replace"))

Los nombres pueden usar UTF-7 modificado o separadores específicos del proveedor. Evita interpretar todas las respuestas LIST con un split() ingenuo. Una biblioteca IMAP de alto nivel puede ser mejor cuando necesitas compatibilidad amplia con nombres internacionales.

Seleccionar INBOX en modo solo lectura

estado, datos = cliente.select("INBOX", readonly=True)
if estado != "OK":
    raise RuntimeError("fallo al abrir INBOX")

cantidad = int(datos[0])
print("mensajes:", cantidad)

readonly=True reduce la posibilidad de cambiar flags o borrar contenido. Abre el buzón deliberadamente en modo escritura únicamente cuando la operación de negocio realmente necesite mutación.

Usa UIDs en lugar de números de secuencia

Los números de mensaje cambian cuando cambia el buzón, especialmente después de EXPUNGE. Los UIDs son más estables dentro del mismo buzón.

estado, datos = cliente.uid("search", None, "ALL")
if estado != "OK":
    raise RuntimeError("falló la búsqueda")

uids = datos[0].split()
print(uids[-10:])

Los UIDs no son identificadores globales eternos. Si el buzón se recrea, cambia su UIDVALIDITY. Una base de sincronización debe guardar buzón, UIDVALIDITY y UID juntos.

Buscar mensajes

estado, datos = cliente.uid(
    "search",
    None,
    "UNSEEN",
    "SINCE",
    "01-Jul-2026",
)

Los criterios son interpretados por el servidor. Las fechas IMAP no incluyen hora. Búsquedas por remitente, asunto o texto también implican quoting y charset. No concatenes entrada arbitraria del usuario en una expresión de búsqueda cruda sin validación.

Leer headers sin marcar como leído

estado, datos = cliente.uid(
    "fetch",
    uid,
    "(BODY.PEEK[HEADER.FIELDS (FROM TO SUBJECT DATE MESSAGE-ID)])",
)

BODY.PEEK solicita datos sin añadir \Seen en servidores compatibles. Obtener BODY[] puede marcar el mensaje como leído. Prueba el comportamiento con el proveedor real y conserva la selección read-only en flujos pasivos.

Interpretar correctamente FETCH

Una respuesta FETCH puede incluir datos adicionales o no solicitados. La documentación advierte que no debes suponer que los bytes siempre están en data[0][1]. Recorre tuplas y verifica el tipo literal.

def literales_fetch(elementos):
    for elemento in elementos or []:
        if isinstance(elemento, tuple) and len(elemento) == 2:
            metadatos, literal = elemento
            if isinstance(literal, bytes):
                yield metadatos, literal

Solicita solo las secciones necesarias e impone tamaños máximos. Para mensajes muy grandes, obtén primero headers y metadatos y después partes seleccionadas o bloques acotados.

Parsear un mensaje

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"))

Headers y cuerpos son datos no confiables. No renderices HTML de correo sin sanitización. Los nombres de adjuntos nunca deben convertirse directamente en rutas locales.

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
        contenido = parte.get_payload(decode=True) or b""
        total += len(contenido)
        if total > limite:
            raise ValueError("el mensaje supera el límite")
        yield parte.get_content_type(), contenido

Decodifica usando el charset declarado y un fallback seguro. La guía de codecs en Python explica decodificación incremental y estrategias de error.

Guardar adjuntos de forma segura

Genera un nombre interno, limita tamaño por archivo y total, inspecciona el formato real y almacena fuera de directorios ejecutables. Un filename MIME puede incluir traversal, caracteres de control, nombres reservados o duplicados.

Usa tempfile en Python durante la validación y hashlib en Python para integridad y deduplicación.

Flags

Las flags comunes incluyen \Seen, \Answered, \Flagged, \Deleted y \Draft. Usa UID STORE y comandos silenciosos cuando no necesites una respuesta expandida.

cliente.uid(
    "store",
    uid,
    "+FLAGS.SILENT",
    r"(\Seen)",
)

Antes de cambiar flags, confirma que UID y buzón siguen identificando el mensaje correcto. Registra la decisión de dominio sin copiar contenido sensible.

El borrado ocurre en dos etapas

El borrado IMAP tradicional añade \Deleted y luego ejecuta EXPUNGE. Un EXPUNGE puede eliminar permanentemente todos los mensajes ya marcados en el buzón seleccionado, incluso por otro cliente.

cliente.uid("store", uid, "+FLAGS.SILENT", r"(\Deleted)")
# No llames expunge automáticamente sin una política explícita.

Cuando estén disponibles, UIDPLUS o MOVE ofrecen operaciones más previsibles. Usa unselect() para liberar el buzón sin expurgar. Ten en cuenta que close() en un buzón escribible puede eliminar mensajes marcados.

Copiar y mover

copy() copia mensajes. Un move heredado suele ser COPY, añadir \Deleted y EXPUNGE, lo que resulta arriesgado con concurrencia. Si el servidor anuncia MOVE, usa la extensión mediante uid("MOVE", ...) y verifica el resultado.

IDLE en Python 3.14

Python 3.14 añade idle(), un context manager iterable que produce notificaciones como EXISTS. Define duración para evitar límites de inactividad del servidor.

with cliente.idle(duration=29 * 60) as idler:
    for tipo, datos in idler:
        if tipo == "EXISTS":
            print("el buzón cambió", datos)

Una notificación no es una sincronización completa. Cuando llegue un evento, ejecuta una búsqueda o sincronización acotada por UID. Reconecta con backoff si se cierra IDLE.

Deadline total y reconexión

El timeout del constructor cubre el establecimiento de conexión, pero flujos largos necesitan un plazo total y política de reconexión. Un IMAP4.abort normalmente exige cerrar el objeto y abrir otra conexión.

No repitas a ciegas comandos de escritura: una desconexión puede ocurrir después de que el servidor aplicó el cambio pero antes de que el cliente recibió confirmación.

Comprobar resultados

La mayoría de métodos devuelve (estado, datos), donde estado suele ser OK, NO o BAD. La ausencia de excepción no siempre significa éxito.

estado, datos = cliente.uid("search", None, "ALL")
if estado != "OK":
    detalle = datos[0] if datos else b"sin detalle"
    raise RuntimeError(f"IMAP devolvió {estado}: {detalle!r}")

Logs y privacidad

No actives imaplib.Debug en producción. Las trazas pueden revelar nombres de buzón, direcciones, asuntos, identificadores y autenticación. Registra solo buzón lógico, operación, duración, cantidad, estado e identificador de correlación.

Pruebas recomendadas

Prueba certificado inválido, login rechazado, buzón ausente, read-only, UIDs y UIDVALIDITY, mensajes multipart, charsets inválidos, adjuntos grandes, flags, reconexión, IDLE, datos FETCH no solicitados y protección contra EXPUNGE accidental.

Errores comunes

Los errores frecuentes son omitir validación de certificado, usar números de secuencia, seleccionar con escritura por defecto, obtener BODY[] y marcar mensajes, leer solo la primera tupla FETCH, confiar en filenames, llamar EXPUNGE automáticamente, registrar contenidos y repetir escrituras inciertas después de timeout.

Conclusión

imaplib ofrece control detallado de buzones IMAP, pero ese control exige disciplina. Usa IMAP4_SSL con contexto verificado explícito, selecciona read-only, prefiere UIDs, usa BODY.PEEK, limita mensajes y adjuntos, revisa cada respuesta y convierte el borrado en un flujo autorizado aparte.

Consulta la documentación oficial de imaplib y el RFC 9051 de IMAP4rev2. Para automatizaciones críticas, combina cuenta aislada, estado idempotente, observabilidad y backups.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    A retro blue mailbox attached to a vibrant yellow wall, perfect for vintage themes.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    poplib en Python: lee correos con POP3

    Aprende poplib en Python para acceder a POP3 con TLS, listar y descargar mensajes, usar UIDL, aplicar límites y evitar

    Ler mais

    Tempo de leitura: 6 minutos
    22/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ftplib en Python: FTP y FTPS seguros

    Aprende ftplib en Python para listar, descargar y subir archivos por FTP o FTPS con TLS, timeouts, límites, reanudación y

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Rack de servidores que representa un endpoint creado con xmlrpc.server en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.server: crea servidores XML-RPC

    Aprende xmlrpc.server en Python para crear servidores XML-RPC, registrar funciones, limitar métodos y rutas y evitar exposición insegura.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Código de error sobre datos binarios que representa fallos manejados con urllib.error en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.error en Python: maneja errores HTTP

    Aprende urllib.error en Python para manejar URLError, HTTPError, descargas incompletas, retries selectivos y diagnósticos de red claros.

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Teclas con la palabra HTML que representan entidades HTML en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    html.entities: convierte entidades HTML

    Aprende html.entities en Python para consultar entidades HTML, convertir nombres y code points y no confundir decodificación con sanitización.

    Ler mais

    Tempo de leitura: 7 minutos
    21/08/2026
    Carpeta con archivos que representa tipos MIME identificados con mimetypes en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes: detecta tipos MIME de archivos

    Aprende mimetypes en Python para identificar tipos de archivo, validar cargas y definir Content-Type con más seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    20/08/2026