Skip to main content

Email templates

Ory Identities comes with built-in templates for all messages sent by the system. You can replace the default templates with custom ones that can carry your own branding, visual elements, and communication tone.

Built-in templates

When you set the account verification method to code, the system uses the recovery_code.valid template to send the recovery code to the user.

If you enabled sending attempted recovery notifications to unregistered addresses, the system uses the recovery_code.invalid template.

If you set the account recovery method to link, the system uses these templates instead:

  • recovery.valid
  • recovery.invalid

To learn more about the recovery flow, read Account recovery and password reset.

Events that trigger an email

Ory Identities sends an email for each template type below. The template type is also the template_type field in the HTTP delivery payload.

The following list of template types is imported directly from the Ory Kratos source code and stays in sync with it:

https://raw.githubusercontent.com/ory/kratos/master/courier/template/type.go

The following table is maintained by hand. It documents when each email is sent and the condition that gates it. These semantics are not represented in the source file above.

Template typeWhen Ory Identities sends itCondition
recovery_code_validA recovery flow is started for a known address (recovery method code).Always, when recovery uses code.
recovery_code_invalidA recovery flow is started for an address with no account (recovery method code).Only if selfservice.flows.recovery.notify_unknown_recipients is true.
recovery_validA recovery flow is started for a known address (recovery method link).Always, when recovery uses link.
recovery_invalidA recovery flow is started for an address with no account (recovery method link).Only if selfservice.flows.recovery.notify_unknown_recipients is true.
verification_code_validA verification flow is started for a known address (method code).Always, when verification uses code.
verification_code_invalidA verification flow is started for an address with no account (method code).Only if selfservice.flows.verification.notify_unknown_recipients is true.
verification_validA verification flow is started for a known address (method link).Always, when verification uses link.
verification_invalidA verification flow is started for an address with no account (method link).Only if selfservice.flows.verification.notify_unknown_recipients is true.
login_code_validA login flow sends a one-time login code.When the one-time code login method is enabled.
registration_code_validA registration flow sends a one-time registration code.When the one-time code registration method is enabled.
verifiable_address_changedA verifiable address on an identity changes; sent to the previous address(es).Only if the notify_previous_addresses hook is enabled.
stubNot sent in production.Test stub used by the Ory Kratos test suite. It does not correspond to a real email.
note

The stub type appears in the embedded source above because it is part of the TemplateType enum, but it is a test stub used by the Ory Kratos test suite and never produces a real email.

Using custom message templates

Templates can be customized to fit your own branding and requirements. If you don't customize a specific template, the system automatically uses the built-in template.

A custom email server is required to use custom templates. Read more about custom SMTP and HTTP servers.

  1. Go to BrandingEmail templates in the Ory Console
  2. Select the email template you want to customize.

The recovery & verification templates only show the versions for the method (one-time code or link) you have selected in the flow configuration.

  • AuthenticationAccount recovery in the Ory Console
  • AuthenticationAccount verification in the Ory Console

Creating templates

Templates use the Go template engine in the text/template package for rendering the email.subject.gotmpl and email.body.plaintext.gotmpl templates, and the html/template package for rendering the email.body.gotmpl template.

Learn more:

tip

Templates can use the Sprig library, which provides more than 100 commonly used template functions

danger

For security reasons, these Sprig functions are disabled in the Ory Network:

  • Date functions: date,date_in_zone,date_modify, now, htmlDate, htmlDateInZone, dateInZone, dateModify
  • Strings: randAlphaNum, randAlpha, randAscii, randNumeric, uuidv4
  • OS: env, expandenv
  • Network: getHostByName

Available variables

The variables available for use in email templates change depending on the flow and the selected method.

The tables below are maintained by hand. The authoritative list of fields for each template is the per-template model struct in Ory Kratos, under courier/template/email/ — for example recovery_code_valid.go and verification_code_valid.go. If a field is missing below, check the matching struct.

In addition to the variables listed for each template, every template can access TransientPayload (see Transient payload in templates) and RequestURL (the URL of the flow request that triggered the email).

For the recovery_code.valid template, the following variables are available:

VariableDescription
ToThe email address the email will be sent to
RecoveryCodeThe recovery code
IdentityThe identity to be recovered
ExpiresInMinutesthe expiration time of the code in minutes
note

The recovery_code.invalid template does not allow to send a direct link to the user, as the recovery flow enforces anti-CSRF measures, which would lead to the flow failing, in case the user opens the link in a different browser.

For the recovery_code.invalid template, the following variables are available:

VariableDescription
Tothe email address the email will be sent to

Mandatory template formats

Each template must have two versions: html and plaintext.

  • html version uses the HTML syntax to achieve the desired look and functionality (such as clickable buttons) of the email message.
  • plaintext version can't contain any HTML. Must contain only plain text content and any necessary gotmpl logic. This version is used as a fallback when the html version cannot be delivered, for example when the user's mail server blocks HTML in all incoming messages.
courier/template/templates/verification/valid/email.body.gotmpl
Hi, please verify your account by clicking the following link:

<a href="{{ .VerificationURL }}">{{ .VerificationURL }}</a>

Customizing template content for specific users

To enable customizing the content of templates based on the identity of the recipient of the email, the Identity object is available inside the templates. This object is a map containing all the attributes of an identity defined in the identity schema, such as id, state, recovery_addresses, verifiable_addresses and traits.

tip

Read this document to learn more about the Ory Identity and the identity schema.

Translated templates (i18n)

You can use nested templates to render email.subject.gotmpl, email.body.gotmpl and email.body.plaintext.gotmpl templates based on user settings, for example based on their chosen language.

To enable i18n customization of templates, customize the identity schema to include the user's preferred communication language. For example:

Sample custom Identity Schema with user language
{
// ...
"properties": {
"traits": {
"type": "object",
"properties": {
// ...
"lang": {
"type": "string",
"title": "Your preferred language"
},
"required": [
"email"
],
"additionalProperties": false,
}
}
}

This identity trait can then be used inside the template to render a section conditionally.

The following example defines various templates for the recovery_code.valid template and renders the respective template depending on the language set in the lang identity traits, that was defined above:

recovery_code/valid/email.body.gotmpl
{{define "en"}}
Hi,

Please enter the following code to recover your account:

{{ .RecoveryCode }}
{{end}}

{{define "fr"}}
Bonjour,

Veuillez entrer le code suivant pour récupérer votre compte:

{{ .RecoveryCode }}
{{end}}

{{define "de"}}
Hallo,

Bitte geben Sie den folgenden Code ein, um Ihr Konto wiederherzustellen:

{{ .RecoveryURL }}
{{end}}

{{- else if eq .Identity.traits.lang "fr" -}}
{{ template "fr" . }}
{{- else if eq .Identity.traits.lang "de" -}}
{{ template "de" . }}
{{- else -}}
{{ template "en" . }}
{{- end -}}
tip

You can use Sprig functions in the nested templates. For security reasons, some functions are disabled in the Ory Network. See the list of disabled functions here.

Metadata in templates

As an administrator, you can set identity metadata, such as the user's language, in your application code using Identity metadata property. Read this document to learn more.

You can access metadata_public through .Identity.metadata_public in email templates.

The following example requires that the field lang is set in the public metadata. Your application could set this value after user completes registration.

recovery_code/valid/email.body.gotmpl
{{define "en"}}
Hi,

Please enter the following code to recover your account:

{{ .RecoveryCode }}
{{end}}

{{define "fr"}}
Bonjour,

Veuillez entrer le code suivant pour récupérer votre compte:

{{ .RecoveryCode }}
{{end}}

{{define "de"}}
Hallo,

Bitte geben Sie den folgenden Code ein, um Ihr Konto wiederherzustellen:

{{ .RecoveryURL }}
{{end}}

{{- if eq .Identity.metadata_public.lang "fr" -}}
{{ template "fr" . }}
{{- else if eq .Identity.metadata_public.lang "de" -}}
{{ template "de" . }}
{{- else -}}
{{ template "en" . }}
{{- end -}}
danger

Since metadata is not validated by Ory Identities, missing entries or unexpected values can cause errors in the template rendering process. If the system encounters errors in the rendering process, Ory Identities uses the default templates.

Transient payload in templates

The transient payload allows you to pass additional data along with certain self-service flows. Transient payloads can be used for passing temporary information to your email template without storing it permanently.

The following example_key transient payload

...
traits: {
email: "user@example.com",
},
transient_payload: {
example_key: "This is an example value"
},
...

can be accessed in the email template using {{index .TransientPayload "example_key"}}


<h1>Recovery Details</h1>
<p>To: {{.To}}</p>
<p>Recovery URL: {{.RecoveryURL}}</p>
<p>Transient Payload: {{index .TransientPayload "example_key"}}</p>
<p>
{{- if eq (index .TransientPayload "lang") "foo" -}}
FOO
{{- else -}}
BAR
{{- end -}}
</p>

This results into the following email to be sent to the user.

<h1>Recovery Details</h1>
<p>To: user@example.com</p>
<p>Recovery URL: https://example.com/recover</p>
<p>Transient Payload: This is an example value</p>
<p>
BAR
</p>

Building on the mechanic above, this example uses the transient payload to carry presentation data — a brand name and a language — through to the email template. It is useful when one Ory project serves several brands or locales and the email link must match the context the user started in.

The example reads a brand and a language when the flow is created, passes them in transient_payload when the flow is submitted, and reads them back in the template to build a branded link.

  1. Read the values when you create the flow. For example, read the brand from the OAuth2 login request's client name and the language from a URL query parameter:

    const brand = flow.oauth2_login_request?.client?.client_name ?? "Example"
    const lang = new URL(window.location.href).searchParams.get("lang") ?? "en"
  2. Inject the values into transient_payload when you submit the flow. For a registration flow, call updateRegistrationFlow; for a verification flow, call updateVerificationFlow:

    // Registration flow
    await frontend.updateRegistrationFlow({
    flow: flow.id,
    updateRegistrationFlowBody: {
    method: "password",
    traits: { email: "user@example.com" },
    password: "a-secure-password",
    transient_payload: { brand, lang },
    },
    })

    // Verification flow
    await frontend.updateVerificationFlow({
    flow: flow.id,
    updateVerificationFlowBody: {
    method: "code",
    email: "user@example.com",
    transient_payload: { brand, lang },
    },
    })

    For the underlying request shapes, see Submit registration flow.

  3. Read the values in the email template and build a branded, localized link:

    {{ $brand := index .TransientPayload "brand" }}
    {{ $lang := index .TransientPayload "lang" }}

    <p>Verify your {{ $brand }} account:</p>
    <a href="https://accounts.example.com/{{ $lang }}/verify?code={{ .VerificationCode }}"> Verify your email </a>

Because the payload lives only for the duration of the flow, supply these values again on every flow submission.