Olympus Docs
CookbookUI & content

Custom email templates

Customize verification, recovery, and notification email content

Kratos's courier ships built-in plain-text and HTML templates for: verification, recovery, and notification emails. Customize per-deployment.

Where they live

Templates are mounted into the Kratos container at /etc/config/kratos/courier/template/:

/etc/config/kratos/courier/template/
├── verification/code/valid/
│   ├── email.body.gotmpl
│   └── email.subject.gotmpl
├── recovery/code/valid/
│   ├── email.body.gotmpl
│   └── email.subject.gotmpl
├── recovery_code/valid/
│   ├── email.body.gotmpl
│   └── email.body.html.gotmpl
│   └── email.subject.gotmpl
└── ...

.gotmpl files are Go templates with access to flow-specific variables ({{ .RecoveryURL }}, {{ .Identity.Traits.email }}, etc.).

Customize

In your platform fork, edit:

  • prod/ciam-kratos/template/recovery_code/valid/email.body.html.gotmpl
  • prod/ciam-kratos/template/recovery_code/valid/email.subject.gotmpl

Apply branding, change wording, add support contact.

Example body template:

<!DOCTYPE html>
<html>
<body style="font-family: sans-serif">
  <h2>Reset your password</h2>
  <p>Hi,</p>
  <p>Use this code to reset your password:</p>
  <p style="font-size: 24px; font-weight: bold">{{ .RecoveryCode }}</p>
  <p>If you didn't request this, ignore this email.</p>
  <p>— Your app team</p>
</body>
</html>

Mount via compose.prod.yml:

ciam-kratos:
  volumes:
    - ./prod/ciam-kratos/template:/etc/config/kratos/courier/template:ro

Kratos reloads templates on SIGHUP via the reload sidecar.

Available variables

Per flow, you get different vars. Common:

  • .RecoveryURL, the verification / recovery link.
  • .RecoveryCode, short-code variant.
  • .Identity.Traits.email, recipient email.
  • .Identity.Traits.name.first, first name (if in schema).
  • .AppName, configurable in kratos.yml.

Refer to Ory Kratos docs for the full list per flow type.

Per-domain customization

CIAM and IAM Kratos have separate template dirs. Customer-facing CIAM emails should differ in branding from internal IAM emails.

Localization

If you need multiple languages:

  1. Detect language from the identity (identity.traits.locale: "fr-FR").
  2. Use Go template conditional:
{{ if eq .Identity.Traits.locale "fr-FR" }}
  Bonjour, votre code: {{ .RecoveryCode }}
{{ else }}
  Hi, your code: {{ .RecoveryCode }}
{{ end }}

For many languages, this gets verbose. Consider an inline dict lookup or a custom Go function (Kratos's templating.funcs extension).

Testing locally

In dev, emails go to MailSlurper. Trigger a recovery flow:

# As any user, click "Forgot password" in Hera
# Then in MailSlurper, view the captured email
open http://localhost:5434

See the rendered template. Iterate until it looks right.

Plain text fallback

Always provide both email.body.gotmpl (plain) and email.body.html.gotmpl (HTML). Some clients display plain; some block HTML. Both should convey the recovery code / link.

On this page