diff --git a/docs/kratos/bring-your-own-ui/05_custom-ui-basic-integration.mdx b/docs/kratos/bring-your-own-ui/05_custom-ui-basic-integration.mdx
index cf726cbfb3..6d22eaa86a 100644
--- a/docs/kratos/bring-your-own-ui/05_custom-ui-basic-integration.mdx
+++ b/docs/kratos/bring-your-own-ui/05_custom-ui-basic-integration.mdx
@@ -756,7 +756,9 @@ the POST request payload. To learn more about customizing your identity schema p
For more complex use cases you can pass more data to the flow using the optional `transient_payload` field. This data gets
forwarded to webhooks without being persisted by Ory like identity traits do. To learn how to configure and use a webhook, please
-have a look at the [Webhooks](../../guides/integrate-with-ory-cloud-through-webhooks.mdx) documentation.
+have a look at the [Webhooks](../../guides/integrate-with-ory-cloud-through-webhooks.mdx) documentation. The transient payload
+also reaches email templates: for a worked example that uses it to build branded, localized links, see
+[Transient payload in templates](../emails-sms/05_custom-email-templates.mdx#transient-payload-in-templates).
For browser applications you must send all cookies and the CSRF token in the request body. The CSRF token value is a hidden input
field called `csrf_token`.
diff --git a/docs/kratos/emails-sms/01_sending-emails-smtp.mdx b/docs/kratos/emails-sms/01_sending-emails-smtp.mdx
index 8038e2d544..9474c9aff5 100644
--- a/docs/kratos/emails-sms/01_sending-emails-smtp.mdx
+++ b/docs/kratos/emails-sms/01_sending-emails-smtp.mdx
@@ -7,6 +7,7 @@ title: Email delivery configuration
import Tabs from "@theme/Tabs"
import TabItem from "@theme/TabItem"
import CodeBlock from "@theme/CodeBlock"
+import CodeFromRemote from "@theme/CodeFromRemote"
```
The Ory Network provides a default SMTP server for sending emails. Ory emails are sent from this address:
@@ -428,39 +429,54 @@ service that doesn't provide an SMTP server.
```
-### Payload
+The body is built from a Jsonnet template. See [Payload](#payload) for the `ctx` object the template receives and an example
+template.
-The payload of the HTTP request is a JSON object that's generated using a Jsonnet template. By default, the following Jsonnet
-template is used:
+
+
-```jsonnet
-function(ctx) {
- recipient: ctx.recipient,
- template_type: ctx.template_type,
- to: if "template_data" in ctx && "to" in ctx.template_data then ctx.template_data.to else null,
- recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null,
- recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null,
- verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null,
- verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null,
- login_code: if "template_data" in ctx && "login_code" in ctx.template_data then ctx.template_data.login_code else null,
- registration_code: if "template_data" in ctx && "registration_code" in ctx.template_data then ctx.template_data.registration_code else null,
- subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null,
- body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null
-}
-```
+## Payload {#payload}
-The courier passes the following object as the `ctx` parameter into the Jsonnet template:
+When you use the HTTP delivery strategy, Ory Identities sends each email as an HTTP request whose body is built from a Jsonnet
+template. The template receives a `ctx` object that Ory Identities constructs for every message.
-| Variable | Type | Description |
-| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `recipient` | String | The email address of the recipient. |
-| `template_type` | String | The type of the template. See the [list of available templates for a full list](./05_custom-email-templates.mdx#built-in-templates) |
-| `template_data` | Object | The data that should be included in the email. See the [list of variables for each template for a full list](./05_custom-email-templates.mdx#available-variables). |
+### The `ctx` object
-In most cases, the default payload should be sufficient.
+The shape of `ctx` is defined by the `httpDataModel` struct in Ory Kratos.
-
-
+
+
+The JSON tags on the struct are the field names available in `ctx`:
+
+| Field | Type | Description |
+| ----------------- | ------ | -------------------------------------------------------------------------------------------------------------------- |
+| `recipient` | String | The email address of the recipient. |
+| `subject` | String | The rendered email subject. |
+| `body` | String | The rendered plaintext body. |
+| `html_body` | String | The rendered HTML body, when available. |
+| `template_type` | String | The template type. See [Events that trigger an email](./05_custom-email-templates.mdx#events-that-trigger-an-email). |
+| `template_data` | Object | The per-template variables. See [Available variables](./05_custom-email-templates.mdx#available-variables). |
+| `message_type` | String | The message channel, for example `email`. |
+| `request_headers` | Object | The HTTP headers from the flow request that triggered the email. |
+
+### Example Jsonnet template
+
+The following template is the example mailer configuration shipped with Ory Kratos. It maps the most common fields out of `ctx`.
+
+
+
+Read fields that the template you target needs from `ctx.template_data`, and read top-level fields such as `recipient` and
+`template_type` directly from `ctx`.
## Troubleshooting
diff --git a/docs/kratos/emails-sms/05_custom-email-templates.mdx b/docs/kratos/emails-sms/05_custom-email-templates.mdx
index 06fa7ff3c9..b72ee16ee0 100644
--- a/docs/kratos/emails-sms/05_custom-email-templates.mdx
+++ b/docs/kratos/emails-sms/05_custom-email-templates.mdx
@@ -10,6 +10,7 @@ sidebar_label: Email templates
import Tabs from "@theme/Tabs"
import TabItem from "@theme/TabItem"
import CodeBlock from "@theme/CodeBlock"
+import CodeFromRemote from "@theme/CodeFromRemote"
```
Ory Identities comes with built-in templates for all messages sent by the system. You can replace the default templates with
@@ -81,6 +82,47 @@ To learn more about registration via a one-time code, read the [one-time code](.
```
+## Events that trigger an email
+
+Ory Identities sends an email for each template type below. The template type (for example `recovery_code_valid`) is the value of
+the `template_type` field in the [HTTP delivery payload](./01_sending-emails-smtp.mdx#payload). It differs from the template names
+used elsewhere on this page (for example `recovery_code.valid`), which identify the template directory and layout.
+
+The following list of template types is imported directly from the Ory Kratos source code and stays in sync with it:
+
+
+
+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 type | When Ory Identities sends it | Condition |
+| ---------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
+| `recovery_code_valid` | A recovery flow is started for a known address (recovery method `code`). | Always, when recovery uses `code`. |
+| `recovery_code_invalid` | A recovery flow is started for an address with no account (recovery method `code`). | Only if `selfservice.flows.recovery.notify_unknown_recipients` is `true`. |
+| `recovery_valid` | A recovery flow is started for a known address (recovery method `link`). | Always, when recovery uses `link`. |
+| `recovery_invalid` | A 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_valid` | A verification flow is started for a known address (method `code`). | Always, when verification uses `code`. |
+| `verification_code_invalid` | A verification flow is started for an address with no account (method `code`). | Only if `selfservice.flows.verification.notify_unknown_recipients` is `true`. |
+| `verification_valid` | A verification flow is started for a known address (method `link`). | Always, when verification uses `link`. |
+| `verification_invalid` | A verification flow is started for an address with no account (method `link`). | Only if `selfservice.flows.verification.notify_unknown_recipients` is `true`. |
+| `login_code_valid` | A login flow sends a one-time login code. | When the one-time code login method is enabled. |
+| `registration_code_valid` | A registration flow sends a one-time registration code. | When the one-time code registration method is enabled. |
+| `verifiable_address_changed` | A verifiable address on an identity changes; sent to the previous address(es). | Only if the `notify_previous_addresses` hook is enabled. |
+| `stub` | Not 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
@@ -212,7 +254,17 @@ For security reasons, these Sprig functions are disabled in the Ory Network:
### Available variables
-The variables available for use in email templates change depending on the flow and the selected method:
+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/`](https://github.com/ory/kratos/tree/master/courier/template/email) — for example
+[`recovery_code_valid.go`](https://github.com/ory/kratos/blob/master/courier/template/email/recovery_code_valid.go) and
+[`verification_code_valid.go`](https://github.com/ory/kratos/blob/master/courier/template/email/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](#transient-payload-in-templates)) and `RequestURL` (the URL of the flow request that triggered
+the email).
```mdx-code-block
@@ -319,6 +371,21 @@ For the `registration_code.valid` template, the following variables are availabl
| `Traits` | the provided traits as specified by the Identity Schema |
| `ExpiresInMinutes` | the expiration time of the code in minutes |
+
+
+
+
+For the `verifiable_address_changed` template, the following variables are available:
+
+| Variable | Description |
+| --- | --- |
+| `To` | the previous address the notification is sent to |
+| `Identity` | the identity whose verifiable address changed |
+| `ChangedAt` | the time the address changed |
+
+This template is sent only when the `notify_previous_addresses` hook is enabled. See
+[Events that trigger an email](#events-that-trigger-an-email).
+
```
@@ -542,3 +609,61 @@ This results into the following email to be sent to the user.
BAR
```
+
+#### Pass brand and language to email links
+
+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:
+
+ ```ts
+ 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`:
+
+ ```ts
+ // 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](../bring-your-own-ui/05_custom-ui-basic-integration.mdx#submit-registration-flow).
+
+3. Read the values in the email template and build a branded, localized link:
+
+ ```gotmpl
+ {{ $brand := index .TransientPayload "brand" }}
+ {{ $lang := index .TransientPayload "lang" }}
+
+ Verify your {{ $brand }} account:
+ Verify your email
+ ```
+
+Because the payload lives only for the duration of the flow, supply these values again on every flow submission.