Email · SMTP & Mailgun
Configure outbound email so password resets and invitations actually send. Pick SMTP or Mailgun; without one, both flows fail silently.
Exo sends two kinds of transactional email: self-serve password resets and team invitations. The backend still boots with no email provider configured, but both flows then go nowhere. The user sees no error and no message arrives. Configure a provider before you invite anyone or rely on password login.
What email is used for
- Password reset. The forgot-password flow emails a reset link that expires in 1 hour. Accounts on an external identity provider get an explainer instead, telling them to sign in through their IdP.
- Invitations. Inviting a user by email sends an accept link that expires in 7 days.
Choosing a provider
Exo picks a provider at boot, in order. SMTP wins if it is configured; otherwise Mailgun; otherwise email is off.
- SMTP, if
EXO_SMTP_HOSTandEXO_SMTP_FROMare set andEXO_SMTP_PORTis a valid port. - Mailgun, if
EXO_MAILGUN_API_KEYandEXO_MAILGUN_DOMAINare set. - Disabled, if neither is. The backend logs a warning and the email flows no-op.
The startup log tells you which one is live:
1mail: provider=smtp2mail: provider=mailgun domain=mg.example.com3WARNING: EXO_SMTP_* not configured ... self-serve password reset emails disabled until EXO_SMTP_HOST and EXO_SMTP_FROM are setSMTP
Point Exo at any SMTP server. Set the host, a from address, and a port, and add credentials if your relay needs them.
| Variable | Required | Default | Purpose | Example |
|---|---|---|---|---|
EXO_SMTP_HOST | to enable | (none) | SMTP server hostname | smtp.acme.com |
EXO_SMTP_FROM | to enable | (none) | From address (see "From address" below) | Exo <no-reply@acme.com> |
EXO_SMTP_PORT | no | 587 | must be > 0; an invalid value disables SMTP | 587 |
EXO_SMTP_USERNAME | no | (none) | sets PLAIN auth when present | apikey |
EXO_SMTP_PASSWORD | no | (none) | paired with the username | SG.… |
EXO_SMTP_TLS | no | starttls | starttls | tls | none | starttls |
EXO_SMTP_TLS takes one of three values: starttls (the default, the usual choice on port 587), tls for implicit TLS (typically port 465), or none for an unencrypted connection, which is for local testing only. Leave the username blank to connect without auth, for a relay that gates on IP instead.
kubectl -n exo create secret generic exo-smtp \--from-literal=EXO_SMTP_HOST="smtp.example.com" \--from-literal=EXO_SMTP_PORT="587" \--from-literal=EXO_SMTP_FROM="Exo <no-reply@example.com>" \--from-literal=EXO_SMTP_USERNAME="apikey" \--from-literal=EXO_SMTP_PASSWORD="..." \--from-literal=EXO_SMTP_TLS="starttls"
Mailgun
If SMTP is not set, Exo falls back to Mailgun. It sends over Mailgun's HTTP API, so you do not need outbound SMTP ports open. Set your API key and sending domain.
| Variable | Required | Default | Purpose | Example |
|---|---|---|---|---|
EXO_MAILGUN_API_KEY | to enable | (none) | Mailgun private API key | key-3ax6… |
EXO_MAILGUN_DOMAIN | yes | exo.mailer.adaptive.dev | your Mailgun sending domain | mg.acme.com |
EXO_MAILGUN_BASE_URL | no | https://api.mailgun.net | use https://api.eu.mailgun.net for the EU region | https://api.mailgun.net |
EXO_MAILGUN_FROM | no | Exo <no-reply@DOMAIN> | From address | Exo <no-reply@mg.acme.com> |
From address & links
Links in reset and invite emails are built from EXO_BASE_URL. Set it to your public URL or the links will point at localhost. Behind a proxy, see networking.
For the from address, a bare address like no-reply@example.com gets Exo as its display name so inboxes show the product. Supply your own Name <addr> to override it. The Mailgun default already includes a name.
Verify delivery
After you deploy, confirm the startup log shows the provider you expect (mail: provider=smtp or provider=mailgun, not the warning). Then run a real flow: invite a test user, or use forgot-password on a local-auth account, and confirm the message arrives. Reset links last 1 hour; invites last 7 days.