Skip to content
Merged
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
38 changes: 19 additions & 19 deletions contents/docs/workflows/transactional-emails/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ You need a [transactional email](/docs/workflows/transactional-emails#create-a-t
POST https://us.posthog.com/api/projects/@current/messaging/send/email/
```

- **Host** - Use `https://us.posthog.com` or `https://eu.posthog.com`, the host where you log in to PostHog. Don't use an ingestion host like `us.i.posthog.com`. If you self-host PostHog, use the URL of your instance.
- **Project** - `@current` is the project that the key belongs to. You can use your project ID instead.
- **Host** – Use `https://us.posthog.com` or `https://eu.posthog.com`, the host where you log in to PostHog. Don't use an ingestion host like `us.i.posthog.com`. If you self-host PostHog, use the URL of your instance.
- **Project** – `@current` is the project that the key belongs to. You can use your project ID instead.

### Request

Expand Down Expand Up @@ -178,8 +178,8 @@ A `503` with the code `delivery_unavailable` means that PostHog couldn't confirm

Pass the distinct ID that you pass to `identify()` as `distinct_id`. This is usually your user's ID. PostHog then records the email's [engagement events](/docs/workflows/engagement-events), such as sent, opened, and clicked, on that user's person, in the same timeline as their product events.

- **No distinct ID** - PostHog records the email on a person whose distinct ID is the email address. That's a different person from your user, unless your app identifies users by their email address.
- **Not a user yet** - For an invite, or an email address check before the account exists, leave `distinct_id` out.
- **No distinct ID** – PostHog records the email on a person whose distinct ID is the email address. That's a different person from your user, unless your app identifies users by their email address.
- **Not a user yet** – For an invite, or an email address check before the account exists, leave `distinct_id` out.

## Send content from your code

Expand Down Expand Up @@ -327,14 +327,14 @@ $result = PostHog::emails()->send([

Mark the email as sensitive when it carries login links, reset tokens, or codes. For a sensitive email, PostHog:

- **Keeps links unchanged** - PostHog doesn't rewrite the email's links to count clicks, so a token in a link never goes through a tracking redirect. [`tracking`](#open-and-click-tracking) can still turn clicks on.
- **Leaves the subject off engagement events** - The email's [engagement events](/docs/workflows/engagement-events) don't carry its subject.
- **Keeps no copy of the content** - PostHog doesn't keep the email's subject or body.
- **Keeps links unchanged** – PostHog doesn't rewrite the email's links to count clicks, so a token in a link never goes through a tracking redirect. [`tracking`](#open-and-click-tracking) can still turn clicks on.
- **Leaves the subject off engagement events** – The email's [engagement events](/docs/workflows/engagement-events) don't carry its subject.
- **Keeps no copy of the content** – PostHog doesn't keep the email's subject or body.

`sensitive` takes `true` or `false`. If you leave it out:

- **A template has the same key** - When a template in your project has the key that you send as `template_key`, the email takes that template's [**Protect sensitive data**](/docs/workflows/transactional-emails#protect-sensitive-data) setting.
- **No template has the key** - The email is sensitive.
- **A template has the same key** – When a template in your project has the key that you send as `template_key`, the email takes that template's [**Protect sensitive data**](/docs/workflows/transactional-emails#protect-sensitive-data) setting.
- **No template has the key** – The email is sensitive.

Send `"sensitive": false` for an email that carries no secrets, such as an order confirmation. PostHog then tracks its clicks too, as it does for a template without **Protect sensitive data**. `sensitive` doesn't change opens or engagement events.

Expand All @@ -359,19 +359,19 @@ Send `"sensitive": false` for an email that carries no secrets, such as an order
| `false` | Off | Off | Off |
| An object | `opens`, on by default | `clicks`, default follows `sensitive` | `engagement`, on by default |

- **Opens** - PostHog adds an open pixel to the email, so it can count opens.
- **Clicks** - PostHog rewrites the email's links to go through a tracking redirect, so it can count clicks.
- **Engagement events** - PostHog captures the email's [engagement events](/docs/workflows/engagement-events), such as sent, opened, and clicked, on the recipient's person. If the project's **Capture email engagement events** setting is off, PostHog captures nothing, whatever you send.
- **Opens** – PostHog adds an open pixel to the email, so it can count opens.
- **Clicks** – PostHog rewrites the email's links to go through a tracking redirect, so it can count clicks.
- **Engagement events** – PostHog captures the email's [engagement events](/docs/workflows/engagement-events), such as sent, opened, and clicked, on the recipient's person. If the project's **Capture email engagement events** setting is off, PostHog captures nothing, whatever you send.

A key that you leave out of the object keeps its default, so `{}` is the same as leaving out `tracking`.

#### Clicks and sensitive emails

When you leave out `clicks`, PostHog tracks clicks only for an email that isn't [sensitive](#sensitive-emails). A tracked link goes through a redirect, so a login link or a reset token in it would go through the redirect too.

- **An email without secrets** - Send `"sensitive": false`, and PostHog tracks its clicks.
- **A sensitive email with safe links** - Send `"tracking": {"clicks": true}`, or `"tracking": true`. PostHog tracks its clicks, and keeps the email's other protections.
- **An email that isn't sensitive** - Send `"tracking": {"clicks": false}` to keep its links unchanged.
- **An email without secrets** – Send `"sensitive": false`, and PostHog tracks its clicks.
- **A sensitive email with safe links** – Send `"tracking": {"clicks": true}`, or `"tracking": true`. PostHog tracks its clicks, and keeps the email's other protections.
- **An email that isn't sensitive** – Send `"tracking": {"clicks": false}` to keep its links unchanged.

A `clicks` value that you send wins over `sensitive`. To keep one link as it is, add `data-ph-no-track` or `clicktracking="off"` to its `<a>` tag, as for [workflow emails](/docs/workflows/library#disabling-link-click-tracking).

Expand All @@ -395,10 +395,10 @@ In a form-encoded request, `tracking` and `sensitive` take `true` or `false`. Se

PostHog returns a `400` and sends nothing when:

- **An unknown key** - The object has a key other than `opens`, `clicks`, and `engagement`.
- **Clicks without opens** - `opens` is `false` and `clicks` is `true`. Click tracking needs open tracking. Turn on `opens`, or turn off `clicks`. With `opens` set to `false` and `clicks` left out, PostHog tracks no clicks, whatever `sensitive` says.
- **Both fields** - The request has `tracking` and `capture_engagement`. Move the value of `capture_engagement` to `tracking.engagement`.
- **A template send** - The request has `template_id` and `tracking` or `sensitive`. A template uses its own settings.
- **An unknown key** – The object has a key other than `opens`, `clicks`, and `engagement`.
- **Clicks without opens** – `opens` is `false` and `clicks` is `true`. Click tracking needs open tracking. Turn on `opens`, or turn off `clicks`. With `opens` set to `false` and `clicks` left out, PostHog tracks no clicks, whatever `sensitive` says.
- **Both fields** – The request has `tracking` and `capture_engagement`. Move the value of `capture_engagement` to `tracking.engagement`.
- **A template send** – The request has `template_id` and `tracking` or `sensitive`. A template uses its own settings.

### Copies and attachments

Expand Down
24 changes: 12 additions & 12 deletions contents/docs/workflows/transactional-emails/dotnet.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The [.NET SDK](/docs/libraries/dotnet) sends transactional emails from version 2
<details>
<summary>Prompt for your coding agent</summary>

Paste this into Claude Code, Cursor, or another coding agent. It sends the agent to this page and gives it the rules that are easy to get wrong.
Paste this into Claude Code, Cursor, or another coding agent. It sends the agent to this page and gives it the rules that agents often get wrong.

```text
Add PostHog transactional email to this .NET app with the PostHog NuGet package, or PostHog.AspNetCore for ASP.NET Core.
Expand Down Expand Up @@ -181,8 +181,8 @@ BackgroundJob.Enqueue<PasswordResetEmailJob>(job =>

Pass the distinct ID that you pass to `IdentifyAsync` as `DistinctId`. This is usually your user's ID. PostHog then records the email's [engagement events](/docs/workflows/engagement-events), such as sent, opened, and clicked, on that user's person, in the same timeline as their product events.

- **No distinct ID** - PostHog records the email on a person whose distinct ID is the email address. That's a different person from your user, unless your app identifies users by their email address.
- **Not a user yet** - For an invite, or an email address check before the account exists, leave `DistinctId` out.
- **No distinct ID** – PostHog records the email on a person whose distinct ID is the email address. That's a different person from your user, unless your app identifies users by their email address.
- **Not a user yet** – For an invite, or an email address check before the account exists, leave `DistinctId` out.

## Send content from your code

Expand All @@ -205,12 +205,12 @@ public async Task<string> SendPasswordResetAsync(User user, string html, string
}
```

- **`TemplateKey`** - Names the email, such as `password-reset`. PostHog groups the email's metrics and sends under this key, so use the same key every time you send this email.
- **`Text`** - If you leave it out, PostHog makes it from `Html`.
- **`From`** - Needs an address on a domain that your project verified. Without it, PostHog uses the project's default sender. `ReplyTo` takes a list of addresses.
- **`Sensitive`** - Marks the email as sensitive, for login links, reset tokens, or codes. If you leave it out, the email takes the setting of the template with the same key, or is sensitive when no template has that key. See [sensitive emails](/docs/workflows/transactional-emails/api#sensitive-emails).
- **`Tracking`** - What PostHog tracks for the email. If you leave it out, PostHog tracks opens and captures engagement events, and tracks clicks only when the email isn't sensitive. See [open and click tracking](#open-and-click-tracking).
- **`CaptureEngagement`** - Deprecated. Set `Tracking = new EmailTracking { Engagement = false }` instead. It still works, but a send that sets both `CaptureEngagement` and `Tracking` gets an error.
- **`TemplateKey`** – Names the email, such as `password-reset`. PostHog groups the email's metrics and sends under this key, so use the same key every time you send this email.
- **`Text`** – If you leave it out, PostHog makes it from `Html`.
- **`From`** – Needs an address on a domain that your project verified. Without it, PostHog uses the project's default sender. `ReplyTo` takes a list of addresses.
- **`Sensitive`** – Marks the email as sensitive, for login links, reset tokens, or codes. If you leave it out, the email takes the setting of the template with the same key, or is sensitive when no template has that key. See [sensitive emails](/docs/workflows/transactional-emails/api#sensitive-emails).
- **`Tracking`** – What PostHog tracks for the email. If you leave it out, PostHog tracks opens and captures engagement events, and tracks clicks only when the email isn't sensitive. See [open and click tracking](#open-and-click-tracking).
- **`CaptureEngagement`** – Deprecated. Set `Tracking = new EmailTracking { Engagement = false }` instead. It still works, but a send that sets both `CaptureEngagement` and `Tracking` gets an error.

`SendTemplateEmailOptions` sends a template made in PostHog, and `SendCodeEmailOptions` sends your content. Content from your code takes no template data, because PostHog renders nothing. See [content from your code](/docs/workflows/transactional-emails/api#send-content-from-your-code) for each field and its checks.

Expand All @@ -227,9 +227,9 @@ public async Task<string> SendPasswordResetAsync(User user, string html, string
| `EmailTracking.None` | Off | Off | Off |
| `new EmailTracking { ... }` | `Opens`, on by default | `Clicks`, default follows `Sensitive` | `Engagement`, on by default |

- **Opens** - PostHog adds an open pixel to the email, so it can count opens.
- **Clicks** - PostHog rewrites the email's links to go through a tracking redirect, so it can count clicks.
- **Engagement events** - PostHog captures the email's engagement events on the recipient's person. If the project's **Capture email engagement events** setting is off, PostHog captures nothing, whatever the send says.
- **Opens** – PostHog adds an open pixel to the email, so it can count opens.
- **Clicks** – PostHog rewrites the email's links to go through a tracking redirect, so it can count clicks.
- **Engagement events** – PostHog captures the email's engagement events on the recipient's person. If the project's **Capture email engagement events** setting is off, PostHog captures nothing, whatever the send says.

If you leave out clicks, PostHog tracks them only when the email isn't sensitive, because a login link or a reset token in a tracked link would go through a redirect. Set `Sensitive = false` for an email that carries no secrets. For a sensitive email with links that are safe to rewrite, set `Tracking = new EmailTracking { Clicks = true }`. A clicks value in `Tracking` wins over `Sensitive`.

Expand Down
24 changes: 12 additions & 12 deletions contents/docs/workflows/transactional-emails/elixir.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The [Elixir SDK](/docs/libraries/elixir) sends transactional emails from version
<details>
<summary>Prompt for your coding agent</summary>

Paste this into Claude Code, Cursor, or another coding agent. It sends the agent to this page and gives it the rules that are easy to get wrong.
Paste this into Claude Code, Cursor, or another coding agent. It sends the agent to this page and gives it the rules that agents often get wrong.

```text
Add PostHog transactional email to this Elixir app with the posthog Hex package.
Expand Down Expand Up @@ -164,8 +164,8 @@ Add the job where your app creates the reset request:

Pass the distinct ID that you pass to `identify()` as `distinct_id`, as a string. This is usually your user's ID. PostHog then records the email's [engagement events](/docs/workflows/engagement-events), such as sent, opened, and clicked, on that user's person, in the same timeline as their product events. The SDK doesn't take it from `PostHog.set_context/2`.

- **No distinct ID** - PostHog records the email on a person whose distinct ID is the email address. That's a different person from your user, unless your app identifies users by their email address.
- **Not a user yet** - For an invite, or an email address check before the account exists, leave `distinct_id` out.
- **No distinct ID** – PostHog records the email on a person whose distinct ID is the email address. That's a different person from your user, unless your app identifies users by their email address.
- **Not a user yet** – For an invite, or an email address check before the account exists, leave `distinct_id` out.

## Send content from your code

Expand All @@ -188,12 +188,12 @@ def send_password_reset(user, html, reset_request_id) do
end
```

- **`template_key`** - Names the email, such as `password-reset`. PostHog groups the email's metrics and sends under this key, so use the same key every time you send this email.
- **`text`** - If you leave it out, PostHog makes it from `html`.
- **`from`** - Needs an address on a domain that your project verified. Without it, PostHog uses the project's default sender. `reply_to` takes one address or a list.
- **`sensitive`** - Marks the email as sensitive, for login links, reset tokens, or codes. If you leave it out, the email takes the setting of the template with the same key, or is sensitive when no template has that key. See [sensitive emails](/docs/workflows/transactional-emails/api#sensitive-emails).
- **`tracking`** - What PostHog tracks for the email. If you leave it out, PostHog tracks opens and captures engagement events, and tracks clicks only when the email isn't sensitive. See [open and click tracking](#open-and-click-tracking).
- **`capture_engagement`** - Deprecated. Pass `tracking: %{engagement: false}` instead. It still works, but a send that sets both `capture_engagement` and `tracking` gets an error.
- **`template_key`** – Names the email, such as `password-reset`. PostHog groups the email's metrics and sends under this key, so use the same key every time you send this email.
- **`text`** – If you leave it out, PostHog makes it from `html`.
- **`from`** – Needs an address on a domain that your project verified. Without it, PostHog uses the project's default sender. `reply_to` takes one address or a list.
- **`sensitive`** – Marks the email as sensitive, for login links, reset tokens, or codes. If you leave it out, the email takes the setting of the template with the same key, or is sensitive when no template has that key. See [sensitive emails](/docs/workflows/transactional-emails/api#sensitive-emails).
- **`tracking`** – What PostHog tracks for the email. If you leave it out, PostHog tracks opens and captures engagement events, and tracks clicks only when the email isn't sensitive. See [open and click tracking](#open-and-click-tracking).
- **`capture_engagement`** – Deprecated. Pass `tracking: %{engagement: false}` instead. It still works, but a send that sets both `capture_engagement` and `tracking` gets an error.

A send has `template_id` or `template_key`, not both. Content from your code takes no `template_data`, because PostHog renders nothing. See [content from your code](/docs/workflows/transactional-emails/api#send-content-from-your-code) for each field and its checks.

Expand All @@ -210,9 +210,9 @@ A send has `template_id` or `template_key`, not both. Content from your code tak
| `tracking: false` | Off | Off | Off |
| A map, such as `tracking: %{clicks: true}` | `:opens`, on by default | `:clicks`, default follows `sensitive` | `:engagement`, on by default |

- **Opens** - PostHog adds an open pixel to the email, so it can count opens.
- **Clicks** - PostHog rewrites the email's links to go through a tracking redirect, so it can count clicks.
- **Engagement events** - PostHog captures the email's engagement events on the recipient's person. If the project's **Capture email engagement events** setting is off, PostHog captures nothing, whatever the send says.
- **Opens** – PostHog adds an open pixel to the email, so it can count opens.
- **Clicks** – PostHog rewrites the email's links to go through a tracking redirect, so it can count clicks.
- **Engagement events** – PostHog captures the email's engagement events on the recipient's person. If the project's **Capture email engagement events** setting is off, PostHog captures nothing, whatever the send says.

If you leave out clicks, PostHog tracks them only when the email isn't sensitive, because a login link or a reset token in a tracked link would go through a redirect. Pass `sensitive: false` for an email that carries no secrets. For a sensitive email with links that are safe to rewrite, pass `tracking: %{clicks: true}`. A clicks value in `tracking` wins over `sensitive`.

Expand Down
Loading
Loading