Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
70 changes: 43 additions & 27 deletions docs/kratos/emails-sms/01_sending-emails-smtp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -428,39 +429,54 @@ service that doesn't provide an SMTP server.
</Tabs>
```

### 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:
</TabItem>
</Tabs>

```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.

</TabItem>
</Tabs>
<CodeFromRemote
lang="go"
link="https://github.com/ory/kratos/blob/master/courier/http_channel.go"
src="https://raw.githubusercontent.com/ory/kratos/master/courier/http_channel.go"
startAt="type httpDataModel struct {"
endAt="}"
/>

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`.

<CodeFromRemote
lang="jsonnet"
link="https://github.com/ory/kratos/blob/master/courier/stub/request.config.mailer.jsonnet"
src="https://raw.githubusercontent.com/ory/kratos/master/courier/stub/request.config.mailer.jsonnet"
/>

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

Expand Down
127 changes: 126 additions & 1 deletion docs/kratos/emails-sms/05_custom-email-templates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -81,6 +82,47 @@ To learn more about registration via a one-time code, read the [one-time code](.
</Tabs>
```

## 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:

<CodeFromRemote
lang="go"
link="https://github.com/ory/kratos/blob/master/courier/template/type.go"
src="https://raw.githubusercontent.com/ory/kratos/master/courier/template/type.go"
startAt="const ("
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.

| 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
Expand Down Expand Up @@ -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
<Tabs groupId="email-templates">
Expand Down Expand Up @@ -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 |

</TabItem>

<TabItem value="verifiable_address_changed" label="Address changed">

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).

</TabItem>
</Tabs>
```
Expand Down Expand Up @@ -542,3 +609,61 @@ This results into the following email to be sent to the user.
BAR
</p>
```

#### 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" }}

<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.
Loading