email-qa
Filter: $ARGUMENTS
Renderer: ${CLAUDE_SKILL_DIR}/../ui-audit/scripts/render-html.mjs
1. Get final HTML for every template
- Gallery script exists? Look in package.json for
email-gallery,email:previeworemails. If one writes every template with fixture data, run it and use its output dir. - Otherwise write a harness in
/tmp/email-qa/render.ts(outside the repo unless the user wants it kept):- import each renderer (React Email
render(), template-literal functions, MJML); - call it with realistic fixtures, including edge cases: a long name, a missing optional field, 0 and 1 and many items, and both locales;
- write
<template>-<case>.html; - stub anything that reads env, DB or network. Run it with the repo's
tsx.
- import each renderer (React Email
- Always QA the final generated HTML, never the JSX.
2. Render + static checks
node ${CLAUDE_SKILL_DIR}/../ui-audit/scripts/render-html.mjs /tmp/email-qa/out/*.html --out /tmp/email-qa/shots
For each file this produces @600/@375 light and dark, plus @600-noimg, and render.json with static issues. Read the sheets for every template.
3. Checklist (beyond what the script flags)
- Placeholders: none of
undefined,NaN,null,[object Object],{{…}},Invalid Date, emptykr,0 stwhere it should be hidden. - Preheader: present, meaningful, and not a repeat of the subject. Padded so body text doesn't leak into the inbox preview.
- Unsubscribe: required for digests and marketing (plus
List-Unsubscribe+List-Unsubscribe-Postheaders in the send code). Transactional mail is exempt; say which kind each template is. - Images off: the message still makes sense, there's alt text on every meaningful image,
alt=""on spacers, and the CTA is live text, not an image. - Dark mode: logos have a transparent or dark-safe variant, there's no dark text on a now-dark background, and the brand colour still passes 3:1. Gmail/Outlook forced inversion can't be emulated, so flag hard-coded
#000text on transparent backgrounds as a risk. - Outlook desktop: table layout, no flex/grid, VML or
msopadding fallback for buttons, widths inwidth=attributes, nomarginon block elements for spacing. - Links: absolute
https://, UTM where the project uses them, no localhost or preview hosts, and no tracking on unsubscribe. - Copy: right locale throughout (run
copy-checkrules on the strings), sender name, and reply-to (matches what the project expects). - CTA visible without scrolling at 375 and not clipped. The total stays under ~102 KB.
Exit criteria
- Every template is rendered in all variants and the sheets have been read.
- No placeholder leaks or relative links, and the viewport meta is present.
- Each remaining issue is labelled bug (fixed) or risk (reported).
- A short summary to the user, plus the shots dir.