Posthorn: La Capa de Correo Saliente Unificada para Proyectos Autogestionados

Posthorn es una puerta de enlace de correo electrónico autogestionada que unifica el correo saliente de tus aplicaciones hacia proveedores transaccionales como Postmark, Resend y AWS SES.

El Problema: El Correo Saliente es un Desastre para los Autogestionados

Nadie quiere ejecutar un servidor de correo en 2026. Los operadores autogestionados usan Postmark, Resend, Mailgun o AWS SES porque son baratos, manejan la entregabilidad correctamente y alguien más se preocupa por SPF/DKIM/DMARC/rechazos/reputación del remitente.

Pero cada aplicación que autogestionas tiene que integrarse con ese servicio de forma independiente. Tu formulario de contacto. Los correos administrativos de tu blog Ghost. Tus enlaces mágicos de Gitea. Tus notificaciones de Mastodon. El Worker de Cloudflare que envía un correo de restablecimiento de contraseña cuando alguien hace clic en el enlace. Cada uno necesita su propia copia de la clave API, su propio código de integración, sus propias peculiaridades en el manejo de reintentos y rebotes. La misma preocupación de correo saliente duplicada en tu pila.

Y en hosts en la nube que bloquean SMTP saliente — DigitalOcean, AWS Lightsail, Linode, Vultr — las aplicaciones que solo usan SMTP no funcionan sin una solución alternativa.

Presentamos Posthorn

Posthorn es el puente. Un contenedor, una configuración, un conjunto de credenciales. Tus aplicaciones apuntan a Posthorn. Posthorn se comunica con tu proveedor.

Proporciona tres formas de entrada:

  • Formulario HTTP (formularios de contacto, registros, webhooks de alerta) — Honeypot + Origen/Referer + límite de velocidad + CSRF opcional; plantilla el correo; envía
  • Modo API HTTP (workers, cron, manejadores de pago, servicios internos) — Autenticación Authorization: Bearer; cuerpo JSON; reintentos idempotentes; to_override por solicitud para envíos transaccionales
  • Receptor SMTP (Ghost, Gitea, Mastodon, Matrix, NextCloud, Authentik, cualquier cosa que emita SMTP) — AUTH PLAIN o certificado de cliente; STARTTLS requerido; listas permitidas de remitente y destinatario; analiza MIME; reenvía a través del transporte de API HTTP

Las tres entradas convergen en un transport.Message y un proveedor saliente — elige entre Postmark, Resend, Mailgun, AWS SES o un relé SMTP saliente.

Lo que Posthorn No Es

Para ahorrarte un giro equivocado:

Lo que hace Mira en su lugar
No es un servidor de correo — Sin almacenamiento de buzones, sin IMAP/JMAP, sin gestión de claves DKIM, sin destino MX Stalwart, Mailcow, iRedMail
No es su propia infraestructura saliente — Posthorn retransmite a través de un proveedor que elegiste; no ejecuta su propia flota SMTP ni gestiona la reputación IP Postal, Hyvor Relay
No es una plataforma de correo de marketing — Sin gestión de listas, sin segmentación, sin panel de campañas Listmonk
No es webmail / interfaz de buzón — Sin interfaz para leer correo Roundcube, Snappymail (con un servidor de correo)

El nicho es la capa de integración entre tus aplicaciones autogestionadas y el proveedor transaccional que ya has elegido.

Inicio Rápido (Docker)

# docker-compose.yml
services:
  posthorn:
    image: ghcr.io/craigmccaskill/posthorn:latest
    restart: unless-stopped
    volumes:
      - ./posthorn.toml:/etc/posthorn/config.toml:ro
    environment:
      POSTMARK_API_KEY: ${POSTMARK_API_KEY}
    ports:
      - "127.0.0.1:8080:8080" # enlazar a loopback; proxy inverso desde tu puerta principal
# posthorn.toml
[[endpoints]]
path = "/api/contact"
to = ["[email protected]"]
from = "Formulario de Contacto <[email protected]>"
honeypot = "_gotcha"
allowed_origins = ["https://example.com"]
required = ["name", "email", "message"]
subject = "Contacto de {{.name}}"
body = """
De: {{.name}} <{{.email}}>

{{.message}}
"""
redirect_success = "/thank-you"

[endpoints.transport]
type = "postmark"

[endpoints.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"

[endpoints.rate_limit]
count = 5
interval = "1m"

Proxy inverso /api/contact desde tu puerta principal (Caddy, nginx, Traefik) a http://posthorn:8080. Apunta la acción de tu formulario a /api/contact. Hecho.

Modo API (Servidor a Servidor)

Para Workers, trabajos cron, servicios internos — cualquier cosa que hable JSON en lugar de formularios:

[[endpoints]]
path = "/api/transactional"
to = ["[email protected]"]
from = "TuApp <[email protected]>"
auth = "api-key"
api_keys = ["${env.WORKER_KEY_PRIMARY}", "${env.WORKER_KEY_BACKUP}"]
required = ["subject_line", "message"]
subject = "{{.subject_line}}"
body = "{{.message}}"

[endpoints.transport]
type = "postmark"

[endpoints.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"
curl -X POST https://posthorn.yourdomain.com/api/transactional \
  -H "Authorization: Bearer $WORKER_KEY_PRIMARY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reset:user-123:$(date -u +%FT%H)" \
  --data '{
    "to_override": "[email protected]",
    "subject_line": "Restablece tu contraseña",
    "message": "Haz clic aquí: https://app.example.com/reset/abc"
  }'

Receptor SMTP (Ghost / Gitea / Mastodon / Authentik)

Para aplicaciones que hablan SMTP de forma nativa y no pueden reconfigurarse para llamar a una API HTTP:

[smtp_listener]
listen = ":2525"
require_tls = true
tls_cert = "/etc/posthorn/cert.pem"
tls_key = "/etc/posthorn/key.pem"
auth_required = "smtp-auth"
allowed_senders = ["*@yourdomain.com"]
max_recipients_per_session = 10
max_message_size = "1MB"

[[smtp_listener.smtp_users]]
username = "ghost"
password = "${env.GHOST_SMTP_PASSWORD}"

[smtp_listener.transport]
type = "postmark"

[smtp_listener.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"

Apunta la configuración SMTP de Ghost (o cualquier aplicación) a posthorn.yourdomain.com:2525 con el nombre de usuario/contraseña anteriores. Posthorn analiza el MIME, construye un transport.Message, reenvía a través de Postmark.

Elegir un Transporte

Transporte Mejor para Autenticación Cuerpo
Postmark Correo transaccional, valores predeterminados sólidos de entregabilidad X-Postmark-Server-Token JSON
Resend API HTTP moderna, panel amigable para desarrolladores Authorization: Bearer JSON
Mailgun Transaccional de mayor volumen, regiones EE. UU. + UE HTTP Basic multipart/form-data
AWS SES Implementaciones nativas de AWS, más barato en volumen AWS SigV4 (personalizado) JSON
SMTP saliente Cualquier relé compatible con STARTTLS (Mailtrap, tu smarthost Postfix, etc.) AUTH PLAIN SMTP DATA

Cambiar de proveedor es una edición de TOML — cada transporte implementa la misma interfaz Transport.

Lista de Verificación de Producción

Antes de dirigir tráfico real a Posthorn:

  • DNS — Registros SPF, DKIM y DMARC en tu dominio de envío. Sin estos, tu correo va a spam.
  • Proxy inverso — Posthorn no termina TLS. Ejecútalo detrás de Caddy, nginx o Traefik.
  • allowed_origins (puntos finales en modo formulario) — configúralo para bloquear envíos a tu dominio. Sin esto, cualquiera puede hacer POST a tu punto final.
  • rate_limit — establece un bucket ajustado por punto final (5/minuto es un valor predeterminado sensato para un formulario de contacto público; el modo API limita la velocidad por clave coincidente).
  • trusted_proxies — si está detrás de un proxy inverso, enumera su CIDR (o usa el preset nombrado cloudflare) para que el limitador de velocidad vea la IP real del cliente.
  • /healthz y /metrics — registrados automáticamente en el mismo receptor. Conecta tu healthcheck de Docker o scrape de Prometheus a estos.

Qué hay en v1.0

Bloque Detalle
Entrada de formulario Cuerpos codificados en formulario + multipart; honeypot, Origin/Referer fail-closed, límite de velocidad, tokens CSRF opcionales
Modo API auth = "api-key" con tokens Bearer (comparación en tiempo constante); tipo de contenido JSON; claves de idempotencia (24h, LRU en memoria); to_override por solicitud
Transportes Postmark, Resend, Mailgun, AWS SES (SigV4 personalizado), relé SMTP saliente
Receptor SMTP Receptor TCP con AUTH PLAIN / certificado de cliente, STARTTLS requerido, listas permitidas de remitente y destinatario, límite de tamaño, MIME → transport.Message
Operaciones /healthz, /metrics (exposición Prometheus), modo de simulación, eliminación de IP, presets nombrados de trusted_proxies (Cloudflare)
Manejo de fallos 1 reintento en transitorios/5xx (1s), 1 reintento en 429 (5s), tiempo de espera máximo de 10s
Registro JSON estructurado; ID de envío UUIDv4 e ID de sesión SMTP; transport_message_id en submission_sent
Implementación Binario único de Go, imagen Docker multi-arquitectura distroless en ghcr.io/craigmccaskill/posthorn

Tres dependencias externas de Go en todo el módulo: analizador TOML, biblioteca UUID, caché LRU. Cada transporte es personalizado — sin SDK de proveedor en el código de transporte.

Hoja de Ruta

  • v2 — madurez de plataforma. Registro de envíos SQLite, cola de reintentos entre reinicios, lista de supresión (automática en rebotes duros), idempotencia duradera, devoluciones de llamada de eventos de ciclo de vida mediante webhook firmado con HMAC, cancelación de suscripción con un clic RFC 8058, archivos adjuntos, cuerpo HTML, múltiples salidas por punto final (correo + webhook + registro de difusión), enrutamiento SMTP multiinquilino.
  • v3 — especulativo. Interfaz de administración, desafío de spam de prueba de trabajo, cifrado PGP. Depende de la tracción de la comunidad.

Compilar desde el Código Fuente

Requiere Go 1.25+.

git clone https://github.com/craigmccaskill/posthorn
cd posthorn/core
go build -o /tmp/posthorn ./cmd/posthorn
/tmp/posthorn version

Conclusión

Posthorn resuelve un problema real para los operadores autogestionados: la fragmentación de la integración del correo saliente. Al proporcionar una puerta de enlace única y unificada con múltiples formas de entrada y backends de transporte, simplifica drásticamente tu infraestructura de correo electrónico. Un contenedor, una configuración, un conjunto de credenciales — y listo.

Fuente

craigmccaskill/posthorn: Puerta de enlace de correo electrónico autogestionada entre tus aplicaciones y un proveedor de correo transaccional (Postmark, Resend, Mailgun, AWS SES o SMTP saliente). Tres formas de entrada (formulario HTTP, API HTTP, SMTP). Un contenedor Docker, una configuración TOML.