From b4efd70da699478ced45bced63426ab01680f862 Mon Sep 17 00:00:00 2001 From: vinckr Date: Mon, 29 Jun 2026 12:36:17 +0200 Subject: [PATCH 1/5] fix: transient payload example --- .../05_custom-ui-basic-integration.mdx | 4 +- .../emails-sms/01_sending-emails-smtp.mdx | 71 ++++++---- .../emails-sms/05_custom-email-templates.mdx | 126 +++++++++++++++++- 3 files changed, 172 insertions(+), 29 deletions(-) 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..57342563cb 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,55 @@ 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 following embed stays in sync with the source: - - + + +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`. +This embed stays in sync with the source: + + + +In most cases, an example like this is sufficient. 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..158bafa38e 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,46 @@ 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 is also the `template_type` field in the +[HTTP delivery payload](./01_sending-emails-smtp.mdx#payload). + +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 +253,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 +370,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 +608,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. From 6a38142259b2acd4373ec43a7608bf92e293531d Mon Sep 17 00:00:00 2001 From: vinckr Date: Mon, 29 Jun 2026 12:37:13 +0200 Subject: [PATCH 2/5] chore: format --- docs/kratos/emails-sms/05_custom-email-templates.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/kratos/emails-sms/05_custom-email-templates.mdx b/docs/kratos/emails-sms/05_custom-email-templates.mdx index 158bafa38e..7644ff87d9 100644 --- a/docs/kratos/emails-sms/05_custom-email-templates.mdx +++ b/docs/kratos/emails-sms/05_custom-email-templates.mdx @@ -97,8 +97,8 @@ The following list of template types is imported directly from the Ory Kratos so endAt=")" /> -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. +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 | | ---------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | @@ -611,8 +611,8 @@ This results into the following email to be sent to the user. #### 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 +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, From 0187500a64c08d6241efd1fb27785769df08d992 Mon Sep 17 00:00:00 2001 From: vinckr Date: Mon, 29 Jun 2026 13:03:13 +0200 Subject: [PATCH 3/5] chore: suggestion --- docs/kratos/emails-sms/05_custom-email-templates.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/kratos/emails-sms/05_custom-email-templates.mdx b/docs/kratos/emails-sms/05_custom-email-templates.mdx index 7644ff87d9..323c7b7b05 100644 --- a/docs/kratos/emails-sms/05_custom-email-templates.mdx +++ b/docs/kratos/emails-sms/05_custom-email-templates.mdx @@ -84,8 +84,9 @@ 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 is also the `template_type` field in the -[HTTP delivery payload](./01_sending-emails-smtp.mdx#payload). +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: From 0dd9cb9bc0c9bcc5a3bdd3ece4c78c9ec6968321 Mon Sep 17 00:00:00 2001 From: vinckr Date: Mon, 29 Jun 2026 14:20:43 +0200 Subject: [PATCH 4/5] chore: format --- docs/kratos/emails-sms/01_sending-emails-smtp.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/kratos/emails-sms/01_sending-emails-smtp.mdx b/docs/kratos/emails-sms/01_sending-emails-smtp.mdx index 57342563cb..9474c9aff5 100644 --- a/docs/kratos/emails-sms/01_sending-emails-smtp.mdx +++ b/docs/kratos/emails-sms/01_sending-emails-smtp.mdx @@ -442,7 +442,7 @@ template. The template receives a `ctx` object that Ory Identities constructs fo ### The `ctx` object -The shape of `ctx` is defined by the `httpDataModel` struct in Ory Kratos. The following embed stays in sync with the source: +The shape of `ctx` is defined by the `httpDataModel` struct in Ory Kratos. -In most cases, an example like this is sufficient. 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`. +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 From 50b9b5fc940b7e20f352f85036b1602ff1aba748 Mon Sep 17 00:00:00 2001 From: vinckr Date: Mon, 29 Jun 2026 14:22:34 +0200 Subject: [PATCH 5/5] chore: format --- docs/kratos/emails-sms/05_custom-email-templates.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/kratos/emails-sms/05_custom-email-templates.mdx b/docs/kratos/emails-sms/05_custom-email-templates.mdx index 323c7b7b05..b72ee16ee0 100644 --- a/docs/kratos/emails-sms/05_custom-email-templates.mdx +++ b/docs/kratos/emails-sms/05_custom-email-templates.mdx @@ -612,7 +612,7 @@ This results into the following email to be sent to the user. #### 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 — +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.