Las aplicaciones que usan el correo electrónico para enviar notificaciones usan el protocolo SMTP que por defecto necesitan estos datos: servidor, puerto, usuario y contraseña. Evidentemente hace falta algo más para que sea más seguro y por eso los grandes proveedores de correo han usando diferentes opciones para asegurar este procotolo, desde contraseñas solo de aplicaciones, doble factor de autenticación (2FA) o SMTP oAuth.
El tema está en ¿que pasa con las aplicaciones antiguas que no soportan estas opciones? o simplemente no se puede tocar el código. Sin ir tan lejos que pasa con las impresoras y escáners que tienen la opción de mandar trabajos por correo. Por lo tanto aquí necesitamos una opción segura, pero que siga funcionado con las credenciales simples del protocolo SMTP AUTH.
En el caso de los que tengamos como proveedor de correo electrónico a Microsoft, es importante saber que a fin de año (2026), la cuentas Office 365 dejarán de funcionar a fin de año con el protocolo SMTP simple. Así que nos toca modificar nuestros desarrollos para que se adapten al SMTP oAuth, pero ¿qué pasa con las aplicaciones o dispositivos que no podemos tocar o cambiar?
Pues tenemos una alternativa con una opción que Azure ofrece: Microsoft 365 connector.
El relay recibe correo mediante SMTP AUTH y STARTTLS en el puerto 587 y lo entrega a Microsoft 365 mediante un conector autenticado por IP pública.
Arquitectura
Aplicación legacy -- SMTP AUTH/STARTTLS:587 --> Relay Docker
Relay Docker -- SMTP/STARTTLS:25 --> MX de Microsoft 365
Microsoft 365 --> Destinatarios internos y externos
Valores del ejemplo
Sustituye los siguientes valores por los reales:
DOMINIO = example.com
NOMBRE_RELAY = smtp-relay.example.com
IP_PUBLICA_RELAY = 203.0.113.25
MX_M365 = example-com.mail.protection.outlook.com
REMITENTE = postmaster@example.com
Las direcciones del ejemplo pertenecen a rangos reservados para documentación.
Requisitos
- Microsoft 365 con permisos de administración de Exchange.
- Servidor Linux/Windows con Docker Compose.
- IP pública estática para el relay.
- Registro DNS A para el nombre del relay.
- Salida TCP 25 desde el relay.
- Entrada TCP 587 limitada a las IP de las aplicaciones (punto muy importante).
Conector de Microsoft 365
En Exchange Admin Center:
- Abrir Mail flow, Connectors.
- Crear un conector desde el servidor de correo de la organización hacia Microsoft 365.
- Activarlo.
- Elegir autenticación mediante la IP pública del relay.
- Añadir la IP pública.
- Activar la restricción para que solamente esa IP pueda usar el conector.
- Activar TLS obligatorio si se desea exigir cifrado.
Configuración esperada:
ConnectorType : OnPremises
SenderIPAddresses : {203.0.113.25}
RestrictDomainsToIPAddresses : True
RestrictDomainsToCertificate : False
RequireTls : True
Verificación:
Connect-ExchangeOnline
Get-InboundConnector -Identity 'Legacy SMTP Relay' | Format-List Name,Enabled,ConnectorType,SenderIPAddresses,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,TlsSenderCertificateName,RequireTls,SenderDomains
Si se utiliza autenticación por IP, no debe quedar activa una restricción por certificado.
Comprueba la IP real del relay:
curl -4 https://api.ipify.org
Certificados TLS
Si el proveedor entrega certificado del servidor, cadena CA y clave privada:
certificate.crt
certificate.ca.crt
certificate.key
Crea el certificado completo:
mkdir -p certs
awk 'NF { sub(/\r$/, ""); print }' certificate.crt > certs/server.crt
awk 'NF { sub(/\r$/, ""); print }' certificate.ca.crt > certs/ca.crt
awk 'NF { sub(/\r$/, ""); print }' certificate.key > certs/privkey.pem
cat certs/server.crt certs/ca.crt > certs/fullchain.pem
chmod 600 certs/privkey.pem
chmod 644 certs/fullchain.pem
No uses solamente certificate.ca.crt: contiene la cadena CA, pero no identifica al servidor.
Docker Mailserver
Volúmenes relevantes:
volumes:
- ./docker-data/dms/mail-data:/var/mail
- ./docker-data/dms/mail-state:/var/mail-state
- ./docker-data/dms/mail-logs:/var/log/mail
- ./docker-data/dms/config:/tmp/docker-mailserver
- ./certs:/tmp/ssl:ro
Variables relevantes:
OVERRIDE_HOSTNAME=smtp-relay.example.com
HOSTNAME=smtp-relay
DOMAINNAME=example.com
DEFAULT_RELAY_HOST=[example-com.mail.protection.outlook.com]
RELAY_PORT=25
POSTFIX_INET_PROTOCOLS=ipv4
PERMIT_DOCKER=
ENABLE_IMAP=0
ENABLE_POP3=0
ENABLE_CLAMAV=0
ENABLE_SPAMASSASSIN=0
ENABLE_FAIL2BAN=0
SSL_TYPE=manual
SSL_CERT_PATH=/tmp/ssl/fullchain.pem
SSL_KEY_PATH=/tmp/ssl/privkey.pem
ACCOUNT_PROVISIONER=FILE
Mantén TLS 1.2 como mínimo. No habilites TLS 1.0 o TLS 1.1.
Cuenta SMTP local
La cuenta del relay no tiene que existir en Microsoft 365:
docker compose run --rm relay setup email add postmaster@example.com
La contraseña solo autentica a las aplicaciones contra el relay.
Si la aplicación usa automáticamente el usuario SMTP como remitente, configura:
Usuario SMTP: postmaster@example.com
From: postmaster@example.com
Workaround para destinatarios internos
Docker Mailserver puede tratar example.com como dominio local. En ese caso, un mensaje a usuario@example.com intentará entregarse mediante Dovecot aunque el buzón real esté en Microsoft 365.
Síntoma:
relay=...[/var/run/dovecot/lmtp]
User doesn't exist
status=bounced
Crea el fichero:
docker-data/dms/config/postfix-transport.regexp
Contenido:
/@example\.com$/ smtp:[example-com.mail.protection.outlook.com]:25
Crea o edita:
docker-data/dms/config/postfix-main.cf
Añade:
transport_maps = regexp:/tmp/docker-mailserver/postfix-transport.regexp
Recrea y verifica:
docker compose up -d --force-recreate relay
docker compose exec relay postconf transport_maps
docker compose exec relay cat /tmp/docker-mailserver/postfix-transport.regexp
El log correcto debe mostrar el MX de Microsoft 365 y status sent. No debe mostrar entrega a /var/run/dovecot/lmtp.
Seguridad
Limita TCP 587 a las IP de las aplicaciones. Usa autenticación SMTP y limita los remitentes permitidos.
No confíes únicamente en la IP fija: un relay SMTP accesible sin autenticación puede convertirse en un open relay.
Prueba desde PowerShell
$smtpServer = 'smtp-relay.example.com'
$credential = Get-Credential
Send-MailMessage -SmtpServer $smtpServer -Port 587 -UseSsl -Credential $credential -From 'postmaster@example.com' -To 'destinatario@dominio-externo.example' -Subject 'Prueba de relay SMTP' -Body 'Mensaje de prueba'
En el puerto 587, UseSsl negocia STARTTLS; no significa necesariamente TLS implícito.
Workaround para .NET Framework antiguo
El error siguiente suele indicar incompatibilidad TLS:
The client and server cannot communicate,
because they do not possess a common algorithm
Si no se puede modificar el código:
- Actualiza .NET Framework y Windows.
- Aplica los parches criptográficos.
- Habilita TLS fuerte para .NET:
HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319
SchUseStrongCrypto = 1
SystemDefaultTlsVersions = 1
HKLM\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319
SchUseStrongCrypto = 1
SystemDefaultTlsVersions = 1
- Reinicia el servicio o servidor.
No habilites TLS 1.0 o TLS 1.1 como solución permanente.
DKIM, SPF y DMARC
El relay no necesita firmar DKIM. Microsoft 365 firma el mensaje al enviarlo hacia Internet si DKIM está habilitado para el dominio.
Get-DkimSigningConfig -Identity example.com | Format-List Domain,Enabled,Status,Selector1CNAME,Selector2CNAME
Resultado esperado:
Enabled : True
Status : Valid
Publica en DNS los dos CNAME indicados por Microsoft 365.
Un SPF habitual cuando Microsoft 365 realiza la entrega final es:
v=spf1 include:spf.protection.outlook.com -all
No añadas la IP del relay al SPF salvo que también entregue directamente a Internet.
Interpretación de logs
Autenticación correcta:
Anonymous TLS connection established ... TLSv1.2
sasl_username=postmaster@example.com
Procesamiento correcto:
Passed CLEAN
Conexión correcta con Microsoft 365:
Trusted TLS connection established to ...mail.protection.outlook.com
Aceptación final:
dsn=2.6.0, status=sent
250 2.6.0 ... Queued mail for delivery
Estos mensajes son informativos:
opendkim: no signing table match
opendkim: no signature data
Solo indican que el relay no firma DKIM.
Los resultados SPF o DKIM iniciales que Microsoft 365 registre al recibir desde la IP del relay describen el primer salto. Lo importante para el destinatario externo son las cabeceras finales después de que Microsoft 365 procese y firme el mensaje.
Verificación final
En las cabeceras del destinatario externo busca:
Authentication-Results:
spf=pass
dkim=pass header.d=example.com
dmarc=pass header.from=example.com
Lista de comprobación:
- [ ] DNS del relay apunta a la IP correcta.
- [ ] TCP 587 solo acepta las aplicaciones autorizadas.
- [ ] El certificado contiene servidor y cadena CA.
- [ ] STARTTLS y autenticación SMTP funcionan.
- [ ] El conector usa la IP pública correcta.
- [ ] La restricción por IP está activada.
- [ ] Los destinatarios internos se enrutan al MX de Microsoft 365.
- [ ] DKIM está habilitado y validado.
- [ ] SPF y DMARC están publicados.
- [ ] El relay no es un open relay.
Conclusión
Este patrón permite mantener aplicaciones legacy sin OAuth y centralizar su salida de correo en Microsoft 365. Las aplicaciones siguen usando SMTP tradicional contra un relay controlado, mientras Microsoft 365 realiza la entrega final y puede aplicar DKIM, SPF y DMARC.