Fue Top 10 en Hacker News

El email moderno se puede montar con piezas de otros

Read in English

Vamos a diseñar el sucesor del email sobre HTTP, arreglando los fallos de diseño que SMTP arrastra desde hace 40 años. Pieza por pieza. El objetivo no es sustituir el sistema actual de correo, ¡Dios me libre!, sino aprender, divertirnos y descubrir tecnologías actuales para reemplazar cada elemento: enviar, recibir, pasarela, claves, etc. El sistema nunca se entenderá con Gmail ni con ningún proveedor de correo clásico: solo habla consigo mismo. Lo único que vamos a conservar es la forma de las direcciones usuario@dominio. Todo lo demás se reinventa.

Como es un sistema de correo sobre HTTP, un buen nombre para el protocolo sería HMTP: Hypertext Mail Transfer Protocol (la S de Simple de SMTP cede su sitio a la H de HTTP).

Ahora toca preparar el stack tecnológico: HTTP, TLS, WebFinger, ActivityPub, Webmention, Ed25519, HPKE, Sigchains, etc.

La lista de materiales

Este diseño no inventa una sola tecnología: todo existe ya.

Problema Tecnología existente Quién la usa hoy
Transporte y códigos de estado HTTP Toda la web
Cifrado de transporte TLS + Let's Encrypt Toda la web
Descubrimiento de usuarios WebFinger (RFC 7033) Mastodon y el Fediverso
Entrega de mensajes POST a un inbox ActivityPub
Verificación del remitente en origen El patrón de Webmention y DKIM IndieWeb, todo el email
Firmas Ed25519 SSH, Signal
Cifrado del contenido HPKE (RFC 9180) MLS, TLS ECH
Identidad que sobrevive a rotar claves Sigchains ATProto (Bluesky), Keybase
Lectura y sincronización JMAP (RFC 8620) Fastmail
Notificaciones push SSE / WebPush Todos los navegadores
Consentimiento en primer contacto Message requests Signal, Instagram
Adjuntos por referencia con hash Content addressing Git, IPFS, Matrix

Cada pieza que vamos a usar está estandarizada, desplegada y probada a escala por millones de personas; lo único nuevo es el ensamblaje.

Elegir HTTP no es solo pragmatismo. Resuelve de saque varias cosas que cualquier protocolo nuevo tendría que montar en algún momento:

  • TLS, virtual hosting y SNI: gratis.
  • Códigos de estado: el catálogo que necesita un protocolo de correo ya existe en HTTP. 202 Accepted (entregado a cola), 429 Too Many Requests + Retry-After (control de ritmo), 404/410 (buzón inexistente/desaparecido), 3xx (buzón mudado), 413 (demasiado grande). Hasta el anti-spam de pago tiene código reservado desde 1997: 402 Payment Required.
  • Infraestructura existente: proxies, balanceadores, Nginx, librerías en todos los lenguajes. El protocolo deja de ser un servidor nuevo y pasa a ser una convención sobre HTTP, como Webmention o Micropub.

Y hay una ironía histórica que cierra el círculo: las cabeceras de HTTP descienden directamente de las cabeceras del email (RFC 822). Llevar el correo a HTTP no es un secuestro, es una vuelta a casa.

Pasemos al siguiente nivel: ¿cómo descubrir a un usuario, verificar su identidad, entregar el mensaje, firmarlo y cifrarlo, todo sobre HTTP?

1. Descubrimiento y delegación (un MX mejor)

La delegación se arregla con un documento estático:

GET https://example.com/.well-known/hmtp/ana
{
  "inbox": "https://mail.migadu.example/hmtp/inbox/ana",
  "keys": { ... },
  "devices": [ ... ]
}

El inbox puede vivir en otro host: eso es el registro MX, pero sin tocar DNS. Un blog estático en GitHub Pages puede delegar su correo a un proveedor sirviendo un JSON. Y permite algo que MX no permite: delegación por usuario (cada buzón de un dominio en un proveedor distinto). De hecho no haría falta inventar ni la ruta: WebFinger (RFC 7033) hace exactamente esto y Mastodon ya demostró que escala.

¿Te suena a herejía sacar el descubrimiento de DNS? El email actual ya dio ese paso: MTA-STS (RFC 8461) publica su política en https://mta-sts.dominio/.well-known/mta-sts.txt, precisamente porque desplegar TLS con Let's Encrypt resultó más fácil que desplegar DNSSEC. El precio real de esta decisión es otro: el dominio tiene que servir HTTPS. Es un acoplamiento que MX no tiene, y lo aceptamos a cambio de la delegación por usuario.

2. Identidad que sobrevive a la rotación de claves

La identidad no puede ser una clave (se pierden, caducan); tiene que ser algo que las claves respaldan. Propuesta de dos anclas:

  • Continuidad criptográfica: el documento de descubrimiento publica la clave actual y una cadena de rotaciones, donde cada clave nueva va firmada por la anterior. Quien te conocía con la clave N puede verificar la N+1 sin confiar en nadie. Es una sigchain, lo que hace ATProto con los DIDs o lo que hacía Keybase.
  • Control del dominio como respaldo: si pierdes la clave sin rotación firmada (te roban el portátil), el dominio declara una clave nueva sin cadena, con un periodo de anuncio obligatorio (digamos 30 días) durante el cual los servidores que te conocían muestran la advertencia "identidad reanclada por dominio, no por firma".

Sin embargo la identidad anclada al dominio tiene su propio talón de Aquiles, y es que un dominio no se posee, se alquila. Si dejas de pagarlo, caduca y alguien lo registra, el nuevo dueño publica sus claves en tu .well-known y desde ese momento recibe tu correo y firma como tú. Nadie tiene forma de distinguir al heredero legítimo del okupa. Es el problema que ATProto intenta resolver separando la identidad del dominio con DIDs, al precio de otra pieza de infraestructura. Sabemos que es un problema real y tiene una solución conocida. Ahora sigamos.

3. Entrega con cola (store-and-forward)

La entrega es un POST al inbox del destinatario:

POST /hmtp/inbox/ana HTTP/1.1
Host: mail.migadu.example
Content-Type: application/hmtp+json

La clave no es la petición, es quién lo hace. Tu cliente no entrega directamente al destinatario, sino a tu propio servidor (un POST autenticado a tu outbox), y es tu servidor quien encola, reintenta con backoff exponencial y respeta los Retry-After. Es admitir que la separación MUA/MSA/MTA de SMTP era correcta. Una genialidad silenciosa del email es que si el servidor destino está caído, tu servidor reintenta durante días y tú te olvidas.

Pero añadamos una mejora que SMTP nunca tuvo. Cada mensaje lleva un ID que es el hash de su contenido, así que los reintentos son idempotentes. El servidor receptor deduplica por ID y el clásico "correo duplicado porque falló el ACK" desaparece por construcción.

El ciclo completo de una entrega, con el nodo destino caído en el primer intento:

sequenceDiagram
    autonumber
    participant Ana as Cliente de Ana
    participant SA as Servidor de Ana
    participant SB as Servidor de Bob

    Ana->>SA: POST /outbox (mensaje firmado)
    SA-->>Ana: 202 encolado
    SA->>SB: GET /.well-known/hmtp/bob
    SB-->>SA: inbox y claves de Bob
    SA->>SB: POST /hmtp/inbox/bob (sobre + cuerpo sellado)
    Note over SB: caído: sin respuesta
    Note over SA: cola: reintenta con backoff exponencial
    SA->>SB: POST /hmtp/inbox/bob (reintento, mismo id)
    SB->>SA: GET /.well-known/hmtp/ana
    SA-->>SB: clave de firma de Ana
    Note over SB: firma verificada, deduplicado por id
    SB-->>SA: 201 delivered

Fíjate en que el diagrama contiene el protocolo entero: los dos GET al .well-known son el descubrimiento y la verificación, el POST es la entrega, y la cola vive donde debe, en el servidor del emisor.

4. Firmar y cifrar con las claves que ya tenemos

El mensaje es un objeto firmado, no texto suelto:

{
  "id": "sha256:9f2c...",
  "from": "ana@example.com",
  "to": ["bruno@example.org"],
  "date": "2026-07-26T10:00:00Z",
  "in_reply_to": "sha256:11ab...",
  "sealed": "<subject y body cifrados con HPKE>",
  "signature": "..."
}

De este modo ganamos muchas cosas:

  • Autenticidad en reposo: el correo almacenado lleva su prueba criptográfica. Un reenvío conserva la firma original. Falsificar el remitente se vuelve imposible incluso en cadenas de reenvíos.
  • Verificación del remitente sin DKIM: el servidor receptor hace GET al .well-known del dominio del from y comprueba que la clave firma. Es el mismo movimiento que la verificación de Webmention. La prueba se busca en la fuente.
  • E2E: el documento de descubrimiento publica claves de cifrado (X25519); el asunto y el cuerpo van sellados juntos con HPKE. El sobre (from, to, id, fecha) queda visible para enrutar y filtrar; todo lo demás, solo para el destinatario. PGP arrastró décadas el error de dejar el subject en claro, y esa metadata delató a más de uno. Aquí no lo repetimos.
  • Hilos: in_reply_to y references por hash de contenido. Conversaciones reconstruibles sin heurísticas.
  • Adjuntos: fuera del mensaje. El archivo se cifra con una clave de un solo uso y el servidor del remitente almacena un blob opaco; la referencia {hash, url, size, key} viaja dentro del cuerpo sellado, así que el E2E cubre también los adjuntos (es el patrón de Matrix). El hash es del blob cifrado: cualquier servidor verifica la integridad sin poder leer nada. El servidor del destinatario lo espeja en la entrega, no en la lectura: nadie sabe cuándo abres un adjunto, y el remitente puede borrar el original cuando los espejos confirman. Se acabó el base64 inflando buzones, y de paso los costes se invierten: quien envía, hospeda. Con un efecto secundario buscado: sin adjuntos dentro, el JSON de un mensaje es siempre pequeño, parsearlo entero en memoria deja de ser un riesgo y el servidor puede imponer un tope agresivo con un 413.

Un detalle que importa: el id es el hash del mensaje en claro, calculado antes de cifrar, y la firma cubre ese mismo contenido. Así todos los participantes de un hilo comparten los mismos identificadores aunque cada copia viaje sellada con una clave distinta. El servidor deduplica comparando ids sin leer nada; el destinatario verifica hash y firma tras descifrar.

Firmar el mensaje completo tiene un precio que DKIM conoce bien: nadie puede tocarlo en tránsito. Y las listas de correo viven justo de eso, de añadir cabeceras (List-Id, el enlace de baja) al mensaje que reparten. No es casualidad que DKIM permita elegir qué cabeceras firmar. En HMTP una lista no modificaría tu mensaje: publicaría uno nuevo, firmado por ella, que referencia el original por su hash. El lector vería quién escribió y quién repartió, cada uno con su firma. Otro problema real con solución conocida. Sigamos.

5. Anti-spam por capas

Como cualquier sistema, es un problema complejo que requiere varias capas de defensa. Con HMTP podríamos empezar con tres:

  1. Coste de identidad: firmar como ana@example.com exige servir el documento de claves en example.com. La identidad está anclada a un dominio, y los dominios cuestan dinero. Es el coste sybil que las identidades autofirmadas no tienen. Sin embargo, un dominio da subdominios y buzones infinitos, así que el coste frena la creación masiva de identidades independientes, no de direcciones. Trabajamos a nivel de raíz.
  2. Consentimiento en primer contacto: un remitente desconocido no entra al buzón; entra a una bandeja de "solicitudes" con su primer mensaje visible (como los message requests de Signal). Aceptas y el hilo se abre para siempre. Un desconocido puede llamar a tu puerta, que es la propiedad esencial del correo, pero no puede llenarte el salón.
  3. Franqueo opcional para desconocidos: el servidor puede responder al primer contacto con 402. Configurable por buzón; el coste de spamear en frío deja de ser cero.

Seamos honestos con la capa 2, porque carga con treinta años de historia en contra. Los sistemas de desafío-respuesta se probaron en los noventa y fracasaron por dos vías. Una: la bandeja de solicitudes puede acabar siendo la nueva carpeta de spam, el problema se muda de habitación. Y dos, la letal: el correo que no escribe ningún humano (la confirmación de tu compra, la factura, el banco, todo lo que sale de un no-reply@) jamás supera un desafío, y ese correo es la mayoría del volumen real.

Por eso aquí el consentimiento es pasivo: nadie resuelve captchas ni sigue enlaces, el primer mensaje llega entero, espera visible y tú decides con un toque. Si tú iniciaste el contacto, el hilo ya nació abierto. Y para el correo de máquinas que tú mismo pediste (el alta en una web que te enviará facturas), la aceptación es una vez y para siempre. ¿Elimina el spam? No. Ningún consentimiento lo elimina, lo reordena. La apuesta es que una bandeja de solicitudes con el mensaje a la vista es mejor trato que un filtro estadístico decidiendo a ciegas qué llega a tu buzón.

6. La lectura también se especifica

El email estandarizó el envío (SMTP) y la lectura (IMAP/POP) como mundos separados. Nosotros no necesitamos inventar nada: leer, sincronizar y buscar buzones sobre JSON/HTTP ya está resuelto y estandarizado por el IETF. Se llama JMAP. HMTP definiría la entrega; la lectura es JMAP con un tipo de objeto nuevo. Push al cliente con SSE o WebPush. El ciclo completo (enviar, entregar, leer, sincronizar) queda sobre HTTP.

La conclusión

Cada problema del correo tiene una solución desplegada y funcionando: el descubrimiento en Mastodon, la entrega en ActivityPub, la verificación en el IndieWeb, la identidad rotable en Bluesky, la lectura en Fastmail, el consentimiento en Signal. Una actualización del correo ya existe, pero nadie la ha ensamblado.

Solucionamos muchos problemas con soluciones elegantes y modernas:

  • Reputación y permiso social (SPF, DKIM, DMARC, PTR inverso, listas negras, meses de calentar una IP): sustituido por verificación criptográfica en cada mensaje, una firma y un GET al .well-known del remitente. Una propiedad que se verifica no necesita reputación.
  • Suplantación del remitente: imposible por construcción. El receptor comprueba la firma contra la clave publicada en el dominio del from, y la firma viaja con el mensaje incluso reenviado.
  • Delegación del correo (el registro MX): un documento estático en .well-known, con delegación por usuario y sin tocar DNS.
  • Privacidad del contenido (el PGP que nadie llegó a configurar): cifrado de extremo a extremo por defecto; el servidor receptor almacena un cuerpo, y un asunto, que no puede leer.
  • Spam (filtros estadísticos a posteriori): consentimiento en primer contacto (los desconocidos llaman a la puerta, no entran al buzón), identidad con coste anclada al dominio y franqueo opcional con 402.
  • Duplicados por reintento: el id del mensaje es el hash de su contenido, así que los reintentos son idempotentes y el receptor deduplica por construcción.
  • Hilos reconstruidos con heurísticas: in_reply_to apunta al hash del mensaje padre; la conversación es un grafo verificable.
  • Buzones ligeros: adjuntos cifrados por referencia, espejados en la entrega. Quien envía, hospeda: el spam con adjuntos gordos le cuesta el disco al spammer, no a ti.
  • Infraestructura sencilla y conocida: todo viaja por HTTPS estándar, detrás del mismo Nginx y el mismo certificado que ya sirven tu web.
  • Rotar claves (la pesadilla operativa de DKIM): un comando. Como nadie fija tu clave, la nueva es confiable al instante y la robada queda inservible igual de rápido.

Hablar es fácil, por ello he implementado un prototipo funcional en Python: github.com/tanrax/hmtp. Todo en un solo archivo. El prototipo cubre el transporte completo: entrega firmada, verificación en origen, cifrado de extremo a extremo, consentimiento en primer contacto, deduplicación, hilos, cola con backoff exponencial y rotación de claves en un comando. Deja fuera, a conciencia, las piezas de la periferia: la cadena de rotaciones firmadas (los receptores consultan tu clave en vivo en cada entrega en lugar de fijarla, así que solo empieza a pagarse cuando los nodos cacheen claves), el franqueo 402, los adjuntos por referencia y la lectura JMAP. El README trae el quickstart (tu primer correo en dos minutos, escribiéndote a ti mismo), una demo de dos nodos intercambiando correo cifrado en tu máquina y la guía de producción completa, del DNS al systemd. Recuerda que es un experimento de diseño con criptografía sin auditar; no lo uses para secretos de los que dependa nadie. Aunque podría ser un sistema ligero de comunicación para el ecosistema que te venga a la cabeza.

Espero que hayas disfrutado el paseo. Y si llegas a enviar tu primer correo con HMTP, me encantaría que me lo contaras.

Actualización (29 de julio de 2026): revisé el artículo tras la discusión en Hacker News. El asunto ahora viaja cifrado junto al cuerpo, los adjuntos quedan cubiertos por el E2E y se espejan en la entrega, se aclara qué cubre el hash del mensaje y cómo encajarían las listas de correo, y se reconocen los límites del consentimiento en primer contacto.

Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.

Ayúdame a seguir escribiendo

Cada café me da un empujón para escribir el siguiente artículo.

Comentarios

Todavía no hay ningún comentario.

Sigue leyendo