Skip to content

Email Delivery: the Authenticated SMTP Relay

Email verification, password recovery and MFA enrolment all depend on one thing: security-critical mail that actually arrives. Public cloud providers commonly block outbound port 25, so a Keycloak or application running in the cloud cannot deliver mail directly. As an interim solution, the platform runs its own hardened outbound relay on infrastructure that has unrestricted outbound mail, and every application submits to it over authenticated TLS.

Interim by design

This relay is a deliberate stop-gap. Applications only know "an SMTP submission endpoint with credentials", so it can later be swapped for a transactional email provider by changing configuration — no application code changes.

Architecture

flowchart LR
    subgraph Cloud["Cloud environments (port 25 blocked)"]
        KC[Keycloak]
        APP[Applications]
    end

    subgraph Relay["Relay host (unrestricted outbound)"]
        SUB["Postfix<br/>submission :587<br/>TLS required + SASL"]
    end

    MX[Recipient mail servers]

    KC -- "STARTTLS + SASL login" --> SUB
    APP -- "STARTTLS + SASL login" --> SUB
    SUB -- "direct delivery via recipient MX" --> MX

The relay is one Postfix instance with Cyrus SASL, provisioned by an Ansible role that is disabled unless a deployment opts in. Nothing in the role names a customer: every caller is just one more entry in a list.

Security properties

Property How it is enforced
Not an open relay smtpd_relay_restrictions = permit_sasl_authenticated, reject — there is no permit_mynetworks and no open fallback. Even a loopback connection must log in
Not an inbound mail server The plain smtp (port 25) listener is removed from master.cf; the host only exposes the submission service on 587
Encryption is mandatory smtpd_tls_security_level = encrypt — credentials are never accepted on a plaintext session; SSLv2/3 and TLS 1.0/1.1 are disabled
Anonymous access is impossible smtpd_sasl_security_options = noanonymous
Per-caller identity One SASL account per caller. Every session is attributable, and a leaked credential is revoked by deleting that single account
Trusted certificate Let's Encrypt certificate issued and renewed automatically (HTTP-01), with a reload hook that restarts Postfix on renewal
Secrets stay out of logs Account passwords are handled with no_log in the automation and never stored in the repository
Safe to rotate Account creation is create-if-missing; a rotation is remove → apply → re-add, never a silent in-place overwrite

Onboarding a caller

A caller (for example, an identity provider) needs four values: the relay hostname, port 587, STARTTLS, and its own username/password. In Keycloak these go under the realm's Email settings:

Keycloak field Value
Host the relay's public hostname
Port 587
Enable StartTLS on
Authentication on, with the caller's own account
From an address on a domain you control

Adding a caller is a one-line change to the relay's user list followed by an apply:

fab provide --env deployment --tags smtp_relay[smtp_relay] Install postfix + Cyrus SASL ... ok
[smtp_relay] Deploy main.cf / master.cf ... changed
[smtp_relay] Create missing SASL accounts ... changed
[smtp_relay] Issue Let's Encrypt cert for the relay hostname ... ok
[smtp_relay] Enable and start postfix ... ok

Verifying the relay

Check the security posture from the outside, then send a real message.

nc -zv -w 3 mail-relay.example.com 25nc: connect to mail-relay.example.com port 25 (tcp) failed: Connection refused# nothing listens for inbound mailopenssl s_client -starttls smtp -connect mail-relay.example.com:587 -brief </dev/null 2>&1 | grep -E "Protocol|Verification"Protocol version: TLSv1.3
Verification: OK
swaks --server mail-relay.example.com --port 587 --tls \ --to [email protected] --from [email protected]<~ 530 5.7.0 Authentication required# unauthenticated submission is refusedswaks --server mail-relay.example.com --port 587 --tls \ --auth LOGIN --auth-user "$SMTP_USER" --auth-password "$SMTP_PASS" \ --to [email protected] --from [email protected]<~ 250 2.0.0 Ok: queued as 4F1C0A2B3D

Deliverability checklist

A relay only helps if receivers trust its mail. Keep the sender domain aligned with:

  • SPF — authorise the relay's address in the domain's SPF record.
  • DKIM — sign outgoing mail for the sender domain.
  • DMARC — publish a policy so receivers can verify alignment.
  • Reverse DNS — the relay's IP should resolve back to its hostname.

Security mail is a target

Verification and reset emails are exactly what phishers imitate. Keep the From domain fixed, keep links pointing at your Keycloak host only, and never include a secret in the message body — the links are signed and expire by design (see Email Verification & MFA).