Skip to content
11 changes: 2 additions & 9 deletions content/integrations/email/client-previews.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,9 @@ layout: integrations

<Callout
type="enterprise"
title="Enterprise plan feature."
style={{ alignItems: "center" }}
title="Paid plan feature."
text={
<>
Email client previews are currently only available on our{" "}
Comment thread
cursor[bot] marked this conversation as resolved.
<a href="https://knock.app/pricing" target="_blank" rel="noopener">
Enterprise plan
</a>
.
</>
<>Email client previews are available on all paid plans (Starter and up).</>
}
/>

Expand Down
62 changes: 58 additions & 4 deletions content/integrations/email/layouts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,16 @@ Inject account- and environment-level variables into your layout with the `vars.

Branding properties set in account settings are available under `vars.branding.*`:

- `vars.branding.logo_url`
- `vars.branding.icon_url`
- `vars.branding.primary_color`
- `vars.branding.primary_color_contrast`
| Variable | Description |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `vars.branding.logo_url` | URL of the logo image for your brand. |
| `vars.branding.icon_url` | URL of the icon image for your brand. |
| `vars.branding.dark_logo_url` | URL of the logo image for dark mode. Defaults to `logo_url` if not set. |
| `vars.branding.dark_icon_url` | URL of the icon image for dark mode. Defaults to `icon_url` if not set. |
| `vars.branding.primary_color` | Primary brand color (hex). Defaults to `#000000`. |
| `vars.branding.primary_color_contrast` | Contrast color for text on primary color backgrounds (hex). Defaults to `#FFFFFF`. |
| `vars.branding.dark_primary_color` | Primary brand color for dark mode (hex). Defaults to `primary_color` if not set. |
| `vars.branding.dark_primary_color_contrast` | Contrast color for text on dark mode primary color backgrounds (hex). Defaults to `primary_color_contrast` if not set. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change in control these default to #FFFFFF and #000000

https://github.com/knocklabs/control/pull/9860


With [per-tenant branding](/multi-tenancy/per-tenant-branding), Knock resolves these properties against the `tenant_id` on the workflow run, falling back to account-level branding if the tenant has none set.

Expand Down Expand Up @@ -223,6 +229,54 @@ The `<!--[if !mso]>` conditional prevents Outlook, which doesn't support `prefer
}
/>

### Styling buttons

The Knock default email layout automatically styles buttons using your branding colors, with full support for dark mode.

Buttons inserted via the visual editor use the `.block-button` class with `--solid` and `--outline` variants. The default layout styles these using your branding colors:

- **Solid buttons** (`.block-button--solid`) use `primary_color` as the background and `primary_color_contrast` as the text color.
- **Outline buttons** (`.block-button--outline`) use a transparent background with `primary_color` as the text and border color.

Each button's individual style attributes from the visual editor (font size, padding, border radius, etc.) are preserved.

```css title="Visual editor button styling"
/* Light mode */
.block-row--button_set-v1 .block-button--solid {
background-color: {{ vars.branding.primary_color | default: "#000000" }};
color: {{ vars.branding.primary_color_contrast | default: "#FFFFFF" }};
}

.block-row--button_set-v1 .block-button--outline {
background-color: transparent;
color: {{ vars.branding.primary_color | default: "#000000" }};
border-color: {{ vars.branding.primary_color | default: "#000000" }};
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
.block-row--button_set-v1 .block-button--solid {
background-color: {{ vars.branding.dark_primary_color | default: vars.branding.primary_color | default: "#000000" }} !important;
color: {{ vars.branding.dark_primary_color_contrast | default: vars.branding.primary_color_contrast | default: "#FFFFFF" }} !important;
}

.block-row--button_set-v1 .block-button--outline {
background-color: transparent !important;
color: {{ vars.branding.dark_primary_color | default: vars.branding.primary_color | default: "#000000" }} !important;
border-color: {{ vars.branding.dark_primary_color | default: vars.branding.primary_color | default: "#000000" }} !important;
}
}
```

#### Why dark mode uses `!important` overrides

The visual editor renders button colors as static inline `style` attributes, which don't change between light and dark mode. The default layout uses `!important` in dark mode to override the inline styles injected by the visual editor. This means:

- **Light mode.** The button respects per-button colors set in the visual editor.
- **Dark mode.** The layout forces branding-aware colors to ensure readability.

If you need full control over button colors in dark mode, you can customize the dark mode styles in your layout or create a custom layout with your own button styling.

### Injecting workflow run scope into a layout at runtime

You can reference workflow-run-scoped variables in your layout with standard Liquid syntax, as long as they appear _after_ `{{content}}` is first rendered. (For variables needed _above_ `{{content}}`, see [pre-content variables](/integrations/email/layouts#defining-pre-content-variables) below.)
Expand Down
65 changes: 65 additions & 0 deletions content/integrations/email/mjml.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,71 @@ The following MJML tags do not have their HTML children wrapped:
}
/>

## Dark mode support in MJML layouts

MJML layouts support dark mode using the `prefers-color-scheme` media query. The Knock default MJML layout includes dark mode support out of the box, automatically swapping colors and images based on the user's system preference.

To add dark mode support to a custom MJML layout, include the color scheme meta tags in `<mj-head>` and define your dark mode styles in an `<mj-style>` block:

```mjml title="MJML layout with dark mode support"
<mjml>
<mj-head>
<mj-raw>
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
</mj-raw>
<mj-style>
:root { color-scheme: light dark; supported-color-schemes: light dark; }
@media (prefers-color-scheme: dark) { .email-wrapper, .email-body {
background-color: #262626 !important; } p, h1, h2, h3 { color: #ffffff
!important; } }
</mj-style>
</mj-head>
<mj-body>
<!-- layout content -->
</mj-body>
</mjml>
```

For swapping logos and icons in dark mode, see [Swapping logos and icons in dark mode](/integrations/email/layouts#swapping-logos-and-icons-in-dark-mode) in the email layouts documentation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated layouts links on MJML page

Low Severity

This page already links to /integrations/email/layouts at the top. The new dark-mode and button sections add two more markdown links to the same path; only the first mention on a page should be linked.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by learned rule: Link Knock concepts on first mention only per page

Reviewed by Cursor Bugbot for commit 5819a6a. Configure here.


## Styling buttons in MJML layouts

The Knock default MJML layout styles buttons using your branding colors, with automatic dark mode support. This applies to both legacy `mj-button` components and visual editor buttons.

#### How branding colors are applied

- **Solid buttons** use `primary_color` as the background and `primary_color_contrast` as the text color.
- **Outline buttons** use a transparent background with `primary_color` as the text and border color.
- **Dark mode** uses `dark_primary_color` and `dark_primary_color_contrast` (falling back to the light mode values if not set).

```mjml title="MJML branding-aware button styling"
<mj-style>
/* Light mode button styles */ .block-button--solid { background-color: {{
vars.branding.primary_color | default: "#000000" }}; color: {{
vars.branding.primary_color_contrast | default: "#FFFFFF" }}; }
.block-button--outline { background-color: transparent; color: {{
vars.branding.primary_color | default: "#000000" }}; border-color: {{
vars.branding.primary_color | default: "#000000" }}; } /* Dark mode overrides
*/ @media (prefers-color-scheme: dark) { .block-button--solid {
background-color: {{ vars.branding.dark_primary_color | default:
vars.branding.primary_color | default: "#000000" }} !important; color: {{
vars.branding.dark_primary_color_contrast | default:
vars.branding.primary_color_contrast | default: "#FFFFFF" }} !important; }
.block-button--outline { background-color: transparent !important; color: {{
vars.branding.dark_primary_color | default: vars.branding.primary_color |
default: "#000000" }} !important; border-color: {{
vars.branding.dark_primary_color | default: vars.branding.primary_color |
default: "#000000" }} !important; } }
</mj-style>
```

#### Why dark mode uses `!important` overrides

The visual editor renders button colors as static inline styles that don't change between light and dark mode. To ensure buttons remain readable on dark backgrounds, the layout uses `!important` in dark mode to override these inline styles. This means per-button colors from the visual editor are respected in light mode, while dark mode enforces branding-aware colors for readability.

For more details, see [Styling buttons](/integrations/email/layouts#styling-buttons) in the email layouts documentation.

## Limitations

- **MJML layouts require the `<mjml>` root tag.** Layouts set to MJML mode must be valid MJML documents.
Expand Down
18 changes: 11 additions & 7 deletions content/multi-tenancy/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@ Use the [tenant API methods](/api-reference/tenants) to create or update a tenan

### `TenantSettings`

| Property | Description |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `branding.primary_color` | A hex value for the primary color |
| `branding.primary_color_contrast` | A hex value for the contrasting color to use with the primary color |
| `branding.logo_url` | A fully qualified URL for an image to use as the logo of this tenant |
| `branding.icon_url` | A fully qualified URL for an image to use as the icon of this tenant |
| `preference_set` | A complete `PreferenceSet` to use as a default for all recipients with workflows triggered for this tenant |
| Property | Description |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `branding.logo_url` | A fully qualified URL for an image to use as the logo of this tenant |
| `branding.icon_url` | A fully qualified URL for an image to use as the icon of this tenant |
| `branding.dark_logo_url` | A fully qualified URL for the logo to use in dark mode. Defaults to `logo_url` if not set |
| `branding.dark_icon_url` | A fully qualified URL for the icon to use in dark mode. Defaults to `icon_url` if not set |
| `branding.primary_color` | A hex value for the primary color. Defaults to `#000000` |
| `branding.primary_color_contrast` | A hex value for the contrasting color to use with the primary color. Defaults to `#FFFFFF` |
| `branding.dark_primary_color` | A hex value for the primary color in dark mode. Defaults to `primary_color` if not set |
| `branding.dark_primary_color_contrast` | A hex value for the contrasting color in dark mode. Defaults to `primary_color_contrast` if not set |
| `preference_set` | A complete `PreferenceSet` to use as a default for all recipients with workflows triggered for this tenant |
Comment on lines +48 to +58

@cellomatt cellomatt Jun 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only tangential to this PR, but should we ticket adding these properties to the OpenAPI spec so that they're included in settings.branding in the reference here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yep we should. Could you file ticket for that?


<Callout
type="alert"
Expand Down
23 changes: 21 additions & 2 deletions content/template-editor/branding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,29 @@ Any branding variables you set will be available under the `vars.branding.*` nam
rows={[
["vars.branding.logo_url", "The URL of the logo to use for your brand."],
["vars.branding.icon_url", "The URL of the icon to use for your brand."],
["vars.branding.primary_color", "The primary color to use for your brand."],
[
"vars.branding.dark_logo_url",
"The URL of the logo for dark mode. Defaults to logo_url if not set.",
],
[
"vars.branding.dark_icon_url",
"The URL of the icon for dark mode. Defaults to icon_url if not set.",
],
[
"vars.branding.primary_color",
"The primary color to use for your brand (hex). Defaults to #000000.",
],
[
"vars.branding.primary_color_contrast",
"The primary contrast color to use for your brand.",
"The primary contrast color to use for your brand (hex). Defaults to #FFFFFF.",
],
[
"vars.branding.dark_primary_color",
"The primary color for dark mode (hex). Defaults to primary_color if not set.",
],
[
"vars.branding.dark_primary_color_contrast",
"The primary contrast color for dark mode (hex). Defaults to primary_color_contrast if not set.",
],
]}
/>
Expand Down
Loading