@comet/agent-features 9.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/package.json +19 -0
- package/rules/coding-guidelines/api-nestjs.instructions.md +107 -0
- package/rules/coding-guidelines/cdn.instructions.md +24 -0
- package/rules/coding-guidelines/general.instructions.md +30 -0
- package/rules/coding-guidelines/git.instructions.md +37 -0
- package/rules/coding-guidelines/kubernetes.instructions.md +59 -0
- package/rules/coding-guidelines/libraries.instructions.md +34 -0
- package/rules/coding-guidelines/naming.instructions.md +39 -0
- package/rules/coding-guidelines/postgresql.instructions.md +40 -0
- package/rules/coding-guidelines/react.instructions.md +102 -0
- package/rules/coding-guidelines/security.instructions.md +44 -0
- package/rules/coding-guidelines/styling.instructions.md +50 -0
- package/rules/coding-guidelines/typescript.instructions.md +50 -0
- package/skills/.gitkeep +0 -0
- package/skills/comet-block/SKILL.md +246 -0
- package/skills/comet-block/references/admin-patterns.md +192 -0
- package/skills/comet-block/references/api-patterns.md +183 -0
- package/skills/comet-block/references/block-loader.md +368 -0
- package/skills/comet-block/references/block-types.md +210 -0
- package/skills/comet-block/references/custom-block-field.md +266 -0
- package/skills/comet-block/references/fixtures.md +436 -0
- package/skills/comet-block/references/image.md +341 -0
- package/skills/comet-block/references/migration.md +597 -0
- package/skills/comet-block/references/registration.md +167 -0
- package/skills/comet-block/references/response-summary.md +102 -0
- package/skills/comet-block/references/rich-text.md +309 -0
- package/skills/comet-block/references/select.md +176 -0
- package/skills/comet-block/references/site-patterns.md +202 -0
- package/skills/comet-mail-react/SKILL.md +541 -0
- package/skills/comet-mail-react/references/components-and-theme.md +441 -0
- package/skills/comet-mail-react/references/layout-patterns.md +315 -0
- package/skills/comet-mail-react/references/styling-and-customization.md +306 -0
- package/skills/comet-minor-update/SKILL.md +191 -0
- package/skills/dev-pm/SKILL.md +100 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: comet-mail-react
|
|
3
|
+
description: Guide for building HTML emails with @comet/mail-react and MJML. Use whenever working on email templates, mail markup, MJML components, email theming, email styling, responsive emails, column layouts, multi-column email sections, rendering Comet CMS block data (such as pixel-image blocks) in emails, or anything involving @comet/mail-react or HTML email development — even for seemingly simple tasks like putting content side-by-side in columns, since email client compatibility is a minefield that requires specific patterns and research before implementing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Building HTML Emails with @comet/mail-react
|
|
7
|
+
|
|
8
|
+
`@comet/mail-react` lets you build responsive, themed HTML emails using React components. Under the hood it uses [MJML](https://documentation.mjml.io/) to generate cross-client-compatible HTML. The library provides a theme system, higher-level wrapper components, a style utility layer, and Storybook integration for live previewing emails during development.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Research Before You Code
|
|
13
|
+
|
|
14
|
+
Email development is fundamentally different from web development. There is no shared rendering engine across email clients — the most constrained major client, Outlook on Windows (2007–2019), uses **Microsoft Word** to render HTML, supporting only a fraction of modern CSS. What works perfectly in a browser will often break in email clients, sometimes in surprising ways.
|
|
15
|
+
|
|
16
|
+
Before implementing any visual technique — even things that seem basic like rounded corners, background images, custom fonts, or flexbox layouts — **verify support across email clients**. Many common CSS properties are partially or fully unsupported. This isn't a "nice to have" step — it prevents hours of debugging and rework.
|
|
17
|
+
|
|
18
|
+
### Essential Resources
|
|
19
|
+
|
|
20
|
+
Keep these open during email development:
|
|
21
|
+
|
|
22
|
+
| Resource | What it's for | URL |
|
|
23
|
+
| ------------------------------ | -------------------------------------------------------------------------------- | ------------------------------------ |
|
|
24
|
+
| **Can I email** | Check CSS/HTML feature support across email clients (like caniuse.com for email) | https://www.caniemail.com/ |
|
|
25
|
+
| **MJML Documentation** | Full reference for all MJML tags and their attributes | https://documentation.mjml.io/ |
|
|
26
|
+
| **Litmus Blog & Resources** | Email development best practices, testing guides, client quirks | https://www.litmus.com/blog/ |
|
|
27
|
+
| **Campaign Monitor CSS Guide** | Comprehensive CSS support tables per email client | https://www.campaignmonitor.com/css/ |
|
|
28
|
+
| **Bulletproof Backgrounds** | VML-based background image generator for Outlook | https://www.backgrounds.cm/ |
|
|
29
|
+
| **Bulletproof Buttons** | VML-based rounded button generator for Outlook | https://www.buttons.cm/ |
|
|
30
|
+
|
|
31
|
+
### The Research Habit
|
|
32
|
+
|
|
33
|
+
When implementing any visual feature:
|
|
34
|
+
|
|
35
|
+
1. Check [Can I email](https://www.caniemail.com/) for the CSS properties involved
|
|
36
|
+
2. If the property isn't supported in Outlook, search for VML workarounds or provide a graceful fallback (skipping border-radius is generally acceptable)
|
|
37
|
+
3. Test in Storybook with the MJML Warnings panel open
|
|
38
|
+
4. When uncertain, consult the Litmus blog or Campaign Monitor guide for known patterns
|
|
39
|
+
|
|
40
|
+
This applies to seemingly simple things: `border-radius`, `background-image`, `flexbox`, `gap`, custom fonts — all have partial or no support in major email clients.
|
|
41
|
+
|
|
42
|
+
### Library Documentation
|
|
43
|
+
|
|
44
|
+
Full documentation for `@comet/mail-react`: https://docs.comet-dxp.com/docs/features-modules/building-html-emails/
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## The MJML Layout Model
|
|
49
|
+
|
|
50
|
+
MJML enforces a strict **section → column → content** nesting hierarchy:
|
|
51
|
+
|
|
52
|
+
- **`MjmlSection`** — a full-width horizontal row
|
|
53
|
+
- **`MjmlColumn`** — divides a section into vertical columns (stacking on mobile by default)
|
|
54
|
+
- **Content components** (`MjmlText`, `MjmlImage`, `MjmlButton`, etc.) — placed inside a column
|
|
55
|
+
|
|
56
|
+
Content components placed outside this hierarchy produce MJML validation warnings and broken layouts. The MJML Warnings panel in Storybook surfaces these during development.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<MjmlSection indent>
|
|
60
|
+
<MjmlColumn>
|
|
61
|
+
<MjmlText variant="heading">Section title</MjmlText>
|
|
62
|
+
<MjmlText variant="body" bottomSpacing>
|
|
63
|
+
Body paragraph with spacing below.
|
|
64
|
+
</MjmlText>
|
|
65
|
+
<MjmlImage src="https://example.com/image.jpg" alt="Example" />
|
|
66
|
+
</MjmlColumn>
|
|
67
|
+
</MjmlSection>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Multi-Column Layouts
|
|
71
|
+
|
|
72
|
+
MJML has no `gap` property. Column padding reduces the content area _inside_ the column — it doesn't add space between column cells. To create a visual gap between columns, apply padding to the inner edges of adjacent columns (`paddingRight` on the left column, `paddingLeft` on the right column). Their sum becomes the visible gap.
|
|
73
|
+
|
|
74
|
+
For two equal columns, apply half the desired gap to each column's inner edge. Both columns have the same total padding, so MJML's equal-width split produces equal content areas without explicit `width` props:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const columnGap = 20;
|
|
78
|
+
const halfGap = columnGap / 2;
|
|
79
|
+
|
|
80
|
+
<MjmlSection indent className="twoColumnsSection">
|
|
81
|
+
<MjmlColumn className="twoColumnsSection__leftColumn" paddingRight={halfGap}>
|
|
82
|
+
<MjmlText>Left column</MjmlText>
|
|
83
|
+
</MjmlColumn>
|
|
84
|
+
<MjmlColumn className="twoColumnsSection__rightColumn" paddingLeft={halfGap}>
|
|
85
|
+
<MjmlText>Right column</MjmlText>
|
|
86
|
+
</MjmlColumn>
|
|
87
|
+
</MjmlSection>;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Do not** apply equal padding on all sides of every column — this adds extra outer-edge spacing that compounds with `indent`/`contentIndentation`, pushing content inward beyond the theme's intended margins.
|
|
91
|
+
|
|
92
|
+
On mobile, reset the gap padding so content stretches full-width, and add a vertical margin between the stacked columns. Column padding compiles to an inner `<td>`, so target it via `.className > table > tbody > tr > td`.
|
|
93
|
+
|
|
94
|
+
→ For complete two-column patterns (equal-width and fixed+fluid) with responsive styles, CSS targeting rules, and the `direction="rtl"` technique for controlling mobile stack order, read [`references/layout-patterns.md`](references/layout-patterns.md).
|
|
95
|
+
|
|
96
|
+
### Ending Tags
|
|
97
|
+
|
|
98
|
+
Some MJML components are [**ending tags**](https://documentation.mjml.io/#ending-tags) — they accept raw HTML as children but **cannot** contain other MJML components. The most common: `MjmlText`, `MjmlButton`, `MjmlTable`, `MjmlRaw`.
|
|
99
|
+
|
|
100
|
+
Once inside an ending tag, you are in **HTML-land for the entire subtree**. Use HTML elements (`<span>`, `<a>`, `<table>`, `<td>`) but not MJML components. The library provides `HtmlText` and `HtmlInlineLink` for themed text and links inside ending tags.
|
|
101
|
+
|
|
102
|
+
For raw HTML layouts outside text, use `MjmlRaw` (or `MjmlTable`). These are escape hatches for cases MJML components can't handle — use them as a last resort.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## The Styling Model
|
|
107
|
+
|
|
108
|
+
Email styling follows a **desktop-first** approach:
|
|
109
|
+
|
|
110
|
+
1. **Base/default styles are inline** — applied through MJML component props or explicit `style` attributes on HTML elements. Desktop clients like Outlook ignore `<style>` blocks entirely, so the base rendering must look correct with inline styles alone.
|
|
111
|
+
|
|
112
|
+
2. **Responsive overrides are progressive enhancement** — registered via `registerStyles` with media queries targeting mobile viewports. Clients that support media queries also support modern CSS, so properties like `flex`, and CSS custom properties are safe inside media queries.
|
|
113
|
+
|
|
114
|
+
3. **`!important` is required** in media query overrides — because inline styles take precedence over `<style>` block rules, responsive overrides need `!important` to win.
|
|
115
|
+
|
|
116
|
+
Never rely on `<style>` blocks for base/desktop layout. Set all default styles inline via MJML component props.
|
|
117
|
+
|
|
118
|
+
### Prefer Theme Breakpoints
|
|
119
|
+
|
|
120
|
+
Always use `theme.breakpoints.*.belowMediaQuery` inside `registerStyles` instead of hardcoding media query values. This keeps responsive styles in sync with the theme configuration. If a breakpoint value is needed repeatedly but doesn't exist in the theme, add it via `createBreakpoint` and module augmentation rather than duplicating raw media queries. Reserve hardcoded media queries for genuinely one-off values.
|
|
121
|
+
|
|
122
|
+
→ For the full `registerStyles` API, `css` helper, and custom component patterns, read [`references/styling-and-customization.md`](references/styling-and-customization.md).
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Common Pitfalls
|
|
127
|
+
|
|
128
|
+
### Avoid Block-Level HTML Elements Inside Ending Tags
|
|
129
|
+
|
|
130
|
+
Don't use `<p>`, `<h1>`, `<h2>`, or other block-level HTML elements inside ending tags. They have wildly inconsistent default margins and spacing across email clients and add no rendering value in email HTML. Instead, use `<td>`, `<div>`, and `<span>` for structure, and build your typography hierarchy through `MjmlText`/`HtmlText` variants rather than HTML semantics. If a block-level element is truly unavoidable, always reset its margins inline: `style={{ margin: 0 }}`.
|
|
131
|
+
|
|
132
|
+
### Set `mso-line-height-rule: exactly` on Every Manual `line-height`
|
|
133
|
+
|
|
134
|
+
Outlook calculates line-height using its own rules, causing unexpected vertical spacing. **Every time** you set `line-height` on a raw HTML element inside an ending tag (`MjmlRaw`, `MjmlText`, etc.), you must also set `mso-line-height-rule: exactly` as an inline style on the same element. This applies to `<span>`, `<td>`, `<div>`, or any other element where you manually control line height. `HtmlText` and built-in MJML components handle this automatically — but any hand-written HTML needs it explicitly.
|
|
135
|
+
|
|
136
|
+
### No CSS `background-image` in Outlook
|
|
137
|
+
|
|
138
|
+
Outlook ignores `background-image` entirely. Use a VML-based workaround for Outlook support, or provide a `background-color` fallback for graceful degradation. See [Bulletproof Backgrounds](https://www.backgrounds.cm/).
|
|
139
|
+
|
|
140
|
+
### No CSS `border-radius` in Outlook
|
|
141
|
+
|
|
142
|
+
Outlook ignores `border-radius` — rounded corners render as sharp rectangles. The workaround is VML `v:roundrect` in conditional comments (`<!--[if mso]>`). See [Bulletproof Buttons](https://www.buttons.cm/) and the [Litmus VML button snippet](https://litmus.com/community/snippets/7-bulletproof-button-vml-approach).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Theme Setup & Type-Safety
|
|
147
|
+
|
|
148
|
+
Create a theme with `createTheme()` and pass it to `MjmlMailRoot`:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { createTheme, MjmlMailRoot } from "@comet/mail-react";
|
|
152
|
+
|
|
153
|
+
const theme = createTheme({
|
|
154
|
+
sizes: {
|
|
155
|
+
bodyWidth: 700,
|
|
156
|
+
contentIndentation: { default: 40, mobile: 20 },
|
|
157
|
+
},
|
|
158
|
+
text: {
|
|
159
|
+
fontFamily: "Georgia, serif",
|
|
160
|
+
fontSize: "18px",
|
|
161
|
+
},
|
|
162
|
+
colors: {
|
|
163
|
+
background: { body: "#EAEAEA", content: "#FAFAFA" },
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
function MyEmail() {
|
|
168
|
+
return <MjmlMailRoot theme={theme}>{/* email content */}</MjmlMailRoot>;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Contributing to `<MjmlHead>` and `<MjmlAttributes>`
|
|
173
|
+
|
|
174
|
+
`MjmlMailRoot` accepts two optional slot props for content that can't be expressed via `registerStyles`:
|
|
175
|
+
|
|
176
|
+
- `head` — appended inside `<MjmlHead>` after the registered-styles block. Use for `<MjmlFont>`, `<MjmlConditionalComment>`, `<MjmlPreview>`, or `<MjmlStyle>` content that depends on React context at render time.
|
|
177
|
+
- `attributes` — appended inside the built-in `<MjmlAttributes>` after the default `<MjmlAll>`. Use for `<MjmlClass>` or per-element defaults (e.g. `<MjmlText fontSize="14px" />`).
|
|
178
|
+
|
|
179
|
+
Pass the children directly — do not wrap them in another `<MjmlHead>` / `<MjmlAttributes>`:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<MjmlMailRoot theme={theme} attributes={<MjmlClass name="link" color="blue" />} head={<MjmlFont name="Foo" href="https://example.com/foo.css" />}>
|
|
183
|
+
{/* email content */}
|
|
184
|
+
</MjmlMailRoot>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
For module-scoped responsive CSS that depends only on the theme, prefer `registerStyles`.
|
|
188
|
+
|
|
189
|
+
### Module Augmentation for Type-Safety
|
|
190
|
+
|
|
191
|
+
`@comet/mail-react` uses TypeScript module augmentation to make custom theme tokens type-safe. Always augment these interfaces when extending the theme — TypeScript will then error on typos or unknown keys.
|
|
192
|
+
|
|
193
|
+
**Text Variants** — restrict `variant` prop to defined names:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
declare module "@comet/mail-react" {
|
|
197
|
+
interface TextVariants {
|
|
198
|
+
heading: true;
|
|
199
|
+
body: true;
|
|
200
|
+
caption: true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Custom Breakpoints** — make new breakpoint keys available in responsive values:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
declare module "@comet/mail-react" {
|
|
209
|
+
interface ThemeBreakpoints {
|
|
210
|
+
tablet: ThemeBreakpoint;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Custom Colors** — add project-specific color tokens:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
declare module "@comet/mail-react" {
|
|
219
|
+
interface ThemeBackgroundColors {
|
|
220
|
+
highlight: string;
|
|
221
|
+
}
|
|
222
|
+
interface ThemeColors {
|
|
223
|
+
brand: { primary: string; secondary: string };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Place `declare module` blocks in your theme file below the `createTheme()` call.
|
|
229
|
+
|
|
230
|
+
→ For the full theme structure, defaults, and all component props, read [`references/components-and-theme.md`](references/components-and-theme.md).
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Configuration
|
|
235
|
+
|
|
236
|
+
`Config` is an augmentable interface that can be used to expose, e.g., environment-specific values such as asset base URLs.
|
|
237
|
+
|
|
238
|
+
- **`Config`** — augmentable interface, no built-in keys. All keys optional.
|
|
239
|
+
- **`MjmlMailRoot.config`** — optional prop that exposes a `Config` value to descendants.
|
|
240
|
+
- **`useConfig`** — hook returning the nearest `Config`, or `{}` if no provider is mounted.
|
|
241
|
+
- **`ConfigProvider`** — standalone provider for cases that bypass `MjmlMailRoot`.
|
|
242
|
+
|
|
243
|
+
Add keys via module augmentation:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
declare module "@comet/mail-react" {
|
|
247
|
+
interface Config {
|
|
248
|
+
assetBaseUrl?: string;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Wire at the root and read from any descendant:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { MjmlMailRoot, useConfig, type Config } from "@comet/mail-react";
|
|
257
|
+
|
|
258
|
+
const config: Config = { assetBaseUrl: process.env.ASSET_BASE_URL };
|
|
259
|
+
|
|
260
|
+
<MjmlMailRoot config={config}>{/* descendants can call useConfig() */}</MjmlMailRoot>;
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Components Overview
|
|
266
|
+
|
|
267
|
+
### MJML Components (Layout Level)
|
|
268
|
+
|
|
269
|
+
| Component | Purpose | CSS Classes |
|
|
270
|
+
| --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
|
|
271
|
+
| `MjmlMailRoot` | Root element, provides theme, renders `<mjml>` skeleton | — |
|
|
272
|
+
| `MjmlWrapper` | Groups sections sharing a background; theme-aware default bg | — |
|
|
273
|
+
| `MjmlSection` | Full-width row, supports `indent` and `disableResponsiveBehavior` | `.mjmlSection`, `.mjmlSection--indented` |
|
|
274
|
+
| `MjmlColumn` | Vertical column inside a section | — |
|
|
275
|
+
| `MjmlText` | Themed text block with `variant` and `bottomSpacing` | `.mjmlText`, `.mjmlText--{variant}`, `.mjmlText--bottomSpacing` |
|
|
276
|
+
| `MjmlImage` | Responsive image | `.mjmlImage` |
|
|
277
|
+
| `MjmlPixelImageBlock` | Renders a Comet CMS `PixelImageBlockData` via `MjmlImage` | `.mjmlPixelImageBlock` |
|
|
278
|
+
| `MjmlButton` | Button (ending tag) | — |
|
|
279
|
+
| `MjmlDivider` | Themed horizontal divider, configurable through theme and variants | `.mjmlDivider`, `.mjmlDivider--{variant}` |
|
|
280
|
+
| `MjmlSpacer` | Vertical spacing | — |
|
|
281
|
+
| `MjmlRaw` | Raw HTML escape hatch (ending tag) | — |
|
|
282
|
+
|
|
283
|
+
### HTML Components (Inside Ending Tags)
|
|
284
|
+
|
|
285
|
+
| Component | Purpose | CSS Classes |
|
|
286
|
+
| --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
|
|
287
|
+
| `HtmlText` | Themed text as HTML element (default `<td>`) | `.htmlText`, `.htmlText--{variant}`, `.htmlText--bottomSpacing` |
|
|
288
|
+
| `HtmlInlineLink` | `<a>` that inherits parent text styles, works in Outlook | `.htmlInlineLink` |
|
|
289
|
+
| `HtmlImage` | Responsive image (`<img>`) | `.htmlImage` |
|
|
290
|
+
| `HtmlPixelImageBlock` | Renders a Comet CMS `PixelImageBlockData` as `<img>` | `.htmlPixelImageBlock` |
|
|
291
|
+
| `HtmlDivider` | Themed horizontal divider, configurable through theme and variants | `.htmlDivider`, `.htmlDivider--{variant}` |
|
|
292
|
+
|
|
293
|
+
Text components (`MjmlText`, `HtmlText`) support `variant` and `bottomSpacing` props tied to the theme. Variants define typography presets (font size, weight, line height, color). Both support responsive values that change at different breakpoints. Set a `defaultVariant` in the theme to apply a variant automatically when none is specified.
|
|
294
|
+
|
|
295
|
+
All components are imported from `@comet/mail-react` — never from `@faire/mjml-react` directly.
|
|
296
|
+
|
|
297
|
+
→ For complete component props, responsive values, scoped theming, and MJML re-exports, read [`references/components-and-theme.md`](references/components-and-theme.md).
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Blocks
|
|
302
|
+
|
|
303
|
+
`@comet/mail-react` ships components that render Comet CMS block data — currently `PixelImageBlockData`. Reach for these instead of raw `MjmlImage` / `<img>` whenever the source is a CMS block-data record.
|
|
304
|
+
|
|
305
|
+
### Pixel-image blocks
|
|
306
|
+
|
|
307
|
+
| Component | Renders | Use within |
|
|
308
|
+
| --------------------- | ----------------------- | ---------------------------------------------- |
|
|
309
|
+
| `MjmlPixelImageBlock` | re-exported `MjmlImage` | an `MjmlColumn` (standard MJML layout) |
|
|
310
|
+
| `HtmlPixelImageBlock` | raw `<img>` | raw HTML or MJML ending tags such as `MjmlRaw` |
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
<MjmlSection indent>
|
|
314
|
+
<MjmlColumn>
|
|
315
|
+
<MjmlPixelImageBlock data={pixelImageData} width={536} />
|
|
316
|
+
</MjmlColumn>
|
|
317
|
+
</MjmlSection>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### Configuration
|
|
321
|
+
|
|
322
|
+
Both blocks read `validSizes` and `baseUrl` from `config.pixelImageBlock` and throw if it's missing. Wire it once on `MjmlMailRoot.config`. In a typical Comet project, `validSizes` is the union of `cometConfig.images.imageSizes` and `cometConfig.images.deviceSizes`; `baseUrl` is the API URL.
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
const config: Config = {
|
|
326
|
+
pixelImageBlock: {
|
|
327
|
+
validSizes: [...cometConfig.images.imageSizes, ...cometConfig.images.deviceSizes],
|
|
328
|
+
baseUrl: process.env.API_URL,
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### Render width
|
|
334
|
+
|
|
335
|
+
The required `width` prop is the desktop render width — the width at which the image displays in the default breakpoint. The block picks the actual source size from `validSizes`, accounting for retina displays.
|
|
336
|
+
|
|
337
|
+
Use `largestPossibleRenderWidth` when the image stretches wider on a narrower breakpoint than its desktop width — e.g. a two-column layout that stacks on mobile. Defaults to `theme.sizes.bodyWidth`.
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
<MjmlPixelImageBlock data={pixelImageData} width={300} largestPossibleRenderWidth={420} />
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### Aspect ratio
|
|
344
|
+
|
|
345
|
+
By default, the rendered aspect ratio comes from the DAM crop area. Override it with the `aspectRatio` prop — accepts a number or a `"WxH"` / `"W:H"` / `"W/H"` string.
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
<MjmlPixelImageBlock data={pixelImageData} width={536} aspectRatio="16x9" />
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
When `data.damFile?.image` is absent, both blocks render nothing — no element, no error. Render a placeholder at the call site if you need one.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Custom Components
|
|
356
|
+
|
|
357
|
+
When built-in components don't cover your needs, create custom components. **Always try to compose layouts using MJML components first** (`MjmlSection`, `MjmlColumn`, `MjmlText`, `MjmlImage`, etc.) — they handle cross-client compatibility automatically. Only drop into raw HTML (`MjmlRaw`, `MjmlTable`) when the MJML layout model genuinely can't express the structure you need. Raw HTML is an escape hatch, not the default approach.
|
|
358
|
+
|
|
359
|
+
When raw HTML is necessary, follow these conventions:
|
|
360
|
+
|
|
361
|
+
### BEM Class Naming
|
|
362
|
+
|
|
363
|
+
Use BEM with camelCase blocks for CSS class names:
|
|
364
|
+
|
|
365
|
+
| BEM Part | Pattern | Example |
|
|
366
|
+
| -------- | ----------------------------- | ------------------------- |
|
|
367
|
+
| Block | `componentName` | `calloutBox` |
|
|
368
|
+
| Element | `componentName__elementName` | `calloutBox__title` |
|
|
369
|
+
| Modifier | `componentName--modifierName` | `calloutBox--highlighted` |
|
|
370
|
+
|
|
371
|
+
### The Pattern
|
|
372
|
+
|
|
373
|
+
1. **Inline styles** for base/desktop rendering
|
|
374
|
+
2. **BEM class names** on elements that need responsive overrides
|
|
375
|
+
3. **`registerStyles`** at module level with media queries
|
|
376
|
+
4. **`!important`** on all responsive overrides
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
import { css, MjmlColumn, MjmlRaw, MjmlSection, registerStyles } from "@comet/mail-react";
|
|
380
|
+
|
|
381
|
+
function CalloutBox({ title, children }: { title: string; children: React.ReactNode }) {
|
|
382
|
+
return (
|
|
383
|
+
<MjmlSection>
|
|
384
|
+
<MjmlColumn>
|
|
385
|
+
<MjmlRaw>
|
|
386
|
+
<tr>
|
|
387
|
+
<td className="calloutBox" style={{ border: "2px solid #0066cc", borderRadius: "8px", padding: "20px" }}>
|
|
388
|
+
<span
|
|
389
|
+
className="calloutBox__title"
|
|
390
|
+
style={{ display: "block", margin: "0 0 8px 0", fontSize: "18px", lineHeight: "24px", msoLineHeightRule: "exactly" }}
|
|
391
|
+
>
|
|
392
|
+
{title}
|
|
393
|
+
</span>
|
|
394
|
+
<div>{children}</div>
|
|
395
|
+
</td>
|
|
396
|
+
</tr>
|
|
397
|
+
</MjmlRaw>
|
|
398
|
+
</MjmlColumn>
|
|
399
|
+
</MjmlSection>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
registerStyles(
|
|
404
|
+
(theme) => css`
|
|
405
|
+
${theme.breakpoints.mobile.belowMediaQuery} {
|
|
406
|
+
.calloutBox {
|
|
407
|
+
padding: 12px !important;
|
|
408
|
+
}
|
|
409
|
+
.calloutBox__title {
|
|
410
|
+
font-size: 16px !important;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
`,
|
|
414
|
+
);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
→ For the full `registerStyles` API, `belowMediaQuery` pattern, overriding built-in components, and `slotProps`, read [`references/styling-and-customization.md`](references/styling-and-customization.md).
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Rendering
|
|
422
|
+
|
|
423
|
+
Use `renderMailHtml` to convert the React tree to final HTML for sending:
|
|
424
|
+
|
|
425
|
+
```tsx
|
|
426
|
+
import { MjmlMailRoot, MjmlSection, MjmlColumn, MjmlText } from "@comet/mail-react";
|
|
427
|
+
import { renderMailHtml } from "@comet/mail-react/server";
|
|
428
|
+
|
|
429
|
+
const { html, mjmlWarnings } = renderMailHtml(
|
|
430
|
+
<MjmlMailRoot theme={theme}>
|
|
431
|
+
<MjmlSection indent>
|
|
432
|
+
<MjmlColumn>
|
|
433
|
+
<MjmlText>Hello, world!</MjmlText>
|
|
434
|
+
</MjmlColumn>
|
|
435
|
+
</MjmlSection>
|
|
436
|
+
</MjmlMailRoot>,
|
|
437
|
+
);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
- **Server** (`@comet/mail-react/server`) — uses `mjml`, requires Node.js
|
|
441
|
+
- **Client** (`@comet/mail-react/client`) — uses `mjml-browser`, works without `fs`
|
|
442
|
+
- `renderMailHtml` is **not** on the main `@comet/mail-react` barrel — always import from `/server` or `/client`
|
|
443
|
+
- Returns `{ html: string; mjmlWarnings: MjmlWarning[] }` — warnings are collected, not thrown
|
|
444
|
+
|
|
445
|
+
### Logging MJML Warnings
|
|
446
|
+
|
|
447
|
+
When generating emails outside Storybook (e.g., in a mail template's `generateMail` method), always log `mjmlWarnings` in development to catch structural issues early:
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
const { html, mjmlWarnings } = renderMailHtml(/* ... */);
|
|
451
|
+
|
|
452
|
+
if (process.env.NODE_ENV === "development" && mjmlWarnings.length) {
|
|
453
|
+
console.warn(`${mjmlWarnings.length} MJML Warnings`, mjmlWarnings);
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Never log MJML warnings in production.** These warnings flag structural MJML issues (e.g., content outside the section → column → content hierarchy) and are useful during development, but the rendered HTML is always produced successfully regardless of warnings. In rare cases, achieving a specific layout intentionally requires a technically invalid MJML structure — logging these in production would spam error trackers like Sentry with noise that cannot be acted upon.
|
|
458
|
+
|
|
459
|
+
Outside Storybook, wrap content in `MjmlMailRoot` yourself (the Storybook decorator handles it automatically).
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Storybook Development
|
|
464
|
+
|
|
465
|
+
### Setup
|
|
466
|
+
|
|
467
|
+
Add the addon to `.storybook/main.ts`:
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
const config = {
|
|
471
|
+
addons: [
|
|
472
|
+
// ... other addons
|
|
473
|
+
"@comet/mail-react/storybook",
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
This single entry auto-configures:
|
|
479
|
+
|
|
480
|
+
- A **decorator** that wraps each story in `MjmlMailRoot`, converts MJML to HTML, and displays the rendered email
|
|
481
|
+
- A **Copy Mail HTML** toolbar button for copying rendered HTML to the clipboard
|
|
482
|
+
- A **Use Public Image URLs** toggle that replaces image sources with public placeholders — useful for testing on external services (Litmus, Email on Acid) that can't reach localhost
|
|
483
|
+
- An **MJML Warnings** panel for debugging validation issues
|
|
484
|
+
|
|
485
|
+
### Writing Stories
|
|
486
|
+
|
|
487
|
+
Stories only define the email content — the decorator handles `MjmlMailRoot`. Every story should render the actual component being demonstrated (not just surrounding context):
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
import { MjmlColumn, MjmlSection, MjmlText } from "@comet/mail-react";
|
|
491
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
492
|
+
|
|
493
|
+
const config: Meta = { title: "Mails/WelcomeEmail" };
|
|
494
|
+
export default config;
|
|
495
|
+
|
|
496
|
+
export const Basic: StoryObj = {
|
|
497
|
+
render: () => (
|
|
498
|
+
<MjmlSection indent>
|
|
499
|
+
<MjmlColumn>
|
|
500
|
+
<MjmlText>Hello from my first email!</MjmlText>
|
|
501
|
+
</MjmlColumn>
|
|
502
|
+
</MjmlSection>
|
|
503
|
+
),
|
|
504
|
+
};
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Pass a custom theme via `parameters.theme`:
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
export const CustomTheme: StoryObj = {
|
|
511
|
+
parameters: { theme: createTheme({ sizes: { bodyWidth: 500 } }) },
|
|
512
|
+
render: () => (
|
|
513
|
+
<MjmlSection indent>
|
|
514
|
+
<MjmlColumn>
|
|
515
|
+
<MjmlText>Narrower email at 500px</MjmlText>
|
|
516
|
+
</MjmlColumn>
|
|
517
|
+
</MjmlSection>
|
|
518
|
+
),
|
|
519
|
+
};
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Development Workflow
|
|
523
|
+
|
|
524
|
+
1. Write email templates as Storybook stories
|
|
525
|
+
2. Preview rendered HTML in the Storybook canvas
|
|
526
|
+
3. Check the **MJML Warnings** panel for validation issues
|
|
527
|
+
4. Use **Copy Mail HTML** to test in external services (Litmus, Email on Acid)
|
|
528
|
+
5. Enable **Use Public Image URLs** when testing on services that can't reach localhost (e.g., Litmus, Email on Acid)
|
|
529
|
+
|
|
530
|
+
### Cross-Client Testing
|
|
531
|
+
|
|
532
|
+
Storybook previews show how the email renders in a web browser, but email clients vary dramatically. Use services like [Litmus](https://www.litmus.com/) or [Email on Acid](https://www.emailonacid.com/) to test the rendered HTML across real email clients and devices. The **Copy Mail HTML** button and **Use Public Image URLs** toggle in Storybook are designed for this workflow.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Related Modules
|
|
537
|
+
|
|
538
|
+
The `@comet/mail-react` package focuses on building email markup. For sending emails and managing templates in a Comet project:
|
|
539
|
+
|
|
540
|
+
- **Mail Templates Module** — server-side template registration, dependency injection, and sending. Integrates with `@comet/mail-react` via `renderMailHtml`. Docs: https://docs.comet-dxp.com/docs/features-modules/mail-templates-module/
|
|
541
|
+
- **Mailer Module** — lower-level mail sending service. Docs: https://docs.comet-dxp.com/docs/features-modules/mailer-module/
|